VerifyKitv0.5.1

Plugins

VerifyKit has two separate plugin systems that serve different purposes:

  1. Core Engine Plugins (VerifyKitPlugin) -- Extend the verification engine with custom trust stores, revocation checking, and input resolvers.
  2. 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

ts
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:

ts
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:

ts
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:

ts
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:

ts
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

  1. setup -- Called once per createVerifier() invocation, in registration order.
  2. trustStore -- Merged (or replaced) into the WASM trust store.
  3. inputResolver -- Called for each verify() or extractMetadata() call before default resolution.
  4. revocation -- After WASM verification completes, if revocation status is unknown, each plugin's OCSP and CRL hooks are tried in order.

Registering Core Plugins

ts
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:

ts
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:

ts
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:

ts
// Next.js App Router: app/api/revocation/route.ts
import { handleRevocation } from '@trexolab/verifykit-plugin-revocation/handler'
 
export const POST = handleRevocation()
ts
// 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())
ts
// 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

tsx
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

ts
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

ts
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:

ts
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:

ts
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.

Plugins can add custom sidebar panels:

ts
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:

ts
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:

ts
const metaPlugin: ViewerPlugin = {
  name: 'my-layout',
  composedPlugins: [zoomPlugin(), searchPlugin(), toolbarPlugin()],
}

Built-in Plugins

VerifyKit includes 24 built-in viewer plugins:

PluginFactoryDescription
zoomzoomPlugin()Zoom in, zoom out, zoom to percentage, fit modes
pageNavigationpageNavigationPlugin()Page navigation, current page input, page count
rotationrotationPlugin()Rotate document CW/CCW
searchsearchPlugin()Find text in document with match highlighting
printprintPlugin()Print the document
downloaddownloadPlugin()Download the PDF file
themethemePlugin()Light/dark theme toggle
fullscreenfullscreenPlugin()Enter/exit fullscreen mode
accessibilityaccessibilityPlugin()UI font scale control with toolbar dropdown
selectionselectionPlugin()Hand tool / text selection cursor toggle
scrollModescrollModePlugin()Vertical, horizontal, and wrapped scroll modes
spreadModespreadModePlugin()Single page, two-page, and two-page-cover spreads
signaturesignaturePlugin()Signature panel, verification status bar, signature widgets
sidebarsidebarPlugin()Collapsible sidebar container for tab panels
thumbnailthumbnailPlugin()Page thumbnail navigation in sidebar
bookmarkbookmarkPlugin()Document outline/bookmark navigation in sidebar
attachmentattachmentPlugin()Embedded file attachments in sidebar
highlighthighlightPlugin()Text and annotation highlighting
openFileopenFilePlugin()Open file button in toolbar
propertiespropertiesPlugin()Document properties dialog
shortcutHelpshortcutHelpPlugin()Keyboard shortcuts help dialog (? key)
contextMenucontextMenuPlugin()Right-click context menu
moreMenumoreMenuPlugin()Overflow menu for toolbar actions
toolbartoolbarPlugin()Toolbar container that renders all toolbar slots
defaultLayoutdefaultLayoutPlugin()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:

tsx
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:

tsx
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:

tsx
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')