Plugin API
Plugins are typed objects with optional lifecycle hooks — no class
hierarchy, so tree-shaking stays effective and framework wrappers can
compose plugins without inheritance. One sdk.install(plugin) per
plugin; uninstall by id.
Plugin shape
import type { TruPDFPlugin, PluginAPI } from '@trupdf/viewer';
const myPlugin: TruPDFPlugin = {
id: 'review-mode', // stable, unique (npm-name+version is conventional)
name: 'Review mode', // optional human-readable name
version: '1.0.0', // optional
toolbar: [/* ToolbarAction[] — flat toolbar contributions */],
sidebar: [/* SidebarPanel[] */],
menu: [/* MenuItem[] */],
presets: [/* PluginPresetDefinition[] — ribbon preset buttons */],
iconAliases: [/* PluginIconAlias[] */],
modularConfig: (config) => config, // config transformer, see below
async onInit(api: PluginAPI) {
api.events.on('annotation.added', (ev) => { /* … */ });
},
async onDestroy() {
// release resources
},
};All fields except id are optional.
Install / uninstall
import { TruPDFSDK } from '@trupdf/viewer';
// At construction…
const sdk = new TruPDFSDK({ plugins: [myPlugin] });
// …or at runtime (onInit runs immediately if an engine is attached)
await sdk.install(myPlugin);
const all = sdk.plugins.list();
await sdk.uninstall('review-mode');Installing a second plugin with an already-registered id throws
Plugin already registered: <id> — namespace your ids.
Toolbar actions
The flat contribution API. Hosts and framework wrappers render these
and emit plugin.toolbar.action on the bus when one fires.
interface ToolbarAction {
id: string; // stable id; used by event payloads
label: string; // visible label / aria-label
icon?: string; // text or SVG
hotkey?: string; // shortcut hint shown in tooltips
handler: (payload?: unknown) => unknown | Promise<unknown>;
}See Toolbar.
Sidebar panels
interface SidebarPanel {
id: string;
label: string;
// Returns DOM the host mounts into the sidebar slot. Framework
// wrappers re-shape this (the React wrapper accepts a node).
render: () => Node | Promise<Node>;
}const panel: SidebarPanel = {
id: 'review-status',
label: 'Review status',
render() {
const el = document.createElement('div');
el.textContent = 'Loading…';
return el;
},
};Menu items
interface MenuItem {
id: string;
label: string;
parent?: string; // menu the item hangs under (File / View / …)
handler?: (payload?: unknown) => unknown | Promise<unknown>;
}Preset buttons
Plugins can register new presetButton types that any
ModularConfig (or a customer's saved UI configuration) references
by buttonType. The SDK registers them when it attaches.
interface PluginPresetDefinition {
buttonType: string; // lookup key, e.g. 'acmeStampButton'
label: string;
icon: string;
hotkey?: string;
// Renders disabled with a "not yet available" tooltip.
isUnimplemented?: boolean;
// Click handler — receives the engine and the event bus.
onActivate: (ctx: { engine: unknown; events: EventBus }) => void | Promise<void>;
// Hide the button when false. Re-evaluated on every render.
isAvailable?: (ctx: { engine: unknown }) => boolean;
}Icon aliases
Map a legacy sprite id to one of the viewer's built-in icon names so existing customer configs resolve your plugin's icons:
interface PluginIconAlias {
spriteId: string; // e.g. 'icon-acme-stamp'
lucideName: string; // icon name, or another sprite id (transitive)
}Modular-config transformer
modularConfig lets a plugin append or replace items in any header
of the rendered UI. Transformers run in plugin registration order —
last write wins — and are skipped when the host doesn't pass a
modularConfig to the viewer (legacy flat-toolbar mode).
const plugin: TruPDFPlugin = {
id: 'acme-tools',
presets: [acmeStampPreset], // buttonType: 'acmeStampButton'
modularConfig: (config) => ({
...config,
modularComponents: {
...config.modularComponents,
acmeStamp: { type: 'presetButton', buttonType: 'acmeStampButton' },
},
}),
};PluginAPI surface
What plugins receive in onInit(api):
api.events— the full SDK event bus (subscribe / emit).api.list()— iterate all installed plugins.api.listToolbarActions()— aggregated toolbar contributions across every plugin (lets framework wrappers render once).api.listSidebarPanels()/api.listMenuItems()/api.listPresets()/api.listIconAliases()— same aggregation for the other contribution kinds.api.applyModularConfigTransformers(config)— runs every plugin's transformer in registration order and returns the final config (a throwing transformer logs a warning and is skipped).
The PluginAPI is deliberately narrow: it does not expose the viewer
engine or the managers. React to state through api.events; drive
the viewer through TruPDFSDK methods (goToPage, setScale,
rotate, getAnnotations(), getForms()).
Lifecycle
onInit(api)fires once per plugin whensdk.attach(engine)runs — or immediately duringsdk.install()if an engine is already attached. Plugins are isolated: one plugin throwing inonInitlogs an error and does not block the others.onDestroy()fires duringsdk.uninstall(id),sdk.detach(), andsdk.destroy().
Listeners registered via api.events.on are not auto-removed —
keep the handler references and call api.events.off in
onDestroy.
Conflicts
- Plugin ids are unique — a duplicate id throws at registration.
- Preset
buttonTypes are last-write-wins, so a later plugin can deliberately override an earlier one (or a built-in). - Toolbar / sidebar / menu contributions are aggregated as-is; use
namespaced ids (
acme-review-…) to avoid colliding with other plugins in the lists.