Kernstra
The SDK entry point: all members are static. Kernstra.init() verifies the license and loads the engine binary; call it once, before creating any viewer, and await it before PDFDocument.fromUrl(), fromFile(), fromBytes() or fromUrlProgressive() — all four require the engine to be loaded.
A valid license, development or production, is required: without one init() shows a "License required" popup, rejects with LICENSE_REQUIRED and never downloads the engine. Pass the token to init, typically from your build's own variable, or set it where the SDK looks when none is passed: a globalThis.KERNSTRA_LICENSE string, a <meta name="kernstra-license" content="…"> tag, or, in Node, process.env.KERNSTRA_LICENSE. The SDK ships prebuilt, so a bundler cannot substitute an environment variable into it; read the variable in your code. The license chapter (Licensing) covers origins, the secure-context rule, the status check, outages and which feature each tool and API member needs.
Usage
import { Kernstra, PDFDocument } from '@kernstra/pdf-web-sdk'; // Application startup (once) await Kernstra.init({ license: import.meta.env.KERNSTRA_LICENSE }); // Then load documents as needed const doc = await PDFDocument.fromUrl('/my-file.pdf');
Lifecycle
Initialize the SDK — verifies the license and loads the engine binary. Call before creating any viewer; resolves when the engine is ready.
Idempotent: a second call with the same options gets the same promise (React's StrictMode needs no guard), one with other options rejects with INVALID_ARGUMENT, and a failed call starts over on the next. An empty or missing license means "no license given"; only setLicense('') signs out and forgets the cached token.
The token is verified locally in milliseconds; its status is confirmed with the licensing API once per load. A load that holds a recent good answer never waits; a load without one (the first on a browser, site data cleared, a frame without storage, an answer older than 48 hours, or a paused license) waits for the check, at most 8 seconds. Nothing runs that we did not publish: the engine, the shaping and subsetting binaries and every bundled font are downloaded from the asset store your license names, by digest, verified and cached in the browser.
| Parameter | Type | Description |
|---|---|---|
| options? | SDKInitOptions | license (your license token), engineUrl, shaperUrl and subsetterUrl (self-hosted copies of the binaries — self-hosting licenses only), workerUrl (the render worker), styleNonce (the page's CSP nonce, carried by every stylesheet the SDK injects) and licensePopup (the "License required" popup in your own words). See Initialization. |
Promise<void> Resolves when the engine is ready.LICENSE_REQUIRED— no valid license, or not a secure context (the SDK runs only over HTTPS or onlocalhost; on a plain-HTTP origin it refuses to start with reasoninsecure-context).FEATURE_NOT_LICENSED—engineUrl,shaperUrlorsubsetterUrlgiven without a self-hosting license.ENGINE_LOAD_FAILED— the binary was not downloaded, not verified, or not started.INVALID_ARGUMENT— a second call with other options, astyleNoncethat is not base64 or base64url, or alicensePopuppart that is not a string.INVALID_STATE— called outside a browser, where there is nowindowordocument: server rendering.
await Kernstra.init({ license: import.meta.env.KERNSTRA_LICENSE });
true if the SDK has been initialized.boolean if (!Kernstra.isInitialized()) { await Kernstra.init({ license: import.meta.env.KERNSTRA_LICENSE }); }
Tear down the SDK and free its resources. Destroy your viewers and close your documents first (each viewer terminates its render worker): with a document still open it throws INVALID_STATE rather than free them from under it.
After an ENGINE_FAILED fault it tears down whatever is open — that is the recovery — and a document closed after it is already released, so its close() does nothing. destroy() removes the onLicenseChange subscriptions, like every other SDK listener; Kernstra.events subscriptions are kept across it.
void INVALID_STATE— a document is still open.
viewer.destroy(); await doc.close(); Kernstra.destroy();
string console.log('Kernstra Web SDK', Kernstra.version);
License
Replaces the license at runtime; resolves to the new license state. A setLicense is a load: the licensing API confirms the token's status, and a license that does not load (revoked, paused, expired, a copied token, a host it does not name, a malformed or forged key) shows the "license required" popup — signing out with '' stops nothing that is open. Presenting the same key again changes neither a pause nor a revocation.
A verdict that adds rights reaches open documents at once; a viewer whose document's rights grow rebuilds its toolbar. A verdict that takes rights away reaches only the next load: each document keeps the rights it was opened with until it is closed.
| Parameter | Type | Description |
|---|---|---|
| token | string | The new license token. '' signs out: it forgets the cached token, but not a stop — only a license that loads lifts a pause or a revocation. |
Promise<LicenseState> Resolves to the new license state.const state = await Kernstra.setLicense(newToken); if (state.valid && state.features.includes('export')) enableDownload();
The current license state, as the exported LicenseState. if (state.valid) state.features.includes('export') type-checks: the valid half has every field, the invalid half the reason.
An invalid state's reason is one of no-license, insecure-context (plain HTTP), malformed, unsupported-algorithm, invalid-signature, not-yet-valid, expired, origin-mismatch, wrong-holder, revoked, paused or stale (the licensing API has not confirmed the license for more than 48 hours, or never on this browser and the check could not get through; the SDK loads, watermarked and without extract, and asks again whenever a document is opened). A paused state carries pauseReason: payment, suspended, term_ended, trial_ended or other. During a licensing-API outage getLicense() says why output is watermarked. It reports a verdict as soon as it arrives; documents already open keep the rights they were opened with.
LicenseState A union narrowed on valid: the valid half carries watermark, env, plan, features, offline and, on a license with an expiry date, expiresAt; the invalid half carries watermark: true, a reason and, when the reason is paused, a pauseReason.const state = Kernstra.getLicense(); if (!state.valid) console.warn('License not valid:', state.reason); else if (state.watermark) console.warn('Output is watermarked (development token)');
Calls callback with each new license state; returns a function that unsubscribes. The same changes arrive as the licenseChanged event on Kernstra.events. A verdict that takes rights away — revoked, paused, fewer features, the outage grace period run out — is reported at once here but reaches only the next load (init, setLicense, or opening a document): whoever is exporting, printing or filling a form in an open document finishes. Listen here if your app should react sooner.
Kernstra.destroy() removes these subscriptions; one made before an init() refused for want of a license stays, so it sees the retry.
| Parameter | Type | Description |
|---|---|---|
| callback | (state: LicenseState) => void | Called with each new license state. |
() => void A function that unsubscribes.const stop = Kernstra.onLicenseChange((state) => { if (!state.valid) showBanner('License problem: ' + state.reason); }); // later stop();
Events and assets
GlobalEvents — on() and off() for GlobalEventMap; see the Event System. Only the SDK emits on it. Kept across destroy(): subscribe once, at module level if you like.GlobalEvents on() and off() for GlobalEventMap.Kernstra.events.on('licenseChanged', (state) => { console.log('license', state.valid ? state.plan : state.reason); });
{ engine, shaper, subset, fonts }, fonts keyed by font id (BUILTIN_FONTS[i].id), for scripting a self-hosted deployment; the subset digest's folder in the store is subsetter. See Engine, binaries and fonts.{ engine, shaper, subset, fonts } const { engine, shaper, subset, fonts } = Kernstra.assetDigests; console.log(engine, Object.keys(fonts).length, 'fonts');