Documentation
Custom Nodes Registry
Register custom node types with full strict TypeScript support.
One of the most powerful features of PaperCast is its Headless Architecture, which allows you to define completely custom widgets and nodes that integrate seamlessly into the engine. However, when working in a strictly typed TypeScript environment, adding custom nodes to a pre-compiled package like @papercast/core can cause type errors (e.g., TS2339) because the core library doesn't know about your custom node shapes.
To solve this, PaperCast uses TypeScript Module Augmentation via the CustomNodesRegistry interface.
The Problem
The core engine uses a discriminated union called AnyNode for all AST manipulations:
// @papercast/core/src/schema.ts
export type AnyNode =
BuiltInNode | CustomNodesRegistry[keyof CustomNodesRegistry];
export interface CustomNodesRegistry {}
By default, CustomNodesRegistry is empty. If you try to pass a MyChartNode into a core function like insertNodeIntoAst(), TypeScript will throw an error because MyChartNode isn't a BuiltInNode.
The Solution: Module Augmentation
You can inject your custom node types directly into @papercast/core's type system by augmenting the module in your host application.
1. Define Your Custom Node
First, define the strict type for your custom node. It must extend BaseNode and specify a unique type literal.
// apps/web/src/types/customNodes.ts
import { BaseNode } from "@papercast/core";
export interface ChartNode extends BaseNode<"chart"> {
props: {
datasetId: string;
chartType: "bar" | "line" | "pie";
showLegend: boolean;
};
}
2. Augment the Registry
Create a .d.ts declaration file in your project (or add this to your existing types file) to augment the @papercast/core module.
// apps/web/src/types/papercast.d.ts
import { ChartNode } from "./customNodes";
declare module "@papercast/core" {
export interface CustomNodesRegistry {
chart: ChartNode;
}
}
3. Benefit from 100% Type Safety
Once augmented, the AnyNode union automatically expands to include ChartNode.
This means:
node.type === "chart"will correctly narrow the type toChartNode.- Core utilities like
findNodeGlobalandinsertNodeIntoAstwill accept and returnChartNodewithout anyas anycasting. - If you use the Model Context Protocol (MCP) server, it inherits these exact types, meaning LLM agents will have perfect type context for your custom properties.
Zero Tolerance for any
[!WARNING] Strict Typing Rule: Never use
(node as any).propsto bypass type checks for custom nodes. Always use Module Augmentation. This guarantees that your AST mutations are safe and structurally sound.