Plugins
VerifyKit has two separate plugin systems that serve different purposes:
- Core Engine Plugins (
VerifyKitPlugin) -- Extend the verification engine with custom trust stores, revocation checking, and input resolvers. - Viewer Plugins (
ViewerPlugin) -- Extend the PDF viewer with UI features like zoom, search, print, sidebar tabs, and toolbar buttons.
Core Engine Plugins
Core plugins extend the Rust/WASM verification engine. They are passed to createVerifier() or through VerifyKitProvider and VerifyKit.create() configuration.
Interface
interface VerifyKitPlugin {
name: string
setup?: (ctx: PluginContext) => void | Promise<void>
trustStore?: TrustStoreConfig
revocation?: {
checkCRL?: (cert: CertificateInfo, urls: string[]) => Promise<RevocationCheckResult>
checkOCSP?: (cert: CertificateInfo, issuer: CertificateInfo, urls: string[]) => Promise<RevocationCheckResult>
}
inputResolver?: (input: PdfInput) => Promise<ArrayBuffer | null>
}Trust Store Extension
A plugin can add custom root CA certificates to the verification trust store:
const enterprisePlugin: VerifyKitPlugin = {
name: 'enterprise-trust',
trustStore: {
certificates: [myCorpRootPem],
mode: 'merge', // 'merge' adds to defaults; 'replace' removes them
},
}Revocation Checking
Plugins can implement online CRL and OCSP revocation checking. The core engine calls these hooks after initial signature validation when a signature's revocation status is unknown:
const myRevocationPlugin: VerifyKitPlugin = {
name: 'my-revocation',
revocation: {
async checkOCSP(cert, issuer, urls) {
// Query OCSP responder at urls[0]
return { status: 'good', source: 'OCSP' }
},
async checkCRL(cert, urls) {
// Download and parse CRL from urls[0]
return { status: 'good', source: 'CRL' }
},
},
}The return type is RevocationCheckResult:
interface RevocationCheckResult {
status: 'revoked' | 'revokedAfterSigning' | 'revokedNoTimestamp' | 'good' | 'unknown'
revocationDate?: Date
reason?: string
source?: string // e.g. 'OCSP', 'CRL'
}Input Resolvers
Plugins can resolve custom input types to ArrayBuffer. The first plugin to return a non-null result wins:
const s3Plugin: VerifyKitPlugin = {
name: 's3-resolver',
async inputResolver(input) {
if (typeof input === 'string' && input.startsWith('s3://')) {
return await fetchFromS3(input)
}
return null // pass through to default resolver
},
}Plugin Lifecycle
- setup -- Called once per
createVerifier()invocation, in registration order. - trustStore -- Merged (or replaced) into the WASM trust store.
- inputResolver -- Called for each
verify()orextractMetadata()call before default resolution. - revocation -- After WASM verification completes, if revocation status is
unknown, each plugin's OCSP and CRL hooks are tried in order.
Registering Core Plugins
import { createVerifier } from '@trexolab/verifykit-core'
const verifier = await createVerifier({
plugins: [myRevocationPlugin, enterprisePlugin],
})Example: @trexolab/verifykit-plugin-revocation
The official revocation plugin performs live CRL and OCSP lookups. It supports two modes:
Node.js -- Direct Mode
When running in Node.js, omit endpoint and the plugin fetches CRL/OCSP directly:
import { createVerifier } from '@trexolab/verifykit-core'
import { revocationPlugin } from '@trexolab/verifykit-plugin-revocation'
const verifier = await createVerifier({
plugins: [revocationPlugin()],
})
const result = await verifier.verify(pdfBuffer, 'document.pdf')
for (const sig of result.signatures) {
// Without plugin: status "unknown", detail "Not checked (offline)"
// With plugin: status "valid" or "invalid" with live check result
console.log(sig.revocationCheck.status, sig.revocationCheck.detail)
}
verifier.dispose()Browser -- Proxy Mode
In the browser, CRL/OCSP endpoints cannot be fetched directly (CORS). Pass an endpoint to route requests through your server:
import { createVerifier } from '@trexolab/verifykit-core'
import { revocationPlugin } from '@trexolab/verifykit-plugin-revocation'
const verifier = await createVerifier({
plugins: [revocationPlugin({ endpoint: '/api/revocation' })],
})Then mount a handler on your server:
// Next.js App Router: app/api/revocation/route.ts
import { handleRevocation } from '@trexolab/verifykit-plugin-revocation/handler'
export const POST = handleRevocation()// Express
import express from 'express'
import { expressHandler } from '@trexolab/verifykit-plugin-revocation/handler/express'
const app = express()
app.use(express.json())
app.post('/api/revocation', expressHandler())// Any framework (core function)
import { processRevocation } from '@trexolab/verifykit-plugin-revocation/handler/core'
const result = await processRevocation({
type: 'crl',
cert: certInfo,
urls: ['http://crl.example.com/root.crl'],
})With React Provider
import { revocationPlugin } from '@trexolab/verifykit-plugin-revocation'
<VerifyKitProvider config={{
workerUrl: 'https://unpkg.com/pdfjs-dist@5.5.207/legacy/build/pdf.worker.min.mjs',
plugins: [revocationPlugin({ endpoint: '/api/revocation' })],
}}>
<MyViewer />
</VerifyKitProvider>With Vanilla JS
import { create } from '@trexolab/verifykit-vanilla'
import { revocationPlugin } from '@trexolab/verifykit-plugin-revocation'
const viewer = create(el, { plugins: [revocationPlugin({ endpoint: '/api/revocation' })] })See the @trexolab/verifykit-plugin-revocation README for the full API reference, import paths, and configuration options.
Viewer Plugins
Viewer plugins extend the <Viewer> component with UI features. Each plugin is an independent module that can contribute toolbar buttons, sidebar tabs, keyboard shortcuts, overlays, and lifecycle handlers.
Interface
interface ViewerPlugin {
name: string
dependencies?: string[]
composedPlugins?: ViewerPlugin[]
// Lifecycle
install?(ctx: PluginContext): void
onDocumentLoad?(e: DocumentLoadEvent): void
onDocumentUnload?(): void
onPageChange?(e: PageChangeEvent): void
onZoomChange?(e: ZoomChangeEvent): void
onRotationChange?(e: RotationChangeEvent): void
destroy?(): void
// UI contributions
renderToolbarSlot?: Partial<ToolbarSlots>
sidebarTabs?: SidebarTabDefinition[]
renderOverlay?: (props: OverlayRenderProps) => ReactNode
renderRightPanel?: (props: OverlayRenderProps) => ReactNode
renderPageOverlay?: (props: PageOverlayRenderProps) => ReactNode
transformToolbarSlots?: (slots: ToolbarSlots) => ToolbarSlots
}Plugin Context
When a plugin's install() method is called, it receives a PluginContext:
interface PluginContext {
store: ViewerStore // Reactive viewer state store
getDocument(): PDFDocumentProxy | null // Currently loaded PDF document
scrollToPage(pageNum: number): void // Scroll viewer to a page
getScrollContainer(): HTMLElement | null
getViewerContainer(): HTMLElement | null
registerShortcut(shortcut: KeyboardShortcut): () => void
onOpenFile?: (file: File) => void // File open handler
t(key: string): string // Translation function
}Toolbar Slots
Plugins contribute buttons to the toolbar by providing renderToolbarSlot entries:
const myPlugin: ViewerPlugin = {
name: 'my-button',
renderToolbarSlot: {
MyButton: ({ store }) => <button onClick={() => { /* ... */ }}>Click</button>,
},
}Named toolbar slots include: SearchPopover, GoToPreviousPage, CurrentPageInput, NumberOfPages, GoToNextPage, ZoomOut, Zoom, ZoomIn, Rotate, CursorTool, SignaturePanel, OpenFile, Download, Print, ThemeToggle, FontScale, Fullscreen, MoreMenu, and any custom slot names.
Sidebar Tabs
Plugins can add custom sidebar panels:
const myPlugin: ViewerPlugin = {
name: 'my-sidebar',
sidebarTabs: [{
id: 'my-tab',
label: 'My Tab',
icon: <MyIcon />,
component: MyTabPanel,
order: 100,
canRender: (state) => state.isDocumentLoaded,
}],
}Keyboard Shortcuts
Register keyboard shortcuts through the plugin context:
const myPlugin: ViewerPlugin = {
name: 'my-shortcuts',
install(ctx) {
ctx.registerShortcut({
id: 'my-action',
key: 'g',
ctrl: true,
handler: (e) => {
e.preventDefault()
// perform action
},
description: 'Go to page',
})
},
}Composed Plugins
A plugin can compose other plugins using composedPlugins. This is how defaultLayoutPlugin works -- it is a meta-plugin that includes all standard plugins:
const metaPlugin: ViewerPlugin = {
name: 'my-layout',
composedPlugins: [zoomPlugin(), searchPlugin(), toolbarPlugin()],
}Built-in Plugins
VerifyKit includes 24 built-in viewer plugins:
| Plugin | Factory | Description |
|---|---|---|
zoom | zoomPlugin() | Zoom in, zoom out, zoom to percentage, fit modes |
pageNavigation | pageNavigationPlugin() | Page navigation, current page input, page count |
rotation | rotationPlugin() | Rotate document CW/CCW |
search | searchPlugin() | Find text in document with match highlighting |
print | printPlugin() | Print the document |
download | downloadPlugin() | Download the PDF file |
theme | themePlugin() | Light/dark theme toggle |
fullscreen | fullscreenPlugin() | Enter/exit fullscreen mode |
accessibility | accessibilityPlugin() | UI font scale control with toolbar dropdown |
selection | selectionPlugin() | Hand tool / text selection cursor toggle |
scrollMode | scrollModePlugin() | Vertical, horizontal, and wrapped scroll modes |
spreadMode | spreadModePlugin() | Single page, two-page, and two-page-cover spreads |
signature | signaturePlugin() | Signature panel, verification status bar, signature widgets |
sidebar | sidebarPlugin() | Collapsible sidebar container for tab panels |
thumbnail | thumbnailPlugin() | Page thumbnail navigation in sidebar |
bookmark | bookmarkPlugin() | Document outline/bookmark navigation in sidebar |
attachment | attachmentPlugin() | Embedded file attachments in sidebar |
highlight | highlightPlugin() | Text and annotation highlighting |
openFile | openFilePlugin() | Open file button in toolbar |
properties | propertiesPlugin() | Document properties dialog |
shortcutHelp | shortcutHelpPlugin() | Keyboard shortcuts help dialog (? key) |
contextMenu | contextMenuPlugin() | Right-click context menu |
moreMenu | moreMenuPlugin() | Overflow menu for toolbar actions |
toolbar | toolbarPlugin() | Toolbar container that renders all toolbar slots |
defaultLayout | defaultLayoutPlugin() | Meta-plugin that composes all plugins above |
defaultLayoutPlugin
The defaultLayoutPlugin() is the recommended starting point. It composes all 24 plugins into a batteries-included layout with a single call:
import { defaultLayoutPlugin } from '@trexolab/verifykit-react'
const [layout] = useState(() => defaultLayoutPlugin())
<Viewer fileBuffer={buffer} plugins={[layout.plugin]} />Disabling Features
Pass a disable object to turn off specific plugins:
const [layout] = useState(() =>
defaultLayoutPlugin({
disable: {
search: true,
print: true,
download: true,
fullscreen: true,
theme: true,
rotation: true,
selection: true,
sidebar: true, // Also disables thumbnails, bookmarks, attachments
signatures: true,
highlights: true,
openFile: true,
properties: true,
shortcuts: true,
contextMenu: true,
},
})
)Accessing Sub-Plugin APIs
The defaultLayoutPlugin() return value exposes API accessors for each sub-plugin:
const [layout] = useState(() => defaultLayoutPlugin())
// Programmatic control via sub-plugin APIs
layout.zoom.zoomIn()
layout.zoom.zoomTo(1.5)
layout.navigation.goToPage(10)
layout.search.open()
layout.theme.toggleTheme()
layout.theme.setTheme('dark')
layout.fullscreen.toggle()
layout.sidebar.open()
layout.sidebar.close()
layout.signature.openPanel()
layout.print.print()
layout.download.download()
layout.rotation.rotateCW()
layout.rotation.rotateCCW()
layout.selection.setTool('hand')
layout.scrollMode.setMode('vertical')
layout.spreadMode.setMode('single')