Linkage LogoLinkage Docs

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.json
  • registry.ts / registry.js (must export default or registry)

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.ts

projectId 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 key
  • label (string, optional): UI label override
  • icon (string, optional): icon identifier for your node palette
  • category (string, optional): grouping/category key
  • root (boolean, optional): mark an entry node
  • configSchema (optional): JSON Schema or Zod schema for inspector configuration
  • input / output (object): keyed connections (each includes a schema)
  • outputConnectionType (optional): "grouped" or "distinct"
  • waitForTrigger (optional): if true, node waits for an external trigger
  • triggerTypes (optional): allowed trigger types ("webhook", "scheduled", "manual", "event")
  • validate (optional): custom validation hook run during defineNode

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)],
});

Next

  • Use the registry in your app via React SDK (StandaloneEditor or useBuildNodeTypes)
  • See CLI for upload options