Migration Guide
This guide covers breaking changes, deprecated APIs, and upgrade paths between VerifyKit SDK versions.
Deprecated APIs
The following APIs in @trexolab/verifykit-core are retained for backward compatibility but should no longer be used in new code.
computeUnsignedFields(signatures, detectedFields?)
Status: Stub — always returns an empty array.
The WASM engine does not expose unsigned field detection through this function. Unsigned signature fields are now reported directly in the VerificationResult when present.
Migration: Remove calls to computeUnsignedFields. If you need to detect unsigned form fields, inspect the VerificationResult returned by verifyPdf() or verifier.verify().
getTrustStore()
Status: Stub — returns an object with empty arrays.
Trust store contents are managed internally by the WASM engine and are no longer exposed to JavaScript at runtime.
Migration: Remove calls to getTrustStore(). To configure the trust store, use setTrustStoreConfig() or pass a trustStore config to createVerifier().
ensureCryptoEngine()
Status: No-op.
The Rust/WASM engine ships its own cryptographic implementation and does not depend on the Web Crypto API. This function existed for the legacy JavaScript engine and now does nothing.
Migration: Remove calls to ensureCryptoEngine(). No replacement is needed.
Version History
v0.11.0 to v0.12.0
Release: the engine ships as a .wasm file instead of a base64 string
Drop-in for anyone using a bundler. Vite, webpack, Next.js, Rollup and Parcel
resolve the .wasm from the reference the engine makes and emit it as a hashed
asset with no configuration — the same "nothing to do" as before, arrived at
differently. Node, Deno and Bun read it from the installed package. Nothing in
the API changed and no call sites move.
Between v0.3.1 and v0.11.0 the binary was base64-encoded into the JavaScript. A string cannot be split out of the chunk that holds it, so every route importing the package carried the whole 1.5 MB engine whether or not it verified anything, and the bytes had to be decoded before compilation could start. As a file it is 1.1 MB rather than 1.5 MB, compiles while it downloads, and is cached separately from the JS.
What to check:
- Your server serves
.wasmasapplication/wasm. Most do. If yours does not, verification still works but the browser cannot stream-compile, and logs a warning naming the content type it received instead. - Anything copying build output by extension. A deploy step that copies
*.jsand*.cssout of a build directory will now leave the engine behind. - CSP. Unchanged in practice —
wasm-unsafe-evalwas already required — but the binary is now fetched from your own origin rather than read from a string, so a restrictiveconnect-srcneeds to allow it.
If you serve the vanilla build from a CDN, dist/ now contains
verifykit_core_wasm_bg.wasm and it must be uploaded alongside
verifykit.umd.js. Kept in the same directory it is found automatically;
otherwise pass wasmUrl to VerifyKit.create(). In exchange verifykit.umd.js
drops from 2.9 MB to 1.3 MB, and the engine is fetched only when a document is
verified.
Packaging changes in the same release. @trexolab/verifykit-core gained a
node export condition, so bundlers that read neither exports nor browser
and fall back to main now get the Node build; any bundler from the last several
years reads one of them. Sourcemaps are no longer published — they were shipping
the SDK's original TypeScript.
v0.10.0 to v0.11.0
Release: the horizontal wheel follows the pages, and 'wrapped' is gone
from ScrollMode
Drop-in unless you name 'wrapped'. Every other scroll mode, and every
other API, is unchanged.
What changes for you:
-
ScrollModeno longer includes'wrapped'. The union is now'vertical' | 'horizontal' | 'page'. If you callsetScrollMode('wrapped'), passinitialState={{ scrollMode: 'wrapped' }}, or hold the value in your own state, TypeScript will now reject it — switch to'vertical', which is what wrapped rendered as in every case a reader was likely to see.It went because it could not be told apart from vertical. Wrapped flowed pages left-to-right and broke onto a new row when the column ran out, so the number of pages per row was decided by the zoom — and at the fit-width zoom the viewer opens on, a page is by definition as wide as its column, so exactly one fits. You had to zoom out to roughly 60% before a second page joined the row. A mode that is invisible until you go looking for it is not a mode, and the thumbnail sidebar already gives the overview a page grid was there to provide.
Nothing throws if the string reaches the viewer anyway — from persisted user settings, say. An unrecognised scroll mode lays out as vertical, so a stale
'wrapped'degrades rather than breaks, and you can migrate stored values at your own pace. -
In
'horizontal', a plain wheel now scrolls across and Shift+wheel scrolls down. Previously the wheel followed the browser's default, so on a page zoomed taller than the window a plain wheel scrolled down and never advanced through the document. In a layout that reads left-to-right that felt like a broken wheel. The axes are now: plain wheel moves through the pages, Shift+wheel moves within a tall page, Ctrl/Cmd+wheel still zooms.This is a deliberate departure from pdf.js, which does no wheel remapping at all. It applies only in horizontal mode — vertical and single-page keep the platform behaviour untouched, as does every other scroll container in your app. If you had built your own wheel handling on top of horizontal mode, remove it or the two will fight over the same event.
v0.8.0 to v0.9.0
Release: pinch-to-zoom on touch devices, and the legacy PdfViewer is gone
Drop-in unless you import PdfViewer or usePinchZoom. Both were removed;
everything else is unchanged and takes the same arguments.
What changes for you:
-
Two-finger pinch zoom now works on touch devices. This is new behaviour, not a repair:
Viewer/CoreViewernever had any pinch handling. A pinch landed on the scroll container, whosetouch-action: pan-x pan-yalso suppresses the browser's own pinch, so nothing happened at all.zoomPluginnow installs the gesture, which meansdefaultLayoutPluginand the vanilla and Vue packages get it with no change on your side.It honours the plugin's existing
minScale/maxScale, zooms about the midpoint between the fingers, and setsfitModetononethe same way Ctrl+wheel does. One-finger scrolling is untouched — thetouchmovelistener is bound only while two fingers are down, so ordinary scrolling stays on the compositor.If you built your own pinch handling on top of the viewer to work around its absence, remove it: two handlers will fight over the same gesture.
-
PdfViewerandPdfViewerHandlewere removed. The monolithic viewer was superseded by the plugin-basedViewer/CoreViewer, and by 0.8.0 nothing in the SDK, the demo, or the documented quick-start rendered it any more — the vanilla package included. Keeping it meant shipping ~2,100 lines of a second viewer that received no fixes: the pinch code above lived there, on a path nothing executed. See LegacyPdfViewer→Viewer/CoreViewerbelow for the swap; it is a two-line change in most apps. -
usePinchZoomwas removed. It was only ever imported byPdfViewer, and it did not work: it settouch-action: noneafter the second finger was already down, which the browser latches at the start of a gesture and so ignores. There is no replacement hook —zoomPluginhandles the gesture, so nothing is left to wire up. -
SigWidget,Highlight,ScrollMode,SpreadModeandCursorToolare unchanged. They were declared elsewhere and merely re-exported throughPdfViewer; they are still exported from the package root under the same names.
v0.7.3 to v0.8.0
Release: the right-click menu obeys disable, and toolbar menus take clicks
over the signature sheet
Drop-in for most consumers. Nothing was removed from the public API and nothing you call takes different arguments. Two changes are the reason this is a minor rather than a patch, and both are confined to code written against the plugin API.
What changes for you:
-
The context menu no longer offers actions whose plugin is not installed. It used to draw a hardcoded list — Save As…, Print, the zoom entries, Rotate, Find, Fullscreen, Document Properties, Keyboard Shortcuts — and call those actions itself, so
defaultLayoutPlugin({ disable: { download: true } })removed the toolbar button, the overflow-menu entry and Ctrl+S while right-click → Save As… went on writing the full PDF to disk. Entries now come from the plugins that own them, exactly as the overflow menu's have since 0.7.0.If you disabled a plugin to take a feature away, you were not getting what you configured before and you are now; no action needed. If you were relying on a context-menu entry while its plugin was disabled, install the plugin. Copy Selected Text is drawn by the menu itself and is always present — it is the browser's own clipboard and no feature toggle governs it.
toolbar: { download: false }is a separate knob and is unchanged: it hides the button and nothing else, because it says where a control appears, not whether the feature exists. -
The context menu's order shifts. Both menus now sort by the same
menuOrder/menuGroupa plugin already declares, so an action holds the same relative place on both surfaces. Find in Document moves to the top, Copy Selected Text sits in a group of its own, and Save As… comes before Print. Nothing was removed. If you assert on menu order in a test, re-record it. -
ViewerPluginContextgained a requiredgetPlugins()member. It returns every plugin resolved into the viewer, composed sub-plugins included. A plugin that only receives the context ininstall(ctx)is unaffected and needs no change. If you build aViewerPluginContextobject literal by hand — a test double, most likely — TypeScript will now ask for the method:tsconst ctx: ViewerPluginContext = { store, registerShortcut, // … getPlugins: () => [], // ← add this } -
New optional plugin API:
renderContextMenuItems. Additive — existing plugins compile and run unchanged. Implement it if you want your plugin to appear on right-click:tsxrenderContextMenuItems: ({ onClose, store }) => ( <MenuItem label="My Thing" onClick={() => { doIt(store); onClose() }} /> )The props are deliberately smaller than
renderMenuItems': nothing can overflow into the context menu, so there is noisOverflowedto ask.ContextMenuItemRenderProps,ContextMenuItemsRenderandcollectContextMenuItemsare exported for typing and composing it. -
The toolbar now sits above the signature sheet (
z-index: 30inline, against the sheet's 20). This is the fix for menus that drew over the sheet on a narrow viewer and did nothing when tapped: az-indexon the toolbar makes it a stacking context, which pinned every popover inside it — the More menu, the search popover, the zoom and text-size dropdowns — to the toolbar's old level of 10, below the sheet. The toolbar's own box never overlaps the sheet, so nothing moved visually. If you overrodez-indexon.verifykit-toolbaror.verifykit-sig-panel, re-check it: the sheet has to stay below the toolbar or it takes the clicks back. Dialogs (50–100), the right-click menu (50) and tooltips (99999) are still above both.
v0.7.2 to v0.7.3
Release: upgrading the viewer now upgrades the engine with it
Drop-in. No API changed. What changed is how the packages ask npm for each other, and the reason it matters is that the old way could leave you running a version you did not think you were running.
Up to 0.7.2, @trexolab/verifykit-react declared its dependency on
@trexolab/verifykit-core as "*" (as did @trexolab/verifykit-plugin-revocation;
@trexolab/verifykit-vanilla did the same for its dependency on the React
package). Every version satisfies "*", so if your project already had an older
core installed, npm kept it and upgraded only the shell:
├── @trexolab/verifykit-core@0.5.13 ← left untouched
└─┬ @trexolab/verifykit-react@0.7.2
└── @trexolab/verifykit-core@0.5.13 deduped
Nothing failed. The shell imports 18 symbols from core and all 18 exist in 0.5.13, so it loaded and ran, and the About dialog shows the React package's own version — it read 0.7.2. But verification was being done by the 0.5.13 engine, without the core-side fixes released since: DocMDP/certified detection (0.6.3) and the Node ESM entry (0.6.5).
Only fixes that landed in core were lost this way. Anything fixed in the
React package — the DocumentPermissions mapping in 0.6.8, for instance — came
through normally, because that is the package being upgraded.
From 0.7.3 each of those edges is a real range (^0.7.3), so npm fetches a
matching engine instead of reusing whatever it finds.
What to do. Nothing, if you install fresh — a clean install always resolved correctly, so this never affected you. If you are upgrading a project that has been on VerifyKit for a while, check what you actually ended up with:
npm ls @trexolab/verifykit-coreIf the version there is behind your viewer, upgrading to 0.7.3 fixes it. On a release before 0.7.3 you have to force it by hand:
npm install @trexolab/verifykit-core@0.7.3Tarballs published before 0.7.3 still carry "*" — they are unchanged, so
pinning to one keeps the old behaviour.
One thing to know about the range. It is ^, not an exact pin. On a 0.x
version ^ locks the minor, which is where this SDK makes its breaking changes,
and it lets a project already on a compatible core keep the single copy it has.
If you deliberately pin core to an older minor while installing a newer viewer,
npm will now give the viewer its own nested copy — which is what makes the viewer
correct, but it does mean two engines in the tree, each with its own trust store
and algorithm policy. npm ls @trexolab/verifykit-core will show both. If you
configure the trust store, import setTrustStoreConfig from
@trexolab/verifykit-react rather than from core directly; the React package
re-exports it, and that way you are configuring the engine the viewer is using.
v0.7.1 to v0.7.2
Release: the documented plugin API is now the exported one
Drop-in. No runtime behaviour changed and nothing was removed — this release only adds exports. It is worth reading anyway, because if you followed the plugin docs before now, three things you were told will not have compiled.
-
Type the viewer context as
ViewerPluginContext, notPluginContext. The package exports two: the core engine's under the plain name, and the viewer's underViewerPluginContext. A plainimport { PluginContext }resolves to the core one, which has.configand no.store, soctx.storefails to type-check. The docs saidPluginContextthroughout; they now sayViewerPluginContext.ts// Before — resolves to the core context; ctx.store does not exist import type { PluginContext } from '@trexolab/verifykit-react' // After import type { ViewerPluginContext } from '@trexolab/verifykit-react' -
The store has no
setState, andsubscribetakes a key first. Samples taught both. The real API:tsstore.update({ currentPage: 5 }) // not setState store.subscribe('currentPage', (page, prev) => { … }) // key first, then that key's values store.subscribeAll((state, prev) => { … }) // every change, whole snapshotsNote the two listeners differ in more than arity:
subscribehands you the one key's new and old value, whilesubscribeAllhands you the whole state before and after — it does not tell you which key changed. -
ZoomChangeEvent.fitModeis'none' | 'width' | 'page'. It was documented with literals belonging to the scroll and spread unions.'page'and'none'appear in more than one view-mode union and do not mean the same thing in each. -
Four plugin types are now importable:
SidebarTabProps,MenuItemRenderProps,MenuItemsRenderandCollectedMenuItems. They were always part of the API surface —renderMenuItemshas receivedMenuItemRenderPropssince 0.7.0 — but had no exported name, so annotating the parameter failed withTS2724. If you worked around this withanyor a local copy of the shape, you can now import the real type. -
The menu primitives are now importable:
MenuItem,MenuDivider,MenuSectionandMenuContainer. Use them inrenderMenuItemsso your entry matches the built-in ones.tsximport { MenuItem } from '@trexolab/verifykit-react' renderMenuItems({ isOverflowed, onClose }) { if (!isOverflowed('Download')) return null return <MenuItem label="Download" hint="Ctrl+S" onClick={() => { save(); onClose() }} /> } -
If you override
--btn-sizeor--toolbar-height, override the bases instead. Both are derived —calc(var(--btn-base) * var(--ui-scale, 1))— and the base is reassigned by pointer type and viewport height (26/36 default, 40/48 coarse pointer, 24/32 short viewport, 36/42 both). Setting the derived variable wins on desktop and then loses to the media rules on a phone. Set--btn-baseand--toolbar-h-base, or--ui-scaleto move everything at once. Nothing changed in the stylesheet here; the docs were describing it wrongly. -
Node.js users: the revocation plugin export is
revocationPlugin, and direct CRL/OCSP mode is selected by omittingendpoint. A blog post showedcreateRevocationPlugin({ mode: 'direct' }); neither the export nor the option exists.
v0.7.0 to v0.7.1
Release: the page fits the column it renders into, not the viewer
Drop-in. No API changed. One behaviour is broader than it was in 0.7.0: a viewer may now adopt Fit Width at any width, not only on a narrow viewer.
-
Auto fit-width is decided by the document column, not the viewer breakpoint. The space a page has is what is left after the sidebar and the signature panel take theirs, which can be far less than the viewer: at 1024×768 the viewer is 644px —
md, not narrow by any measure — while the column is 336px for a 735px page. 0.7.0 fitted onlyxs/smviewers and so left 419px of that page out of view. The test is now the page against its own column, re-run when the column changes — on resize, or when the signature panel opens.Nothing here overrides a zoom you named. Pass
initialState.scaleorinitialState.fitMode(initialScale/initialFitModeon the legacy<PdfViewer>) and auto-fit does not run at all. Without one, the viewer opens at 120% as before and only fits when the page does not fit; once the reader zooms, the zoom is theirs and auto-fit stays out of it.
v0.6.8 to v0.7.0
Release: every enabled control reachable at every width, disabled plugins actually disabled
Drop-in for most consumers. No API was removed and nothing you call takes different arguments. One behaviour change is deliberate and is the reason this is a minor rather than a patch.
What changes for you:
-
The overflow menu no longer offers actions whose plugin is not installed. It used to call
downloadPdf,print,toggleFullscreenand Document Properties directly out ofactions, independently of which plugins you had installed — so a viewer configured with the Download plugin disabled still handed the user a working Download entry. Menu entries now come from the plugins that own them, so disabling a plugin removes its menu entry too.If you were relying on that, install the plugin. If you disabled Download precisely to prevent saving, you were not getting what you configured before and you are now; no action needed. This is the one change that can remove a control you were counting on.
-
Toolbar buttons are no longer hidden by viewport width. The toolbar measures itself and moves what does not fit into the overflow menu. If you wrote CSS against
.verifykit-toolbar__collapsibleor the:nth-child(n+3) { display: none }rule to control what collapsed, those rules are gone — delete your overrides. Overflowed slots are hidden inline and carry a[data-slot]wrapper you can select instead. -
Width-dependent styling keys off the viewer's box, not the window.
.verifykit-viewercarriesdata-breakpoint="xs|sm|md|lg"(under 400/under 560/under 840/ rest), fed by a ResizeObserver. If you had@media (max-width: …)rules targeting viewer internals, switch them to.verifykit-viewer[data-breakpoint="xs"] …. The upside is that an embedded viewer in a narrow column now adapts on a wide screen, which a media query could never do. Height still uses media queries — the viewer fills it. -
Touch targets grow on any coarse pointer, including phones. Buttons go to 40px and the toolbar to 48px where they previously stayed at 26px/36px below 769px. Chrome is taller on touch devices; if you sized a container around the old toolbar height, re-check it.
-
The signature panel is a bottom sheet on narrow viewers. It opens at 35% of the viewer height instead of a fixed 70%, and the drag grip is visible and usable where it used to be hidden. If you snapshot-test the panel on a narrow viewport, expect a different height and an extra grip element.
-
Narrow viewers default to fit-width. Only when you have not set
fitMode. An explicitfitModeininitialStateis still respected exactly as before. -
New optional plugin API:
renderMenuItems. Additive — existing plugins compile and run unchanged. Implement it if you want your plugin to appear in the overflow menu when its toolbar slot does not fit:tsxrenderMenuItems: ({ isOverflowed, onClose, store }) => isOverflowed('MyThing') ? ( <MenuItem label="My Thing" onClick={() => { doIt(store); onClose() }} /> ) : nullNote
isOverflowedreturns false for a slot that was never registered: if the consumer removed it throughtoolbar.transform, they do not want it in the menu either. A plugin with no toolbar slot at all should skip the check and always contribute. -
ToolbarSlotPropsgained two optional fields (overflowSlots,renderMenuItems) andViewerStoreStategainedbreakpointandviewerHeight. Both are additive. If you construct aViewerStoreStateliteral by hand rather than starting fromINITIAL_STATE, TypeScript will now ask for the two new fields.
v0.6.7 to v0.6.8
Release: the Printing and Copying rows work in both viewers, and
DocumentPermissions is filled in
Drop-in. No API changed. What changes is what the Document Properties dialog
shows, and what a DocumentPermissions value coming out of the viewer contains.
What changes for you:
<PdfViewer>now shows Printing and Copying at all. The two rows were wired to a state value nothing could set, so they were absent from every document. If you were reaching past the dialog to display permissions yourself, you no longer have to.- Unencrypted documents show "Allowed" for both rows instead of showing
neither. A PDF with no
/Encryptdictionary restricts nothing; the dialog used to omit the rows as if the answer were unknown. If you snapshot-test the dialog, expect two more rows on unencrypted files. DocumentPermissionsfrom the viewer now has all ten fields. It carried onlyprintingandcopying, with the restundefinedbehind a cast. Code that readpermissions.modification,.formFilling,.accessibility,.assembly,.annotations,.encryptedor.permissionFlagswas gettingundefinedand, if it treated that as a boolean, reading every one of them as forbidden. Those fields now hold the real values, so such code will start behaving differently — correctly, but differently.encryptionMethodfrom the viewer is a filter name, not an algorithm. pdf.js exposes/Filter("Standard") but not/V, so this reads"Standard","None"for unencrypted documents, or"Unknown". The fuller string —"Standard (AES)","Standard (RC4-128)"— comes from the WASM extractor, viauseVerification().permissions.
v0.6.6 to v0.6.7
Release: CertificateViewer crash fix, real document permissions, lint enforced
Drop-in for JavaScript consumers. One TypeScript signature changed; nothing you call behaves differently unless you were hitting one of the bugs below.
What changes for you:
-
extractSignaturesFromPdf()returnsRawPdfSignature[], notany[]. The runtime value is identical — the shape was always fixed by the Rust side — but it is now described by an exported interface. If you assigned the result to a hand-written type, TypeScript will now check that assignment instead of waving it through. Import the type if you need to name it:tsimport type { RawPdfSignature } from '@trexolab/verifykit-core'If your own type disagrees with the real shape, that is the error telling you so. Delete yours and use
RawPdfSignature. -
<CertificateViewer certs={[]} />no longer crashes when you populate it. If you render this public export before your certificates have loaded, you were gettingRendered more hooks than during the previous render.and probably working around it with ahasCerts &&guard. The guard is now unnecessary; it is also harmless to keep. -
Document Properties may now show different permission text. Documents that forbid printing showed "Allowed"; they now show "Not Allowed", and print-at-low-resolution-only documents show "Low resolution only" rather than collapsing into a yes/no. If you snapshot-test that dialog, expect the change.
-
decompressFlate()can now return bytes where it used to returnnull. Headerless deflate streams were never actually attempted. Callers that treatednullas "not compressed" will now receive the decompressed content instead. -
Nothing to do about the lint change.
npm run lintis at zero errors and CI enforces it; that affects contributors to this repository, not consumers.
v0.6.5 to v0.6.6
Release: ./package.json is exported; Node consumer tests in CI
Drop-in — no API changes. Package metadata and repository tooling only; the viewer and the verification engine are byte-for-byte what 0.6.5 shipped.
What changes for you:
require('@trexolab/verifykit-core/package.json')works. It previously threwERR_PACKAGE_PATH_NOT_EXPORTED— a package with anexportsmap has to list that subpath explicitly, and it was missing. If a bundler plugin or a version-reporting tool failed on your setup for that reason, it will now resolve. Applies to all four packages.- Nothing else to do. No imports, props, or behaviour changed.
v0.6.4 to v0.6.5
Release: the core loads under plain Node.js
Drop-in — no API changes. Only @trexolab/verifykit-core changed; the other
three packages are republished at 0.6.5 with no code changes so the versions stay
aligned.
What changes for you:
@trexolab/verifykit-corenow initialises under Node.js, Deno and Bun. The built bundle imported its wasm-bindgen glue without a file extension. Bundlers resolve that; Node's ESM resolver does not, soawait initWasm()— and with itcreateVerifier()andverifyPdf()— threwERR_MODULE_NOT_FOUND: Cannot find module '…/pkg/verifykit_core_wasm'for any consumer not running the package through a bundler. Every release up to and including 0.6.4 is affected; if you verify signatures in a Node service, a CLI, a test runner that does not bundle, or a serverless function, upgrade.- Browser and framework integrations are unaffected and always were — Vite, webpack and Next.js all resolved the extensionless specifier. If the viewer works for you today, this release changes nothing about it.
- The console no longer logs
using deprecated parameters for the initialization functionon every WASM initialisation. If you were filtering that warning out of your logs, you can drop the filter.
v0.6.3 to v0.6.4
Release: certification banner fits the space it has
Drop-in — no API changes. Only @trexolab/verifykit-react changed; the core,
vanilla and revocation packages are republished at 0.6.4 with no code changes so
the four versions stay aligned.
What changes for you:
-
The certification banner no longer overflows the "Signature Panel" button. Its message was
white-space: nowrapwithoutmin-width: 0, so as a flex item it refused to shrink and ran underneath the button. Latent since the bar was written; visible from 0.6.3, when the Adobe-worded sentence reached ~140 characters. -
The banner picks its wording to fit the width it has. Rather than clipping the sentence mid-email, it measures three complete phrasings and renders the longest that fits:
Sentence full Certified by <CN> <email>, <OU>, certificate issued by <issuer CN>.compact Certified by <CN>, <OU>.minimal Certified by <CN>.The permission sentence is dropped before the message loses characters, and the untrimmed text is always on the element's
title.
If you render <DocumentMessageBar>
yourself, its props are unchanged — but the text it renders now depends on the
width of the bar, so a test asserting the exact banner string should either set a
wide viewport or read the title instead. The signature panel is unaffected.
v0.6.2 to v0.6.3
Release: DocMDP certification detected regardless of key order
Drop-in — no API changes. Some documents that reported as merely "signed" will now correctly report as certified.
What changes for you:
- Certified (DocMDP) documents written by iText and similar tools are now
recognised. The permission extractor required
/TransformParamsto follow/DocMDPimmediately; PDF dictionaries are unordered, and iText writes/Reference[<</TransformMethod/DocMDP/Type/SigRef/TransformParams<</P 1…>>…]. Those files read as uncertified:mdpPermissionstayed null, the message bar showed "Signed and all signatures are valid.", and the panel's CERTIFIED badges never appeared. PdfSignature.mdpPermissionis now populated for those documents (1,2or3). If your code branches on it, a document that previously took the "not certified" path may now take the certified one — that is the fix, but it is worth checking any snapshot tests or analytics that counted certified documents.- The banner wording changed to match Adobe, from
Certified by <name>.toCertified by <CN> <email>, <OU>, certificate issued by <issuer CN>.Every part degrades independently, so a certificate without an email or organisation still yields a clean sentence. See 0.6.4 above for how it adapts to width.
Signature validity is unaffected. apply_doc_mdp_rules only invalidates a
P=1 certification when the certifying signature does not cover the whole file,
and that is checked independently of this extraction — no document changes from
valid to invalid because of this release.
v0.6.1 to v0.6.2
Release: registry keeps every version; docs corrections
No SDK code changed. Installing 0.6.2 gets you the same viewer as 0.6.1 — the fixes are to publishing infrastructure and documentation.
What changes for you:
- Pinning an older version now works. The registry previously served only the
newest release, so
npm install @trexolab/verifykit-react@0.6.0returned 404 as soon as 0.6.1 shipped. Every published version is now available, and 0.5.13, 0.5.14 and 0.6.0 have been restored. A plainnpm installstill gives you the latest. pdfjs-distis no longer pulled in through the registry. The manifest kept advertising it as a dependency of@trexolab/verifykit-reactafter v0.6.0 bundled it, so resolving through the registry installed ~10 MB of pdf.js the viewer does not use. If you upgraded to 0.6.x already and still seepdfjs-distin your lockfile with nothing else depending on it, delete the lockfile entry and reinstall.
Each version now advertises the dependencies it actually shipped with, so 0.5.x
correctly declares pdfjs-dist and 0.6.x does not.
v0.6.0 to v0.6.1
Release: correct signature Field Name on encrypted PDFs
Drop-in — no breaking changes.
What changes for you:
- Signature Properties no longer shows ciphertext as the Field Name. On a
password-protected PDF the "Field Name" row rendered mojibake such as
{:žhe*K÷#ΘoIT{…. A signature field's AcroForm/Tis encrypted in an encrypted document and the Rust/WASM core cannot decrypt PDF strings, soPdfSignature.fieldNamecarries raw ciphertext there. The dialog now displays the name PDF.js reports for the signature's widget, already decrypted, and omits the row when no decrypted name is obtainable rather than printing bytes. PdfSignature.fieldNameis deliberately unchanged.appearance-swappermatches it against the/Tit reads through pdf-lib — both encrypted — and that pairing is what lets the on-page icon swap resolve by name on encrypted documents. Do not "fix" it in your own code by decrypting it.SignatureDetailsTabaccepts an optionalfieldNameprop, the decrypted name to display, overridingsig.fieldName. Backward compatible: when omitted,sig.fieldNameis used if displayable and the row is hidden if it is not.
v0.5.14 to v0.6.0
Release: Next.js works without ssr: false; pdf.js bundled and lazy-loaded
Drop-in for application code — no source changes are required. The changes are to how the package is built and shipped, and one is worth acting on.
What you can now delete
dynamic(() => import('./Viewer'), { ssr: false }). The package ships a'use client'directive and no longer evaluates pdf.js at module scope, so it can be imported directly — including from a Server Component. Leaving your existingssr: falsewrapper in place still works; removing it is optional.- Any
config.resolve.alias.canvas = falsewebpack workaround. VerifyKit renders through PDF.js's browser canvas and never imports the Nodecanvaspackage. This alias was never needed and can go.
What you should act on
pdfjs-distis no longer a dependency of@trexolab/verifykit-react. pdf.js is bundled into the package (in a lazily-loaded chunk) andpdfjs-distmoved todevDependencies. Consequences:-
Remove
pdfjs-distfrom your ownpackage.jsonunless you import it directly yourself — installing it no longer affects which pdf.js the viewer uses. -
Any script that copies assets out of
node_modules/pdfjs-distwill break, because it may no longer be installed. CMaps and standard fonts ship inside the SDK instead:bash# before cp -r node_modules/pdfjs-dist/cmaps public/cmaps # after cp -r node_modules/@trexolab/verifykit-react/cmaps public/cmaps cp -r node_modules/@trexolab/verifykit-react/standard_fonts public/standard_fonts -
workerUrlis unchanged and still required — the worker is still fetched at runtime and must match5.5.207exactly.
-
- The package now ships unminified. Your bundler minifies it as normal; this only affects the raw tarball size and makes stack traces readable. (esbuild's name mangling corrupts pdf.js's private class fields, so minifying here was not an option.)
Behaviour change
waitForVerifier()no longer throwsError: Verifier not configured. It now waits for WASM initialisation instead of rejecting when called early, which meansuseVerification().load()works when called from a mount effect. If you added a retry loop or a readiness guard to work around this, you can remove it. Nothing catches this error in practice, so this is safe — but if you were matching on that message, it will no longer appear.
v0.5.13 to v0.5.14
Release: Encrypted-PDF signature icon now correct in every integration
No API changes. Drop-in upgrade.
- The on-page signature icon for encrypted PDFs now shows verified status in
every integration, not only in apps that happened to wire
onPasswordAccepted={verification.applyPassword}by hand. The accepted password now travels automatically throughVerifyKitProvider, so React (Viewer,CoreViewer, legacyPdfViewer),@trexolab/verifykit-vanillaand Vue all swap Adobe's yellow "?" to the validated icon with no host changes. onPasswordAcceptedandapplyPassword()still work and are still exported — they are now an opt-in escape hatch rather than a requirement. Keep them if you drive your own PDF.js instance; a password reported twice is ignored, so having both wired is harmless.- Owner-password-only PDFs (encrypted, but opening with an empty user password) are no longer skipped.
- Requirement to be aware of:
<Viewer>anduseVerification()must be under the same<VerifyKitProvider>— that provider is what carries the password between them. This is already true of any normal setup.
v0.5.2 to v0.5.3
Release: Adobe-parity improvements + configurable zoom + certificate chain export
Two small visible behavior changes plus a lot of additive API. No required code changes; most integrators can drop-in upgrade.
Behavior changes (may require attention)
-
SHA-1 signatures now default to
valid. Previously, any signature whose only issue was a SHA-1 digest producedoverallStatus: 'warning'. As of v0.5.3 the default mirrors Adobe Reader: SHA-1 signatures pass the algorithm check (andSignatureCheckResult.algorithmNameis set to"SHA-1"so UIs can still disclose the algorithm). If your app routed the old warning into a specific UI state or metric, either update that logic or restore the strict policy:tsimport { createVerifier } from '@trexolab/verifykit-core' const verifier = await createVerifier({ algorithmPolicy: { sha1: 'warn' } }) // — or globally — import { setAlgorithmPolicy } from '@trexolab/verifykit-core' await setAlgorithmPolicy({ sha1: 'warn' })MD5 / MD2 / MD4 remain hardcoded
Invalid— not configurable. -
Viewer zoom range is now 25 %–1000 % (was 40 %–500 %). Matches
pdf.jsand lets users inspect fine detail in signature stamps / seals. The zoom dropdown filters to the configured range, so no UI breaks. Restore the old range with:tsximport { defaultLayoutPlugin } from '@trexolab/verifykit-react' const layout = defaultLayoutPlugin({ zoom: { minScale: 0.4, maxScale: 5 } })Vanilla:
VerifyKit.create(el, { zoom: { minScale: 0.4, maxScale: 5 } }).
New features
- Legacy
adbe.pkcs7.sha1signatures now verify (previously produced false "document modified" INVALID on PDF 1.3 signatures with nosignedAttrs). No migration needed — you'll just see more PDFs verify correctly. - Certificate chain export from the Signature Properties dialog's Certificates tab (
Export Chain ▾→ PEM / PKCS#7 / ZIP). Also exposed programmatically: new core helperbuildCertChainPkcs7and new React helpersexportCertChainAsPem/exportCertChainAsPkcs7/exportCertChainAsZip. CertificateInfo.rawDer(Uint8Array) — raw DER bytes now exposed on every CertificateInfo. Never read by the verification pipeline. Per-cert PEM / DER buttons inCertificateViewer.DetailPanelpreviously rendered only if you populated this field; they now render automatically.AlgorithmPolicy+setAlgorithmPolicy/resetAlgorithmPolicyruntime API — see above.SignatureCheckResult.algorithmName— optional field on every check, populated by the algorithm-strength check so UIs can display the algorithm regardless of overall status.ZoomPluginOptions—zoomPlugin(),defaultLayoutPlugin({ zoom }), and vanillaVerifyKit.create({ zoom })all accept{ minScale?, maxScale?, step? }.- Docs-site: new
/faqroute + IndexNow protocol + richer JSON-LD schemas. Does not affect SDK consumers.
Repository layout
- Internal reorganisation on our side. No published package name, entry point or API changed, so nothing here affects your integration.
Upgrade path: npm install + rebuild. Review the two behavior changes above; apply the opt-out snippets if your app depends on the previous behavior.
v0.4.6 to v0.5.0
Release: Full codebase audit, type safety, and Rust/WASM cleanup
This is a non-breaking quality release. No API changes, no new exports, no removed exports. Drop-in upgrade from v0.4.x.
Key changes:
- Full TypeScript audit -- resolved all type errors and unused variables across all packages.
- Rust/WASM engine cleanup -- zero clippy warnings, idiomatic Rust patterns, removed dead functions and stale constants.
- Plugin-revocation type safety -- fixed type mismatches in handler functions.
- ESLint configuration overhaul -- unified linting across the monorepo.
- Fresh production builds verified clean -- all four packages build without warnings.
Upgrade path: Drop-in replacement. Run npm install and rebuild. No code changes required.
v0.4.0 to v0.4.5
Release: Display status fix, Adobe Reader parity improvements, PAdES tab, document timestamp indicators
Key changes:
-
padesLevelis now optional --PdfSignature.padesLevelhas changed fromPAdESLeveltoPAdESLevel | null. For non-ETSI signatures (e.g.,adbe.pkcs7.detached),padesLevelisnull. ETSI signatures (ETSI.CAdES.detached,ETSI.RFC3161) continue to report their PAdES conformance level. -
attemptedfield onSignatureCheckResult-- A new optionalattempted?: booleanfield distinguishes between checks that were actively tried (e.g., online revocation request) and checks that were never attempted (e.g., revocation without the plugin). This enables accurate Adobe Reader parity ingetDisplayStatus(). -
getDisplayStatus()Adobe Reader parity changes -- The display status logic now considers theattemptedfield. Revocation checks that were actively attempted but returnedunknowncause the display status to be"unknown", while checks that were never attempted (offline) do not penalize the signature. This matches Adobe Reader DC behavior: offline = valid, online + failed = unknown. -
PAdES tab in signature properties -- The
<SignaturePropertiesModal>now includes a PAdES tab that shows the detected conformance level and related details for ETSI signatures. -
Document Timestamp visual indicators -- Document timestamps (
ETSI.RFC3161sub-filter) are now visually distinguished from regular signatures in the viewer.
Upgrade path:
-
If your code reads
sig.padesLevel, add a null check:ts// Before console.log(`PAdES: ${sig.padesLevel}`) // may be null now // After if (sig.padesLevel) { console.log(`PAdES: ${sig.padesLevel}`) } else { console.log('Standard PKCS#7 signature (no PAdES level)') } -
If you check
revocationCheck.statusfor UI display, consider using the newattemptedfield to differentiate between "not checked" and "checked but unknown." -
No breaking changes to the React component API. The PAdES tab and document timestamp indicators are added automatically.
v0.3.2 to v0.4.0
Release: Flicker-free appearance swap, single-render VerificationFloater
Key changes:
- Flicker-free appearance swap — The viewer now swaps between normal and signature appearance streams without visual flicker. Previously, switching appearances could cause a brief blank frame.
- Single-render VerificationFloater — The
VerificationFloatercomponent now renders in a single pass instead of mounting, measuring, and re-rendering. This eliminates layout shift when the floater appears.
Upgrade path: Drop-in replacement. No API changes.
v0.3.1 to v0.3.2
Release: Embed VerificationFloater, smart context menu, live demo, polyfill fix
Key changes:
- Embedded VerificationFloater — The verification status floater is now embedded directly in the viewer rather than rendered as a portal. This simplifies integration and avoids z-index conflicts.
- Smart context menu — Right-clicking a signature field now shows a context menu with signature details, certificate info, and verification status.
- Live demo — The documentation site gained an interactive demo page at
/demo. - Polyfill fix — Fixed a compatibility issue with the
structuredClonepolyfill in older browsers and Node.js environments.
Upgrade path: Drop-in replacement. No API changes.
Pre-v0.3.1 to v0.3.1+
This was the most significant architectural change in the SDK's history.
WASM Base64 Embedding
Before v0.3.1, the WASM binary (verifykit_core_wasm_bg.wasm) was shipped as a separate file. You needed bundler plugins to handle it:
// vite.config.ts (no longer needed)
import wasm from 'vite-plugin-wasm'
export default { plugins: [wasm()] }// webpack.config.js (no longer needed)
module.exports = {
experiments: { asyncWebAssembly: true },
}From v0.3.1, the WASM binary was base64-embedded directly into the JavaScript bundle, so no external .wasm file had to be served and no bundler configuration was required.
Superseded in v0.12.0. The binary ships as a
.wasmfile again — but the plugins this section tells you to remove are still not needed, because bundlers now handle it natively. See v0.11.0 to v0.12.0.
Migration:
- Remove
vite-plugin-wasmor any WASM-related bundler plugins. - Remove webpack
asyncWebAssemblyexperiment configuration. - Remove any static file serving rules for
.wasmfiles. - Update to
@trexolab/verifykit-core>= 0.3.1.
If you still need to load the WASM binary from a custom location (e.g., a CDN), you can use setWasmUrl():
import { setWasmUrl } from '@trexolab/verifykit-core'
setWasmUrl('/custom/path/verifykit_core_wasm_bg.wasm')Viewer Migration
Legacy PdfViewer → Viewer / CoreViewer
The SDK originally shipped a monolithic PdfViewer component that bundled all viewer features (toolbar, zoom, search, sidebar, print) into a single component. The plugin-based architecture replaced it with two components, and PdfViewer was removed in 0.9.0 — if you are on 0.8.x or earlier and still import it, this is the change to make.
| Component | Package | Purpose |
|---|---|---|
CoreViewer | @trexolab/verifykit-react | Headless viewer — renders PDF pages with no built-in UI. All features are added via plugins. |
Viewer | @trexolab/verifykit-react | Pre-configured viewer — wraps CoreViewer with the defaultLayoutPlugin that includes toolbar, zoom, search, page navigation, print, and sidebar. |
Migration from PdfViewer to Viewer:
// Before
import { PdfViewer } from '@trexolab/verifykit-react'
<PdfViewer fileUrl="/doc.pdf" />
// After
import { Viewer, VerifyKitProvider } from '@trexolab/verifykit-react'
<VerifyKitProvider>
<Viewer fileUrl="/doc.pdf" />
</VerifyKitProvider>The Viewer component requires a VerifyKitProvider ancestor. The provider initializes the WASM engine, manages verification state, and provides the context that plugins use.
Plugin Architecture Migration
The monolithic PdfViewer included all features by default with no way to remove or customise individual features. The plugin architecture lets you compose only what you need:
import {
CoreViewer,
VerifyKitProvider,
toolbarPlugin,
zoomPlugin,
searchPlugin,
} from '@trexolab/verifykit-react'
// Only include zoom and search — no toolbar, no print, no sidebar
const plugins = [zoomPlugin(), searchPlugin()]
function App() {
return (
<VerifyKitProvider>
<CoreViewer fileUrl="/doc.pdf" plugins={plugins} />
</VerifyKitProvider>
)
}The defaultLayoutPlugin() is a meta-plugin that composes the standard set of plugins. Use it when you want the full default experience:
import {
CoreViewer,
VerifyKitProvider,
defaultLayoutPlugin,
} from '@trexolab/verifykit-react'
const plugins = [defaultLayoutPlugin()]
function App() {
return (
<VerifyKitProvider>
<CoreViewer fileUrl="/doc.pdf" plugins={plugins} />
</VerifyKitProvider>
)
}This is equivalent to using the Viewer component, which applies defaultLayoutPlugin automatically.
Need Help?
If you encounter issues while upgrading, check the Troubleshooting guide or open an issue on the GitHub repository.