TPTruPDF

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.

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;
  },
};
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 when sdk.attach(engine) runs — or immediately during sdk.install() if an engine is already attached. Plugins are isolated: one plugin throwing in onInit logs an error and does not block the others.
  • onDestroy() fires during sdk.uninstall(id), sdk.detach(), and sdk.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.