Vanilla JS
@trexolab/verifykit-vanilla is a zero-build, drop-in PDF viewer with signature verification. No React knowledge required -- load from a CDN or install via npm and embed a full-featured PDF viewer in any web page.
The package bundles React internally and wraps @trexolab/verifykit-react to expose all viewer features as imperative JavaScript methods.
CDN Installation
Add the stylesheet and script tags to your HTML:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://verifykit.trexolab.com/cdn/verifykit.css">
<style>
html, body { height: 100%; margin: 0; padding: 0; }
</style>
</head>
<body>
<div id="viewer" style="width: 100%; height: 100vh;"></div>
<script src="https://verifykit.trexolab.com/cdn/verifykit.umd.js"></script>
<script>
const viewer = VerifyKit.create(document.getElementById('viewer'), {
workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
theme: { mode: 'system' },
features: {
signatures: true,
print: true,
download: true,
search: true,
fullscreen: true,
},
})
fetch('/document.pdf')
.then(r => r.arrayBuffer())
.then(buf => viewer.load(buf, 'document.pdf'))
</script>
</body>
</html>The UMD bundle includes the WASM core. The
.wasmbinary file must be served from the same directory as the JS file. When using the CDN, this is handled automatically.
npm Installation
For bundler-based projects:
npm install @trexolab/verifykit-vanillaimport { create } from '@trexolab/verifykit-vanilla'
import '@trexolab/verifykit-vanilla/verifykit.css'
const viewer = create(document.getElementById('viewer'), {
workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
theme: { mode: 'system' },
features: {
signatures: true,
print: true,
download: true,
},
})
const result = await viewer.load(pdfArrayBuffer, 'report.pdf')
console.log('Signatures:', result.signatures.length)
viewer.destroy()The PDF.js Worker
workerUrl is the one required option — create() throws without it. It
has to point at pdf.worker.min.mjs from pdfjs-dist@5.5.207, the
legacy/build/ copy.
That version is fixed, not chosen. verifykit.umd.js has the pdf.js library
bundled inside it; the worker is a separate file loaded at runtime, and pdf.js
refuses to run a library and a worker whose versions differ. The examples above
use the unpkg URL, which pins the version for you:
https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs
To serve it yourself instead — a CSP with no external script-src, or an
air-gapped deployment — download that file once and point workerUrl at your
own copy:
curl -o public/pdf.worker.min.mjs \
https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjsconst viewer = VerifyKit.create(el, { workerUrl: '/pdf.worker.min.mjs' })Re-download it whenever you upgrade VerifyKit: a mismatched pair fails hard, and the console names both versions when it does. See Installation → PDF.js Worker Setup for the npm-registry alternative and the full explanation.
Creating a Viewer
Use VerifyKit.create(container, options?) to mount a viewer on a DOM element:
const viewer = VerifyKit.create(document.getElementById('viewer'), {
workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
theme: { mode: 'system' },
})Container requirements: The container element must exist in the DOM and have explicit dimensions (width and height). The viewer fills its parent -- if the parent has zero height, nothing renders. A common pattern is style="width: 100%; height: 100vh;".
If the container is null or undefined, create() throws "requires a valid HTMLElement". Make sure the element exists before calling create(), or wrap the call in a DOMContentLoaded listener.
Loading Documents
Use viewer.load(source, fileName?) to load a PDF. It accepts three input types:
// URL string
await viewer.load('/documents/report.pdf', 'report.pdf')
// File object (e.g., from a file input)
const file = inputElement.files[0]
const buffer = await file.arrayBuffer()
await viewer.load(buffer, file.name)
// ArrayBuffer
const response = await fetch('/document.pdf')
const buf = await response.arrayBuffer()
await viewer.load(buf, 'document.pdf')load() returns a Promise<VerificationResult> containing all signature data. You can inspect the result immediately:
const result = await viewer.load(buffer, 'doc.pdf')
for (const sig of result.signatures) {
console.log(sig.name, sig.overallStatus)
}All imperative methods (like
goToPage,zoomIn) are no-ops untilload()has resolved. Alwaysawaitthe load before calling other methods.
Password-protected documents
Encrypted PDFs need no extra handling. The viewer shows its own password prompt, and once the password is accepted the signature drawn on the page swaps from Adobe's yellow "?" to the validated status icon, exactly as it does for an unencrypted file. The original signed bytes are never modified — the swapped copy is display-only and held in memory.
Configuration Options
interface VerifyKitOptions {
// Worker (REQUIRED)
workerUrl: string // URL to the PDF.js worker script (required)
// Engine (optional)
wasmUrl?: string // URL to verifykit_core_wasm_bg.wasm.
// Defaults to the copy in dist/, resolved
// relative to the bundle's own URL.
// Theme
theme?: {
mode?: 'light' | 'dark' | 'system'
overrides?: Record<string, string>
}
// Features
features?: VerifyKitFeatures
// Core config
trustStore?: TrustStoreConfig
plugins?: VerifyKitPlugin[]
// i18n
locale?: string
translations?: Partial<TranslationStrings>
// Callbacks
onVerified?: (result: VerificationResult) => void
onError?: (error: LoadError) => void
onDocumentLoaded?: (info: { pageCount: number; fileName: string }) => void
onPageChange?: (page: number) => void
onZoomChange?: (scale: number) => void
onThemeChange?: (mode: 'light' | 'dark') => void
}Instance Methods
Once a document is loaded, the viewer instance exposes a full programmatic API.
Navigation
viewer.goToPage(5) // Jump to page 5
viewer.goToNextPage() // Next page
viewer.goToPreviousPage() // Previous page
viewer.goToFirstPage() // First page
viewer.goToLastPage() // Last page
viewer.getCurrentPage() // Returns current page number
viewer.getPageCount() // Returns total page countZoom
viewer.zoomIn() // Zoom in one step
viewer.zoomOut() // Zoom out one step
viewer.zoomTo(2.0) // Set zoom to 200%
viewer.setFitMode('width') // Fit to width ('none', 'width', 'page')
viewer.getZoom() // Returns current zoom scaleRotation
viewer.rotateCW() // Rotate 90 degrees clockwise
viewer.rotateCCW() // Rotate 90 degrees counter-clockwise
viewer.getRotation() // Returns current rotation in degreesSearch
viewer.openSearch() // Open the search bar
viewer.closeSearch() // Close the search barPrint and Download
viewer.print() // Open print dialog
viewer.download() // Download the PDFFullscreen
viewer.enterFullscreen() // Enter fullscreen mode
viewer.exitFullscreen() // Exit fullscreen mode
viewer.toggleFullscreen() // Toggle fullscreenSignature Panel
viewer.openSignaturePanel() // Open the signature details panel
viewer.closeSignaturePanel()// Close the signature panelTheme
viewer.setTheme('dark') // Set theme ('light', 'dark', 'system')Revalidation
viewer.revalidate() // Re-verify all signatures on the current bufferState Inspection
viewer.getVerificationResult() // Returns VerificationResult or null
viewer.getSignatures() // Returns PdfSignature[]Event Handling
Subscribe to viewer events using viewer.on(event, callback). Each call returns an unsubscribe function.
// Verification complete
viewer.on('verified', (result) => {
console.log('Signatures:', result.signatures.length)
})
// Document loaded
viewer.on('documentLoaded', (info) => {
console.log(`Loaded ${info.pageCount} pages: ${info.fileName}`)
})
// Page navigation
const unsub = viewer.on('pageChange', (page) => {
console.log('Current page:', page)
})
// Zoom changes
viewer.on('zoomChange', (scale) => {
console.log('Zoom:', Math.round(scale * 100) + '%')
})
// Theme changes
viewer.on('themeChange', (mode) => {
console.log('Theme:', mode)
})
// Rotation
viewer.on('rotationChange', (rotation) => {
console.log('Rotation:', rotation)
})
// Fullscreen
viewer.on('fullscreenChange', (isFullscreen) => {
console.log('Fullscreen:', isFullscreen)
})
// Error (receives a LoadError object with name and message)
viewer.on('error', (error) => {
console.error('Viewer error:', error.name, error.message)
})
// Unsubscribe
unsub()Feature Flags
Toggle individual viewer features via the features option. Each feature defaults to the value shown:
interface VerifyKitFeatures {
search?: boolean // default: true
print?: boolean // default: true
download?: boolean // default: false
signatures?: boolean // default: true
thumbnails?: boolean // default: true
fullscreen?: boolean // default: true
themeToggle?: boolean // default: true
openFile?: boolean // default: false
rotation?: boolean // default: true
contextMenu?: boolean // default: true
properties?: boolean // default: true
shortcuts?: boolean // default: true
highlights?: boolean // default: true
}Example -- enable download and file opening, disable print:
const viewer = VerifyKit.create(el, {
workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
features: {
download: true,
openFile: true,
print: false,
},
})Internally, features flags are mapped to defaultLayoutPlugin({ disable: {...} }).
With File Input
A common pattern using a standard HTML file input:
<input type="file" id="file-input" accept=".pdf" />
<div id="viewer" style="width: 100%; height: 80vh;"></div>
<script src="https://verifykit.trexolab.com/cdn/verifykit.umd.js"></script>
<script>
const viewer = VerifyKit.create(document.getElementById('viewer'), {
workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
})
document.getElementById('file-input').addEventListener('change', (e) => {
const file = e.target.files[0]
if (!file) return
file.arrayBuffer().then(buf => viewer.load(buf, file.name))
})
</script>With Core Plugins
Core verification plugins (like revocation checking) can be passed through to the WASM engine:
import { create } from '@trexolab/verifykit-vanilla'
import { revocationPlugin } from '@trexolab/verifykit-plugin-revocation'
const viewer = create(document.getElementById('viewer'), {
workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
plugins: [revocationPlugin()],
trustStore: {
certificates: [myRootCaPem],
mode: 'merge',
},
})
const result = await viewer.load(pdfBuffer, 'document.pdf')
// Revocation checks now include live CRL/OCSP lookupsSelf-Hosting
When self-hosting instead of using a CDN, copy the entire dist/ directory:
cp -r node_modules/@trexolab/verifykit-vanilla/dist/ public/verifykit/Then reference the files from your HTML:
<link rel="stylesheet" href="/verifykit/verifykit.css">
<script src="/verifykit/verifykit.umd.js"></script>dist/ contains verifykit_core_wasm_bg.wasm, the verification engine — about
1.1 MB, and the largest thing in the package. It is a separate file rather than
part of the bundle so the browser can compile it while it downloads and cache it
apart from the JS, which changes far more often.
It is resolved relative to the bundle's own URL, so serving the two from the same
directory needs no configuration — that is what copying the whole of dist/
above gets you. Set wasmUrl when they are split up:
VerifyKit.create(el, {
workerUrl: '/verifykit/pdf.worker.min.mjs',
wasmUrl: '/verifykit/verifykit_core_wasm_bg.wasm',
})Serve it as application/wasm. Browsers refuse to stream-compile anything else,
and the engine falls back to a slower non-streaming path with a console warning
naming the type it got instead.
Serving it from a different origin than the page needs CORS. A <script src>
does not, but the bundle fetches the .wasm, so the host must send
Access-Control-Allow-Origin. Without it the viewer loads normally and then
fails when it tries to verify, which is a confusing way round. Give the JS and
the .wasm the same cache lifetime as well: the wasm-bindgen glue and its binary
are generated together and cannot be mixed across releases.
The worker is not in dist/ — it is not part of the package at all, so
copying dist/ does not get you it. Download it alongside and point workerUrl
at your copy, or the viewer still reaches out to a CDN at runtime:
curl -o public/verifykit/pdf.worker.min.mjs \
https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjsVerifyKit.create(el, { workerUrl: '/verifykit/pdf.worker.min.mjs' })The version must be 5.5.207 — see The PDF.js Worker.
Cleanup
Always call destroy() when the viewer is no longer needed. This cleans up store subscriptions, event listeners, and worker blob URLs:
viewer.destroy()In single-page applications, call destroy() when the viewer's container is removed from the DOM (e.g., on route change or component unmount).