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
- Register: Plugin object passed to
PluginManager.register(plugin) - Validate: Check
dependenciesare already loaded - Load styles: Inject plugin CSS via tag
- Setup: Call
plugin.setup(ctx)— the plugin registers hooks - Active: Plugin hooks fire during rendering and editing
- 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 }));