Render Note Execution#
A render note (type render) displays output generated by one or more linked script notes, connected via the renderNote relation. Unlike the standard text-rendering pipeline (CKEditor post-processing, reference-link resolution, mermaid, etc.), render notes bypass content parsing entirely: the server compiles a bundle of script + HTML, the client injects the HTML directly into the DOM, and executes the script asynchronously.
The full pipeline spans three layers:
Render.tsx widget
└─ render.tsx service → POST script/bundle/:noteId → server getScriptBundleForFrontend
├─ append bundle.html to DOM
└─ executeBundleWithoutErrorHandling (async, fire-and-forget)
└─ eval(bundle.script) inside ScriptContext
└─ [optional] renderIfJsx → Preact component mount
Entry Point: Render Widget#
Render.tsx is the Preact type widget for render notes. It dispatches to one of three sub-components based on the note's renderNote/disabled:renderNote relations :
RenderContent— note has arenderNoterelation; performs the actual render.SetupRenderContent— no relation yet; shows a setup UI to pick or create a script note.DisabledRender— relation is explicitly disabled; shows a re-enable button.
RenderContent holds a contentRef div and triggers a refresh() via useEffect whenever the note changes. It also re-renders in response to Trilium events: renderActiveNote (keyboard shortcut), refreshData (floating buttons), and entitiesReloaded (attribute changes on the renderNote relation) .
The widget is registered as the type widget for render notes in note_types.tsx .
Bundle Fetch#
render() in render.tsx drives the fetch loop:
- Collects all
renderNoterelation values (note IDs) from the note . - Clears the container and hides it if no render note IDs exist .
- For each render note ID, POSTs to
script/bundle/:renderNoteIdviaserver.postWithSilentInternalServerError<Bundle>(). A missing bundle (e.g., a protected note without an active session) throws immediately with a descriptive message .
The server-side handler getBundle in script.ts calls getScriptBundleForFrontend() , which returns a Bundle with:
| Field | Contents |
|---|---|
script | Combined JS/JSX from the render note and its children; each note wrapped in an IIFE with exports, module, require, and api |
html | Concatenated raw HTML from any HTML child notes |
noteId | Root script note ID |
allNoteIds | All note IDs included in the bundle |
JSX notes are transpiled server-side (Sucrase) before inclusion. The note / allNotes objects are stripped before transmission; the frontend uses its own FNote instances instead .
DOM Injection#
For each bundle, render() creates a fresh <div> container, appends it to the widget element, then injects bundle.html directly — no sanitization, as the HTML is authored by the user . Multiple renderNote relations produce multiple sibling containers in the order the relations are defined.
Asynchronous Script Execution#
Script execution is deliberately fire-and-forget — the promise from executeBundleWithoutErrorHandling() is not awaited so scripts cannot block the Trilium UI . In bundle.ts:
- Builds a
ScriptContextobject containingmodules,notes(keyed by ID),apis(aFrontendScriptApiinstance per note), and arequire()resolver for loading child notes as modules. - Runs
eval()of the script as anasyncIIFE with the context bound asthis.
The $container jQuery element is passed through to the context so scripts can write into their own DOM subtree.
JSX Component Path#
After execution, if bundle.html is empty (no HTML child notes), renderIfJsx() runs:
- Confirms the root note MIME is
text/jsx; silently returns otherwise . - Confirms the execution result is a function (a Preact component) .
- Wraps the component in a
UserErrorBoundaryand callsrenderReactWidgetAtElement()to mount it at the container element .
Error Handling#
- Execution errors are caught by the
.catch()inrender()and forwarded toonError, whichRenderContentuses to set an error state — displaying aRenderErrorCardin place of the content . - JSON-shaped error strings from the server are parsed before forwarding .
- When render notes are displayed via
getRenderedContent()incontent_renderer.ts(e.g., in previews),showRenderError()lazily imports and mountsRenderErrorCardas a fallback.
Key Source Files#
| File | Role |
|---|---|
render.tsx | Core service: fetch loop, DOM injection, async execution, JSX path |
Render.tsx | Preact type widget: lifecycle, event wiring, error state |
bundle.ts | Bundle type, executeBundleWithoutErrorHandling, executeBundle |
script_context.ts | Constructs the ScriptContext API object passed to eval |
content_renderer.ts | Dispatches to renderService.render() for type render notes |
script.ts (server) | getBundle endpoint handler |
script.ts (server service) | getScriptBundleForFrontend: compiles and returns the bundle |