A framework-agnostic document viewer library for JavaScript and TypeScript that allows you to display various document formats in your web applications.
- Framework Independent: Works with vanilla JavaScript, React, Vue, Angular, Svelte, and any other framework
- TypeScript Support: Fully typed for better developer experience
- Multiple Formats: Supports PDF, Office documents (XLS/XLSX, DOC/DOCX, PPT/PPTX), images (PNG, JPG/JPEG), and text files
- Easy to Use: Simple API with minimal configuration
- Customizable: Flexible styling and configuration options
- Lightweight: Minimal dependencies
- PDF (
.pdf) - Excel (
.xls,.xlsx) - Word (
.doc,.docx) - PowerPoint (
.ppt,.pptx) - Images (
.png,.jpg,.jpeg) - Text (
.txt)
npm install universal-doc-viewerOr with yarn:
yarn add universal-doc-viewerIf you're working with the source code:
# Clone the repository
git clone <repository-url>
cd ng-doc-viewer
# Install dependencies
npm install
# Build the library (REQUIRED before testing)
npm run build
# Test with a local HTTP server
python3 -m http.server 8000
# Then open http://localhost:8000/simple-test.htmlSee TESTING.md for detailed testing instructions.
import { DocumentViewer } from 'universal-doc-viewer';
const viewer = new DocumentViewer({
container: '#viewer-container',
url: 'https://pdfobject.com/pdf/sample.pdf',
width: '100%',
height: '800px'
});
viewer.render();import { createViewer } from 'universal-doc-viewer';
const viewer = await createViewer({
container: '#viewer-container',
url: 'https://pdfobject.com/pdf/sample.pdf'
});<!DOCTYPE html>
<html>
<head>
<title>Document Viewer</title>
</head>
<body>
<div id="viewer-container"></div>
<script type="module">
import { DocumentViewer } from 'universal-doc-viewer';
const viewer = new DocumentViewer({
container: '#viewer-container',
url: 'https://pdfobject.com/pdf/sample.pdf',
height: '90vh'
});
viewer.render();
</script>
</body>
</html>import { useEffect, useRef } from 'react';
import { DocumentViewer } from 'universal-doc-viewer';
function DocumentViewerComponent({ url }) {
const containerRef = useRef(null);
const viewerRef = useRef(null);
useEffect(() => {
if (containerRef.current) {
viewerRef.current = new DocumentViewer({
container: containerRef.current,
url: url,
height: '600px'
});
viewerRef.current.render();
}
return () => {
if (viewerRef.current) {
viewerRef.current.destroy();
}
};
}, [url]);
return <div ref={containerRef}></div>;
}<template>
<div ref="viewerContainer"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { DocumentViewer } from 'universal-doc-viewer';
const props = defineProps({
url: String
});
const viewerContainer = ref(null);
let viewer = null;
onMounted(() => {
viewer = new DocumentViewer({
container: viewerContainer.value,
url: props.url,
height: '600px'
});
viewer.render();
});
onUnmounted(() => {
if (viewer) {
viewer.destroy();
}
});
watch(() => props.url, (newUrl) => {
if (viewer) {
viewer.changeDocument(newUrl);
}
});
</script>import { Component, ElementRef, Input, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { DocumentViewer } from 'universal-doc-viewer';
@Component({
selector: 'app-document-viewer',
template: '<div #viewerContainer></div>'
})
export class DocumentViewerComponent implements OnInit, OnDestroy {
@Input() url: string;
@ViewChild('viewerContainer', { static: true }) viewerContainer: ElementRef;
private viewer: DocumentViewer;
ngOnInit() {
this.viewer = new DocumentViewer({
container: this.viewerContainer.nativeElement,
url: this.url,
height: '600px'
});
this.viewer.render();
}
ngOnDestroy() {
if (this.viewer) {
this.viewer.destroy();
}
}
}<script>
import { onMount, onDestroy } from 'svelte';
import { DocumentViewer } from 'universal-doc-viewer';
export let url;
let viewerContainer;
let viewer;
onMount(() => {
viewer = new DocumentViewer({
container: viewerContainer,
url: url,
height: '600px'
});
viewer.render();
});
onDestroy(() => {
if (viewer) {
viewer.destroy();
}
});
$: if (viewer && url) {
viewer.changeDocument(url);
}
</script>
<div bind:this={viewerContainer}></div>The main class for creating document viewers.
new DocumentViewer(options: ViewerOptions)interface ViewerOptions {
// Required
container: HTMLElement | string; // Container element or selector
url: string; // Document URL
// Optional
type?: DocumentType; // Document type (auto-detected if not provided)
width?: string; // Viewer width (default: '100%')
height?: string; // Viewer height (default: '600px')
styles?: Partial<CSSStyleDeclaration>; // Custom styles
showLoader?: boolean; // Show loading indicator (default: true)
loadingMessage?: string; // Custom loading message
useGoogleDocsViewer?: boolean; // Use Google Docs for Office files (default: true)
useMicrosoftViewer?: boolean; // Use Microsoft viewer for Office files (default: false)
useBlobFallback?: boolean; // Use blob/memory fallback for CORS issues (default: true)
onLoad?: () => void; // Success callback
onError?: (error: Error) => void; // Error callback
}Renders the document in the container.
await viewer.render();Reloads the current document.
await viewer.reload();Destroys the viewer and cleans up resources.
viewer.destroy();Changes the document and re-renders.
await viewer.changeDocument('https://example.com/new-document.pdf');Gets the current document type.
const type = viewer.getDocumentType(); // 'pdf' | 'xlsx' | etc.Helper methods to check document type.
if (viewer.isPDF()) {
console.log('Viewing a PDF');
}const viewer = new DocumentViewer({
container: '#viewer',
url: 'document.pdf',
styles: {
border: '2px solid #333',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0,0,0,0.1)'
}
});const viewer = new DocumentViewer({
container: '#viewer',
url: 'document.pdf',
onError: (error) => {
console.error('Failed to load document:', error);
alert('Could not load the document. Please try again.');
},
onLoad: () => {
console.log('Document loaded successfully!');
}
});// Use Microsoft Office Online Viewer
const viewer = new DocumentViewer({
container: '#viewer',
url: 'spreadsheet.xlsx',
useMicrosoftViewer: true
});
// Use Google Docs Viewer (default)
const viewer2 = new DocumentViewer({
container: '#viewer2',
url: 'presentation.pptx',
useGoogleDocsViewer: true
});const viewer = new DocumentViewer({
container: '#viewer',
url: 'initial-document.pdf',
height: '700px'
});
await viewer.render();
// Later, change to a different document
document.getElementById('changeBtn').addEventListener('click', async () => {
await viewer.changeDocument('new-document.docx');
});You can also use individual viewer classes directly:
import { PDFViewer, ImageViewer, TextViewer, OfficeViewer } from 'universal-doc-viewer';
// PDF Viewer
const pdfViewer = new PDFViewer(containerElement, {
container: containerElement,
url: 'document.pdf'
});
await pdfViewer.render();
// Image Viewer
const imageViewer = new ImageViewer(containerElement, {
container: containerElement,
url: 'image.png'
});
await imageViewer.render();- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
Office documents (Excel, Word, PowerPoint) are displayed using online viewers:
- Google Docs Viewer (default): Free, no authentication required
- Microsoft Office Online Viewer: Alternative option
Note: Both viewers require the document to be publicly accessible via a URL. Local files or authentication-protected files may not work with these viewers.
PDF files are rendered using PDF.js, which provides:
- Client-side rendering
- Page navigation controls
- High-quality rendering
Loading Strategy: The library automatically loads PDF.js in one of two ways:
-
Bundled environments (Webpack, Rollup, etc.): If you're using a bundler, you can optionally install
pdfjs-distas a dependency:npm install pdfjs-dist
The library will use the bundled version automatically.
-
Browser/CDN usage: If
pdfjs-distis not installed or the import fails, the library automatically falls back to loading PDF.js from CDN (cdnjs.cloudflare.com). No additional configuration needed!
This dual approach ensures the library works seamlessly in any environment, whether you're using it directly in the browser or with a build tool.
When loading documents from external URLs, CORS (Cross-Origin Resource Sharing) restrictions may prevent direct loading. To handle this, the library includes an automatic blob/memory fallback feature.
When direct URL loading fails (typically due to CORS errors), the library automatically:
- Downloads the file to browser memory using
fetch() - Creates a blob URL from the downloaded data
- Uses the blob URL to render the document
This feature is enabled by default for PDFs, images, and text files. It works automatically without any configuration.
// Default behavior - blob fallback is automatic on CORS errors
const viewer = new DocumentViewer({
container: '#viewer',
url: 'https://example.com/document.pdf'
});
// Explicitly enable blob fallback (force download to memory)
const viewer2 = new DocumentViewer({
container: '#viewer',
url: 'https://example.com/document.pdf',
useBlobFallback: true // Always use blob fallback
});
// Disable blob fallback (fail if direct loading doesn't work)
const viewer3 = new DocumentViewer({
container: '#viewer',
url: 'https://example.com/document.pdf',
useBlobFallback: false // Never use blob fallback
});Open test-blob-fallback.html in your browser to test the blob fallback functionality:
python3 -m http.server 8000
# Open http://localhost:8000/test-blob-fallback.htmlThe test page includes buttons to:
- Load documents normally (with automatic fallback)
- Force blob fallback mode
- View detailed console logs of the fallback process
- Office Documents: Blob fallback does NOT work for Office documents (Excel, Word, PowerPoint) because they use third-party viewers (Google Docs Viewer or Microsoft Office Online) which require the URL to be publicly accessible from their servers.
- Memory Usage: When using blob fallback, the entire document is loaded into browser memory. For large files, this may consume significant memory.
- Browser Support: Blob fallback uses standard browser APIs (
fetch,URL.createObjectURL) and works in all modern browsers.
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
For issues and questions, please open an issue on the GitHub repository.