Node Registry
Define and upload node schemas for Linkage to render in your editor
Linkage uses a node registry (a JSON-serializable file) to understand what nodes exist in your product and how to render their inputs/outputs. This is how you get typed input controls, control blocks, and consistent node UX without hard-coding UI for every node.
Registry shape
A registry is:
version: a version string (semver recommended)nodes: an array of node definitions (name/type + typed input/output connections)
The CLI accepts either:
registry.jsonregistry.ts/registry.js(must exportdefaultorregistry)
Example (registry.ts)
Prefer the SDK helpers so schemas are serialized and validated automatically.
import { defineNode, defineNodeRegistry } from "@linkage-open/lib";
import { z } from "zod";
export const registry = defineNodeRegistry({
version: "1.0.0",
nodes: [
defineNode({
name: "Example Node",
type: "example",
root: true,
input: {
customerId: {
schema: z.string(),
reduce: false,
userInput: true,
},
},
output: {
greeting: {
schema: z.string(),
reduce: false,
},
},
}),
],
});
export default registry;defineNode serializes Zod schemas to JSON, checks required schema fields, and validates defaultValue when provided.
Uploading
Upload the registry to Linkage so the editor can fetch node definitions:
linkage config set-api-key
linkage upload ./registry.tsprojectId is read from pz.json (generated by linkage init). You can also pass it explicitly:
linkage upload ./registry.ts --project-id <projectId>Node fields
Each node supports:
name(string): display name (and stable identifier)type(string): category/type keylabel(string, optional): UI label overrideicon(string, optional): icon identifier for your node palettecategory(string, optional): grouping/category keyroot(boolean, optional): mark an entry nodeconfigSchema(optional): JSON Schema or Zod schema for inspector configurationinput/output(object): keyed connections (each includes aschema)outputConnectionType(optional):"grouped"or"distinct"waitForTrigger(optional): if true, node waits for an external triggertriggerTypes(optional): allowed trigger types ("webhook","scheduled","manual","event")validate(optional): custom validation hook run duringdefineNode
Dynamic ports
If a node's ports depend on config, use resolveNodePorts before calling defineNode.
import {
defineNode,
defineNodeRegistry,
resolveNodePorts,
} from "@linkage-open/lib";
import { z } from "zod";
const switchNode = resolveNodePorts(
{
name: "Switch",
type: "control",
input: {
value: { schema: z.string(), reduce: false },
},
output: {},
},
{ branches: 2 },
(config) => ({
output: Object.fromEntries(
Array.from(
{ length: Number((config as { branches?: number }).branches ?? 1) },
(_, index) => [
`branch_${index + 1}`,
{ schema: z.string(), reduce: false },
],
),
),
}),
);
export default defineNodeRegistry({
version: "1.0.0",
nodes: [defineNode(switchNode)],
});