EmbedPDF
Svelte PDF Viewer

A Svelte PDF viewer written for Svelte 5

One tag and your users can read, search, mark up, fill in and sign PDFs. Want your own design? The headless parts speak runes and snippets, just like the rest of your Svelte 5 code. Free and open source.

4K+stars1M+downloadsApache-2.0 licensed
The EmbedPDF viewer running in a Svelte app
+page.svelte
<script lang="ts">
  import { PDFViewer } from '@embedpdf/viewer-svelte';
</script>

<PDFViewer
  src="/report.pdf"
  style="height: 100vh"
/>
Svelte 5 native
  • Runes and snippets
  • SvelteKit SSR safe
  • Callback props, typed
Svelte-first

It feels at home in your Svelte code

Everything you touch from Svelte follows the patterns Svelte 5 introduced: runes, snippets and callback props.

State, the runes way

useSearchState() gives you one reactive object. Read state.hitCount in your markup and it updates by itself. No stores to subscribe to.

Snippets for your parts

Pass a {#snippet fallback()} to show while a file opens. Whatever you put inside <Stage> is drawn on every page.

Events are props

Listen with plain callback props like onready, the Svelte 5 way. No event dispatchers, and every callback is typed.

Only what you import

Every feature is its own import path. Search, forms or redaction you don’t use never reach your bundle.

Fine in SvelteKit

The ready-made viewer goes straight into +page.svelte. No {#if browser} checks and no onMount tricks.

Private by design

The PDF engine runs in a Web Worker in the browser. Your users’ files are never uploaded anywhere.
SvelteKit in 3 steps

Add it to a SvelteKit app

The same three steps work in a plain Svelte 5 + Vite project.

  1. 1

    Install the viewer

    @embedpdf/viewer-svelte brings the whole viewer: toolbar, panels and the PDF engine. It needs Svelte 5.

    Terminal
    npm install @embedpdf/viewer-svelte@next
  2. 2

    Drop it into a route

    Use it in any +page.svelte. It is safe to render on the server, so no guards are needed. Give it a height and it fills the box.

    src/routes/report/+page.svelte
    <!-- src/routes/report/+page.svelte in SvelteKit: no {#if browser} needed. -->
    <script lang="ts">
      import { PDFViewer } from '@embedpdf/viewer-svelte';
    </script>
    
    <PDFViewer src="/report.pdf" style="height: 100vh" />
  3. 3

    Take control with onready

    onready hands you the viewer once it is live. Every feature is a plugin you reach by name. This one searches each document for “total” when it opens.

    +page.svelte
    <script lang="ts">
      import { PDFViewer, type Viewer } from '@embedpdf/viewer-svelte';
    
      function onready(viewer: Viewer) {
        viewer.documents.onOpened(() => viewer.get('search').search({ text: 'total' }));
      }
    </script>
    
    <PDFViewer src="/report.pdf" {onready} style="height: 100vh" />
Headless

Or build your own viewer with runes

Install @embedpdf/svelte and there is no ready-made UI at all. You get the engine, the page view and every feature as small functions. You write the markup.

This search box is the whole thing: an input, a counter and two buttons. Style it however you like. The matches light up in the pages by themselves.

  • Action objects never change, so they are safe in $effect
  • Works with Tailwind, Skeleton, shadcn-svelte or your own CSS
  • Every value and callback is typed
Search.svelte
<script lang="ts">
  import { RenderLayer } from '@embedpdf/svelte/render';
  import { SearchLayer, useSearch, useSearchState } from '@embedpdf/svelte/search';
  import { Stage } from '@embedpdf/svelte/stage';

  const search = useSearch();
  const state = useSearchState();
</script>

<form onsubmit={(event) => event.preventDefault()}>
  <input oninput={(event) => search.search({ text: event.currentTarget.value })} />
  {#if state.status === 'searching'}
    <span>Searching…</span>
  {/if}
  {#if state.hitCount > 0}
    <span>{state.activeHitIndex + 1} of {state.hitCount}</span>
  {/if}
  <button type="button" onclick={() => search.previousHit()}>↑</button>
  <button type="button" onclick={() => search.nextHit()}>↓</button>
</form>

<Stage>
  <RenderLayer />
  <SearchLayer />
</Stage>
Build or install

What it takes to build this yourself

Many Svelte teams start with PDF.js and a canvas. It works, until the feature requests arrive.

What you needEmbedPDFBuilding it yourself on PDF.js
Show the pagesOne tagpdfjs-dist, a worker setup and a canvas per page
Long documentsOnly the pages on screen are drawnYou add virtual scrolling yourself
SearchBuilt in, with a list of every matchA text layer, a find controller and your own search UI
AnnotationsStandard PDF annotations, saved into the fileYour own editor and your own save logic
RedactionContent removed from the fileNot possible with PDF.js alone
Phones and tabletsPinch to zoom, menus that fit a small screenTouch handling and a mobile layout to design
Languages13 built in, right-to-left includedEvery label to translate

PDF.js is a great PDF renderer. The point is the weeks of UI work on top of it, which EmbedPDF has already done.

FAQ

Svelte PDF viewer, answered

Quick answers for Svelte and SvelteKit developers.

Does it work with Svelte 4?

No. EmbedPDF is written for Svelte 5 (5.29 or later). It uses runes for state, snippets for custom parts and callback props for events. If your app is still on Svelte 4, the official Svelte migration tool can move most code over for you.

Does it work with SvelteKit and server rendering?

Yes. Import <PDFViewer> in a +page.svelte file and use it. The package is safe to import on the server, so you don’t need {#if browser}, onMount or ssr = false. The server renders an empty box of the right size, and the viewer starts in the browser.

I used @embedpdf/svelte-pdf-viewer in version 2. Is there an upgrade path?

Yes. Version 3 calls the ready-made viewer @embedpdf/viewer-svelte. Swap the package, then follow the migration guide in the docs. Note that version 3 needs Svelte 5.

Can I use it without SvelteKit?

Yes. It works in any Svelte 5 app, for example a plain Vite project. The engine’s WebAssembly is part of your build, so there is nothing extra to copy or host.

How do I build my own toolbar?

You have two options. Keep the ready-made viewer and add your own Svelte components to its toolbar with <ToolbarItem>. Or install @embedpdf/svelte, the headless package, and build the whole UI yourself with functions like useStage() and useSearchState().

How is it different from other Svelte PDF components?

Most Svelte PDF components wrap PDF.js and focus on showing pages, sometimes with zoom and a basic search. EmbedPDF is a full document viewer: annotations, comments, stamps, forms, signatures and redaction, saved into the PDF. It runs on PDFium, the engine inside Chrome.

Do my users’ files leave their device?

No. The engine runs in a Web Worker in the browser. PDFs are opened, changed and saved on the device, and nothing is fetched from a third party at runtime.

What does it cost?

Nothing. EmbedPDF is open source under the Apache-2.0 license, free for personal and commercial use.

Ready when you are

Your next Svelte app opens PDFs

Install it, add one tag, and move on to the parts of your app that only you can build.

Terminal
npm install @embedpdf/viewer-svelte@next