Add a render plugin to your site
Site render plugins are declared on a normal Curvenote plugin (the same kind of module you use for custom directives and transforms). You list the plugin from your project config, then list each renderer module under renderers.
1. Point curvenote.yml at your plugin¶
project:
plugins:
- ./plugins/node-renderers.mjs2. Export directives and renderers¶
A minimal plugin pairs a custom Curvenote directive with a renderer module. The directive emits an AST node whose type matches a key in the renderer’s map:
const fancyNoteDirective = {
name: 'fancy-note',
arg: { type: String, doc: 'Optional title.' },
body: { type: 'myst', doc: 'Note body.' },
run(data) {
return [
{
type: 'fancyNote',
title: typeof data.arg === 'string' ? data.arg : 'Fancy note',
children: data.body || [],
},
];
},
};
const plugin = {
name: 'My site renderers',
directives: [fancyNoteDirective],
renderers: [
{
name: 'FancyNote',
doc: 'Renders fancyNote AST nodes.',
source: '../modules/fancy-note.mjs',
},
],
};
export default plugin;source is resolved relative to the plugin file. Supported entrypoints:
| Extension | What the CLI does |
|---|---|
.mjs / .js | Copies a hashed file into the site public/ folder |
.tsx / .jsx / .ts | Bundles with esbuild (React left external), then copies the result |
See Packaging formats for when to use each style.
3. Export an ASTRenderer (or a plain map)¶
Prefer an ASTRenderer factory. The theme calls it with the host React instance and an ASTComponent you can use to render nested AST nodes. In a vanilla .mjs module, build the tree with React.createElement (no JSX transform). If you prefer JSX, use pre-bundled ESM or CLI-bundled TSX instead.
export default function ASTRenderer({ React, ASTComponent }) {
const { createElement: h } = React;
function FancyNote({ node }) {
return h(
'aside',
null,
h('div', null, node.title ?? 'Fancy note'),
h(ASTComponent, { ast: node.children }),
);
}
return {
fancyNote: FancyNote,
};
}You can also export a plain NodeRenderers map as the default export. The factory form is better whenever you need nested content or hooks from the theme’s React.
At render time, each component receives { node, className } — the same contract as built-in theme renderers.
4. Build or start¶
curvenote start, curvenote build, and deploy paths emit site renderers into the site manifest as config.renderers: [{ name, url }]. The theme dynamic-imports those URLs and merges them after its defaults.
During curvenote start, TSX sources are watched and rebundled when they change.
For a full working layout (plugin file, modules, TSX sources, and demos), see the example-renderers companion project used in Curvenote development.
Next, choose how to package your module, or jump to override and extend patterns.
- AST
- Abstract Syntax Tree
- CLI
- Command Line Interface
- URL
- Uniform Resource Locator