A PDF editor library that ships the annotation workflow, not just a canvas.
Use one Svelte component for multi-page PDF rendering, touch-friendly zoom and panning, pen/highlighter/text/line tools, eraser preview, merged PDF export, and annotation-only persistence.
Feature Set
Built for production review flows.
The public wrapper is meant for app developers; the copied core keeps the richer UI and rendering behavior behind it.
Natural annotation tools
Pen, highlighter, text, line, pointer, selection, and eraser tools are available as built-in plugins.
Touch-first navigation
Hand panning, double-tap zoom, minimap controls, and responsive toolbar placement are built in.
Multi-page rendering
The wrapper keeps per-page annotations synchronized while the core renders adjacent page previews.
Merged PDF export
Users can export a completed PDF with annotations merged into the original document.
Annotation-only saves
Persist just the annotation JSON when your application wants to store edits separately from the PDF.
Plugin-controlled tools
Pass a plugin list to enable built-in tools, disable tools, or register EmbedPDF extensions.
Quick Start
Use the library wrapper first.
Import `PdfEditor` from the package, give it a PDF source, and store `pageAnnotations` whenever `dataUpdated` fires.
import { PdfEditor } from 'sveltekit-pdf-editor';
let pdfBlob: Blob | null = null;
let pageAnnotations = [];
let fileName = 'marked-document.pdf';
function handleDataUpdated(event) {
pageAnnotations = event.detail.newData;
localStorage.setItem('pdf-annotations', JSON.stringify(pageAnnotations));
}
<PdfEditor
{pdfBlob}
bind:pageAnnotations
{fileName}
ownerId="teacher-1"
on:dataUpdated={handleDataUpdated}
/>Annotation data model
`pageAnnotations` stores all annotation data without rewriting the PDF. Persist this payload when you want users to resume editing later.
type PageAnnotations = Annotation[][];
// pageAnnotations[0] contains annotations for page 1
// pageAnnotations[1] contains annotations for page 2
const pageAnnotations = [
[
{
id: 'annotation-1',
owner: 'teacher-1',
type: 'drawing',
path: 'M 12 40 L 120 40',
x: 0,
y: 0,
originWidth: 595,
originHeight: 842,
width: 595,
scale: 1,
brushSize: 3,
brushColor: '#111827'
}
]
];Save callbacks
Use callback props when you prefer direct functions over DOM events. Each save callback receives the all-page annotation array.
async function saveAnnotations(annotations) {
await fetch('/api/pdf-annotations', {
method: 'POST',
body: JSON.stringify({ annotations })
});
}
<PdfEditor
{pdfBlob}
{pageAnnotations}
handleSave={saveAnnotations}
onSaveAnnotations={saveAnnotations}
handleComplete={saveAnnotations}
/>API Reference
Props accepted by `PdfEditor`.
`PdfEditorCore` is exported for advanced integrations, but most apps should use `PdfEditor` so multi-page annotation sync, events, and legacy prop compatibility are handled for you.
| Prop | Type | Default | What it does |
|---|---|---|---|
| pdfBlob | Blob | File | ArrayBuffer | Uint8Array | null | null | The PDF source. When null, the component renders its slot instead of the editor. |
| pageAnnotations | any[][] | [] | Annotation data grouped by page. Page 1 is index 0. Use this to restore or persist edits. |
| fileName | string | '' | Name used when creating File input and exporting a completed PDF. |
| ownerId | string | 'user1' | Default annotation owner id. Used to identify which annotations the current user can edit. |
| user | string | undefined | undefined | Overrides ownerId when you want a different active user id. |
| allowPrinting | boolean | true | Allows the editor UI to expose print/export actions when supported by the core. |
| disabled | boolean | false | Disables editing interactions for the whole document. |
| disabledPages | Array<{ from_page; to_page }> | [] | Legacy prop for page ranges where editing should be disabled. |
| disabled_pages | Array<{ from_page; to_page }> | undefined | Core-style page range prop. Takes priority over disabledPages when provided. |
| currentPage | number | 1 | Initial active page. The wrapper also emits pageChange when users navigate. |
| savingState | 'saving' | 'saved' | 'fail' | 'saved' | Legacy save state for showing saving or failure state in the toolbar. |
| saveState | SaveState | undefined | undefined | Preferred save state object. When provided, it overrides savingState. |
| autoSaveEnabled | boolean | undefined | undefined | Optional controlled auto-save toggle. Pass false for demos that should only save on explicit user action. |
| plugins | PdfEditorPlugin[] | defaultPdfEditorPlugins | Controls which built-in tools are enabled and which EmbedPDF registrations are installed. |
| allowTeacherMark | boolean | false | Enables the teacher mark/stamp workflow. |
| teacherMarkName | string | 'User' | Display name written into teacher mark metadata. |
| homework_info | any | undefined | Optional metadata passed to the homework info modal. |
| isPageLoading | boolean | false | Lets host apps tell the core to defer page-camera restore while external loading is active. |
| wasmUrl | string | '/vendor/embedpdf/pdfium.wasm?v=2.14.4' | Location of the EmbedPDF PDFium wasm file. Keep the wasm version aligned with @embedpdf packages. |
| handleSave | (annotations) => void | Promise<void> | undefined | Called when the editor save action runs. Receives all page annotations. |
| handleComplete | (annotations) => void | Promise<void> | undefined | Called after save when the user completes the editing session. |
| onSaveAnnotations | (annotations) => void | Promise<void> | undefined | Callback prop for persisting annotation-only JSON during save. |
| onAnnotationChange | (annotations) => void | undefined | Callback prop fired whenever annotations change. Mirrors annotationsChange event data. |
| retryFailedSave | () => void | Promise<void> | undefined | Called from the toolbar retry action when a save failed. |
Events
Subscribe with Svelte events or callback props.
| Event | Detail | When it fires |
|---|---|---|
| on:dataUpdated | { newData, annotations, currentPage } | Fires whenever annotation data changes. Use detail.newData as the canonical all-page annotation payload. |
| on:annotationsChange | { annotations, currentPage } | A focused annotation-change event with the same all-page annotation payload. |
| on:save | { annotations, currentPage } | Fires after handleSave/onSaveAnnotations complete successfully. |
| on:done | { newData, annotations, currentPage } | Fires after completion save and handleComplete finish. |
| on:pageChange | { page, annotations } | Fires when the active page changes. annotations contains the destination page annotations. |
Plugin System
Install tools by passing plugins.
The current plugin system controls the editor capabilities at runtime. A plugin can enable a built-in tool, be disabled with `enabled: false`, or attach an EmbedPDF registration that the core forwards into `<EmbedPDF />`.
import {
PdfEditor,
defaultPdfEditorPlugins,
createPdfEditorPlugin,
penPlugin,
textPlugin,
linePlugin
} from 'sveltekit-pdf-editor';
const reviewOnlyPlugins = [penPlugin, textPlugin, linePlugin];
const customEmbedPdfPlugin = createPdfEditorPlugin({
id: 'my-embedpdf-extension',
label: 'My EmbedPDF extension',
embedPdfRegistration: myEmbedPdfRegistration
});
const plugins = [
...defaultPdfEditorPlugins.filter((plugin) => plugin.tool !== 'pointer'),
customEmbedPdfPlugin
];
<PdfEditor {pdfBlob} {pageAnnotations} {plugins} />Required stable plugin id.
Optional built-in tool id. If omitted, the plugin can still carry an EmbedPDF registration.
Set to false to keep a plugin object in config while disabling it.
Human readable label for your own plugin management UI.
Reserved for custom UI plugins as the extension surface grows.
Optional registration object appended to the EmbedPDF plugin list.
Ready to test
Open the demo and draw on a real PDF.
The demo route uses the same exported `PdfEditor` wrapper documented above, so it is the best place to verify props, saves, and plugin combinations.