Documents
Give the viewer a document with src: a URL, a File, or bytes.
{ src: '/reports/2026-q3.pdf' }A relative URL is relative to your page. A URL on another site needs CORS headers that allow
yours, like any fetch.
Open the file someone picks#
src follows its prop: give it another document, and the viewer opens that one in its place.
import { useState } from 'react';
import { PDFViewer } from '@embedpdf/viewer-react';
export function Upload() {
const [file, setFile] = useState<File | null>(null);
return (
<>
<input type="file" accept="application/pdf" onChange={(e) => setFile(e.target.files?.[0] ?? null)} />
<PDFViewer src={file} style={{ height: '80vh' }} />
</>
);
}People can also open files themselves: drop a PDF on the viewer, or use Open in the document menu. Both open it as a new tab.
Several documents#
Give documents instead of src to open several at once. Each one gets a tab:
{
documents: [
{ src: '/contract.pdf', name: 'Contract' },
{ src: '/appendix.pdf', name: 'Appendix A' },
{ src: '/scan.pdf', name: 'Signed scan', active: true },
],
}| Field | What it is | Default |
|---|---|---|
src | A URL, a File, or bytes | none |
name | What its tab shows | the file’s name |
password | Its password, if it has one | none |
active | Show this one first | the first document |
The tabs show while more than one document is open. To show them always or never, set
layout.tabs('always') or layout.tabs('never') in your layout.
onDocumentChange tells you which document is showing each time that changes, and null once none is.
Passwords#
A document with a password opens locked, and the viewer asks for it. When you know it, pass it along and nobody is asked:
{ documents: [{ src: '/payroll.pdf', password: secret }] }A wrong password asks again. A document that can’t be opened at all shows why in its tab, with a Try again button.
Open and close documents from code#
viewer.documents opens, closes and switches documents. It’s the documents plugin, the same as
in headless:
const { document } = await viewer.documents.open(
{ kind: 'url', url: '/invoice-1042.pdf' },
{ name: 'Invoice 1042' },
);
viewer.documents.setActive(document.id);
viewer.documents.close(document.id);open() resolves once the document is ready, and makes it the active one.
Download and print#
The document menu has Download and Print, and Ctrl/Cmd+S and Ctrl/Cmd+P work
while the viewer has focus. Both include every annotation, form value and signature. When the
scope doesn’t allow printing, or you turn it off, Print isn’t there, and
Ctrl/Cmd+P says so instead of printing the web page.
To run them from your own button, use the commands document:download and document:print, or
call them from code:
viewer.commands.execute('document:print');Save to your server#
viewer.documents.download() gives you the PDF with every change, as bytes, without a download
dialog. Make it a button of your own:
{
commands: [
{
id: 'acme:save',
label: 'Save',
icon: 'save',
shortcut: 'Mod+S',
run: async ({ viewer, documentId }) => {
const bytes = await viewer.documents.download(documentId);
await fetch(`/api/documents/${documentId}`, { method: 'PUT', body: bytes });
},
},
],
layout: (layout) => layout.replace('document:download', 'acme:save'),
}Mod+S now saves to your server instead of downloading, because a command of yours wins over a
built-in one with the same shortcut.
Warn before changes are lost#
When someone closes a tab or leaves the page with unsaved changes, the viewer asks first. Unsaved
means changed since it opened or since the last download. viewer.documents.get().hasUnsavedChanges
tells you the same, and onUnsavedChangesChanged fires when it changes:
viewer.documents.onUnsavedChangesChanged(({ documentId, hasUnsavedChanges }) => {
saveButton.disabled = !hasUnsavedChanges;
});Good to know#
Big files#
The local engine needs the whole file before the first page shows, so a large scan takes as long as its download. The toolbar is there at once; only the pages wait.
Where the bytes go#
With the local engine, a document never leaves the browser: no upload, no server. Each document runs in the engine’s worker, so a heavy page doesn’t freeze your app.
Every documents method#
viewer.documents is the headless documents plugin. Its methods, state and events are listed on
Opening documents, Several
documents and Saving.
Your feedback goes directly to the documentation team.