EmbedPDF

Setup

One package gives you the whole viewer: the toolbar and panels, the PDF engine, and everything the engine needs, served from your own site.

install.html
<script type="module" src="https://cdn.jsdelivr.net/npm/@embedpdf/viewer@3/dist/embedpdf.js"></script>

On a plain HTML page, load the script from jsDelivr as above. In an app with a bundler, install @embedpdf/viewer and import '@embedpdf/viewer' once, which defines the <embedpdf-viewer> element.

Give it a size#

The viewer fills the box you give it and scrolls the pages inside it, so the box needs a height. Any height works: a fixed one, 100vh, or the remaining space of a flex or grid layout (flex: 1; min-height: 0). Its width follows the box too, and the viewer adapts to it, not to the window: see Small screens.

Pass it config#

Everything the viewer can do is one config object. In a framework, every key is a prop:

config.html
<div id="viewer" style="height: 100vh"></div>

<script type="module">
  import EmbedPDF from 'https://cdn.jsdelivr.net/npm/@embedpdf/viewer@3/dist/embedpdf.js';

  EmbedPDF.init({
    target: '#viewer',
    src: '/report.pdf',
    theme: 'system',
    disable: ['forms'],
    onReady: (viewer) => console.log('ready', viewer),
  });
</script>
KeyWhat it isPage
srcOne document: a URL, a File, or bytesDocuments
documentsSeveral documents, shown as tabsDocuments
layoutWhere every toolbar, button, menu and panel goesLayout
commandsYour own actionsButtons & commands
iconsYour own iconsButtons & commands
theme'light', 'dark', 'system', or your colorsTheming
locale'auto' or a language codeTranslations
stringsYour own words, and other languagesTranslations
disableFeatures, or single buttons, to leave outTurn features off
readOnlyEvery feature that changes the document offTurn features off
scopeWhat the user may do, checked by the engineTurn features off
identityWho the user is: the author of their annotationsAll features
notAllowed'hide' (default) or 'disable' what the scope doesn’t allowTurn features off
pluginsThe settings of each feature’s pluginPlugins
engineWhere the engine’s files come from, or another engineSelf-hosting
onReadyCalled with the viewer once it’s liveThe viewer in code
onDocumentChangeCalled with the document that’s showing: the first one, each one the user switches to, and null after the last one closesDocuments
onPanelResizeCalled with a panel’s id and width when the user resizes itPanels

The config is plain data and a few functions, the same in every framework. Keep it in a file of its own when it grows, and spread it into the viewer, as in the Quick start.

TypeScript#

Every key is typed, including the ids of the built-in buttons and panels, so your editor completes them. A config in a file of its own gets its types from satisfies:

import type { ViewerConfig } from '@embedpdf/viewer';
 
export const config = {
  theme: { accent: '#7c3aed' },
  layout: (layout) => layout.place('main', 'bottom'),
} satisfies ViewerConfig;

Every framework package exports the same ViewerConfig type.

Server-side rendering#

The viewer runs in the browser only, and every framework package is safe to import on the server. The server renders an empty box of the size you gave it, and the viewer starts when the page loads in the browser, so there’s nothing to wrap:

ssr.html
<!-- A static page needs nothing: the element upgrades when the script loads. -->
<embedpdf-viewer src="/report.pdf" style="height: 100vh"></embedpdf-viewer>
<script type="module" src="https://cdn.jsdelivr.net/npm/@embedpdf/viewer@3/dist/embedpdf.js"></script>

Next.js, Nuxt, SvelteKit, Astro and Angular SSR all work this way. With Angular, @defer (on viewport) also keeps the viewer’s code out of the first download until it scrolls into view.

Change the config later#

Five keys follow their prop while the viewer runs: src opens the new document in place of the old one; theme and locale switch the look and the language; disable and readOnly take tools away or bring them back. Any plugin’s settings change through the viewer (see The viewer in code). Everything else is read once, when the viewer starts. To change it, mount a new viewer:

remount.html
<script type="module">
  const element = document.querySelector('embedpdf-viewer');

  // A key that's read once builds a new viewer, with the documents opened again.
  element.config = {
    ...element.config,
    layout: (layout) => layout.place('main', 'bottom'),
  };
</script>

A new viewer opens its documents again.

Several viewers on a page#

Each viewer has its own documents, layout, settings and state. Keyboard shortcuts work in the viewer that has focus, so two viewers never both zoom on one key press.

Good to know#

What it needs from the browser#

Chrome and Edge 114, Firefox 125 and Safari 17 (iOS 17), or newer: the first versions with the popover API, which the viewer’s menus and dialogs open in.

The engine runs in a Web Worker, as WebAssembly.

A strict Content Security Policy#

The viewer runs under a strict CSP with no 'unsafe-inline'. Security & CSP has the policy to copy.

Air-gapped and self-hosted#

Nothing is fetched from a third party at runtime unless you point the viewer there. See Self-hosting for every file and the setting that controls it.

The viewer is built on the same packages as headless. Everything you set up here still applies if you build your own interface later.

Was this page helpful?

Your feedback goes directly to the documentation team.