Installation and support
Install @kernstra/pdf-web-sdk with npm install @kernstra/pdf-web-sdk. The SDK ships as dual ESM + CJS with TypeScript declarations. It works with any framework (React, Vue, Angular, Svelte, vanilla JS) and any bundler that reads package.json exports (Vite, webpack 5, esbuild, Rollup; the support table has the floors).
Bundle it at ES2022 or later. The package is ES2022; a build that transpiles it to an older target breaks the SDK. Set your bundler's target for this package to es2022 or later — Vite build.target: 'es2022' (some Vite versions default to an older target), esbuild --target=es2022 — and leave it out of Babel or SWC down-leveling of node_modules.
Example
script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; where render.worker.js is served, when workerUrl is set connect-src 'self' https://<assets store> https://<licensing api>; img-src 'self' data:; stamps, signatures, thumbnails and print pages are data: images style-src 'self' 'nonce-<nonce>'; or 'unsafe-inline'
Browser and toolchain support
| Supported | |
|---|---|
| Browsers | The last two major versions of Chrome, Edge, Firefox and Safari, and Firefox ESR. At the least Chrome and Edge 111, Firefox 114 (ESR 128), Safari 16.4: the viewer's stylesheet uses color-mix() and dynamic viewport units, which older versions draw wrong. On a phone or tablet, Safari on iOS and iPadOS 16.4 or newer and Chrome on Android — they update themselves, so the latest stable release is what is tested. An older browser is not refused; nothing is promised for it |
| Page | A secure context (HTTPS, or localhost); WebAssembly; Web Workers of the module kind when workerUrl is set. No cross-origin isolation (COOP/COEP) is needed |
| Bundler | One that reads package.json exports and emits ES2022 or later: Vite 5+, webpack 5, esbuild, Rollup 3+, Parcel 2, Next.js 13+, Nuxt 3+, Angular 16+. Webpack 4 cannot parse the package; a build that transpiles node_modules to an older target breaks the SDK (above). None is needed: see Without a bundler below |
| TypeScript | 5.0 or later, any moduleResolution. The declarations are served for import and require alike |
| Server-side rendering | The package may be imported on a server: nothing touches window or document until Kernstra.init() or a viewer is constructed, which belongs in browser-only code (useEffect, onMounted, afterNextRender); called on a server, Kernstra.init() rejects with INVALID_STATE |
| Shadow DOM | Not supported: the SDK's stylesheets go into document.head, and its dialogs and popovers into document.body |
| Node | 20 or later, to install |
| Size | index.js is about 1.7 MB gzipped as published; your bundler's minifier brings it to about 1.15 MB gzipped. The engine (about 4.4 MB) is downloaded once per browser from the asset store, not bundled |
Notes
Loading it lazily. Nothing needs the SDK before a document is shown, so a page that opens PDFs on demand keeps it out of its first load with a dynamic import(): every bundler above splits it into a chunk of its own (esbuild with --splitting), fetched when the import runs. Kernstra.init() downloads and starts the engine, so call it as soon as a PDF becomes likely (a hover over the link, a route that will show one) rather than at the click: the second call gets the same promise back. In code: const { Kernstra, PDFDocument, PDFViewer } = await import('@kernstra/pdf-web-sdk'); then await Kernstra.init({ license: import.meta.env.KERNSTRA_LICENSE });, const doc = await PDFDocument.fromUrl(url); and new PDFViewer(container, doc).
Without a bundler. A page with no build step — plain HTML, a server-rendered site, a CMS template — loads the SDK as an ES module by its URL. dist/index.js is one self-contained module that imports nothing, so no import map is needed, and dist/render.worker.js is the optional render worker. Take both from the same version: copy them from node_modules/@kernstra/pdf-web-sdk/dist/ (or the package tarball) into a folder of your site. In code, inside a <script type="module"> on a page whose <div id="viewer"> has a height: import { Kernstra, PDFDocument, PDFViewer } from '/vendor/kernstra/index.js'; then await Kernstra.init({ license: '<your license key>', workerUrl: '/vendor/kernstra/render.worker.js' }); (the workerUrl is optional), const doc = await PDFDocument.fromUrl('/files/invoice.pdf'); and new PDFViewer(document.getElementById('viewer'), doc);. Everything else — the options, the API, the events — is the same as in a bundled app; the TypeScript declarations (dist/index.d.ts) are optional and only serve your editor.
The render worker is served by your own site. A browser starts a worker only from the page's own origin, so a workerUrl on another origin is refused: the SDK then logs a RENDER_FAILED warning and renders on the main thread, as it does without workerUrl. Keep render.worker.js of the same version next to the SDK in your folder.
Serve the page over HTTPS (or from localhost while developing), as every page that runs the SDK must be; a page opened from disk (file://) is not supported.
The license is checked against the page's origin, as in a bundled app.
A content security policy needs nothing more than the Content security policy notes below list: script-src 'self' covers the module and worker-src 'self' the worker.
Content security policy. The SDK makes requests only to the places its license names, and draws its own interface with stylesheets it adds to the page. The policy in the example above covers it; the points below say why each line is there.
'wasm-unsafe-eval' lets the browser compile the engine; it is narrower than 'unsafe-eval' and every supported browser has it.
The assets store and the licensing API are the two origins the license token names; the first serves the engine, the shaping and subsetting binaries and the bundled fonts, the second answers the status check on each load. An offline license names neither, and a self-hosted deployment replaces the store with your own origin — see Engine, binaries and fonts.
With tools.sign.webFonts: true add https://fonts.googleapis.com to style-src and https://fonts.gstatic.com to font-src. Nothing else is loaded from a third party.
A style-src without 'unsafe-inline' refuses every <style> the SDK adds unless it carries the page's nonce. Pass that nonce once, as styleNonce in Kernstra.init(), and every sheet the SDK injects — the print frame's included — carries it: await Kernstra.init({ license, styleNonce: nonceFromYourServer });. When styleNonce is not given, the SDK reads the nonce a page declares for its tooling as <meta property="csp-nonce" nonce="…">, the convention Vite and others follow. With a nonce in force the Google Fonts link carries it too, so style-src needs no Google origin. The SDK styles its elements through the CSSOM, never with style="…" markup, so style-src-attr needs nothing. A value that is not base64 or base64url is refused with INVALID_ARGUMENT.
A policy that enforces Trusted Types (require-trusted-types-for 'script') is not supported in 1.0.
One thing comes from your own build rather than the license: the render worker, whose policy is the one its own response carries — serve render.worker.js with a Content-Security-Policy that has script-src 'self' 'wasm-unsafe-eval', since the SDK's engine is compiled inside the worker too. Anything you point the SDK at yourself — a document URL, customFonts URLs, an uploadTo() destination, a stamp image url — needs its origin in connect-src or img-src as well. See Web worker rendering.