Skip to content

Plugin System API

Plugin System Reference
Reference
[1]

Plugin System Architecture

Every non-trivial feature (LaTeX, Mermaid, embeds) is implemented as a plugin.

Design Goals

  • Plugins are the primary extension mechanism
  • Plugins are isolated — one cannot break another
  • Plugins are lazy-loadable via dynamic import()
  • Plugins have a well-defined lifecycle (setup/teardown)
  • The plugin API is stable and versioned
[2]
// Plugin Interface
interface SciNotebookPlugin {
  id: string;                // Unique, e.g. "sci-nb-latex"
  name: string;
  version: string;
  dependencies?: string[];   // IDs of plugins this depends on

  setup?(ctx: PluginContext): void | Promise<void>;
  teardown?(ctx: PluginContext): void;

  cellTypes?: CustomCellType[];
  rendering?: PluginRenderingHooks;
  keybindings?: PluginKeybinding[];
  toolbar?: PluginToolbarItem[];
  styles?: string | string[];
}
[3]
// PluginContext — passed to setup() and teardown()
interface PluginContext {
  // Pipeline hooks
  addPreprocessor(fn: PreprocessorFn): Unsubscribe;
  addASTTransformer(fn: ASTTransformer): Unsubscribe;
  addRenderer(renderer: CellRenderer): Unsubscribe;
  addPostprocessor(fn: PostProcessor): Unsubscribe;

  // Engine access
  engine: Readonly<EditorEngine>;
  getNotebook(): Readonly<Notebook>;
  getCell(id: string): Readonly<Cell> | undefined;
  updateCellSource(id: string, source: string): void;
  insertCell(index: number, type?: CellType): Cell;

  // Event bus
  on(event: string, handler: Function): Unsubscribe;
  emit(event: string, payload?: unknown): void;

  // Configuration
  config: Record<string, unknown>;

  // Logging
  log: {
    info(msg: string, ...args: unknown[]): void;
    warn(msg: string, ...args: unknown[]): void;
    error(msg: string, ...args: unknown[]): void;
  };
}
[4]

Plugin Lifecycle

register → validate deps → load styles → setup() → Active
                                                       │
                                unregister / destroy ──┘
                                teardown() → remove all hooks
  1. Register: Plugin object passed to PluginManager.register(plugin)
  2. Validate: Check dependencies are already loaded
  3. Load styles: Inject plugin CSS via tag
  4. Setup: Call plugin.setup(ctx) — the plugin registers hooks
  5. Active: Plugin hooks fire during rendering and editing
  6. Teardown: plugin.teardown(ctx) — the plugin cleans up
[5]
// PluginManager
class PluginManager {
  register(plugin: SciNotebookPlugin): void;
  unregister(pluginId: string): void;

  getPlugin(id: string): SciNotebookPlugin | undefined;
  getAll(): ReadonlyArray<SciNotebookPlugin>;
  isRegistered(id: string): boolean;

  // Lazy-load a plugin from a dynamic import
  loadPlugin(loader: () => Promise<SciNotebookPlugin>): Promise<void>;
}
[6]
// Custom Cell Types
interface CustomCellType {
  id: string;           // e.g. 'plotly'
  name: string;         // Human-readable, e.g. 'Plotly Chart'
  icon?: string;        // Toolbar icon
  editor?: CellEditorDescriptor;
  renderer?: CellRenderer;
}

interface CellEditorDescriptor {
  component: string;    // Name or reference to the editor component
  props?: Record<string, unknown>;
}
[7]
// Rendering Hooks
interface PluginRenderingHooks {
  preprocess?: (source: string, cell: Cell) => string;
  postprocess?: (html: string, cell: Cell) => string;
  astTransform?: (tokens: Token[], cell: Cell) => Token[];
  renderer?: CellRenderer;
}

// Toolbar Items
interface PluginToolbarItem {
  id: string;
  label: string;
  icon?: string;
  action: (ctx: PluginContext) => void;
  isVisible?: (ctx: PluginContext) => boolean;
  isActive?: (ctx: PluginContext) => boolean;
  group?: string;       // Grouping in toolbar
  priority?: number;    // Position order
}
[8]
// Inter-Plugin Communication via event bus

// Plugin A emits
ctx.emit("my-plugin:data-ready", { values: [1, 2, 3] });

// Plugin B listens
ctx.on("my-plugin:data-ready", (payload) => {
  console.log(payload.values);
});

// Convention: prefix events with pluginId: to avoid collisions
[9]

Built-in Plugins

Plugin Package Description
sci-nb-latex plugin-latex KaTeX math rendering
sci-nb-mermaid plugin-mermaid Mermaid diagrams
sci-nb-embeds plugin-embeds HTML/iframe/component embeds
sci-nb-images plugin-images Image upload/resize
sci-nb-tables plugin-tables Rich table editing
sci-nb-code-highlight plugin-code-highlight Shiki/Prism syntax highlighting
[10]
// Plugin Development Guide — complete example
export function myPlugin(options?: MyPluginOptions): SciNotebookPlugin {
  return {
    id: "my-plugin",
    name: "My Plugin",
    version: "1.0.0",

    setup(ctx) {
      // Register a postprocessor to highlight TODOs
      ctx.addPostprocessor((html, cell) => {
        return html.replace(/TODO/g, '[TODO]');
      });

      // Listen for cell updates
      ctx.on("cell:updated", ({ cellId }) => {
        ctx.log.info(`Cell ${cellId} was updated`);
      });
    },

    teardown(ctx) {
      ctx.log.info("My Plugin destroyed");
    },
  };
}

// Register it
engine.pluginManager.register(myPlugin({ highlight: true }));

Integrated under the Sci DNA / VeloSci Ecosystem