Custom nodes

What a custom node is made of — INode, INodeDescription, INodeContext and INodeResult — the project layout, and the two-step build that produces the manifest.

A node is a class implementing the SDK's INode interface. Two members are required: a description that declares what the node is, and an execute that does the work.

The deep authoring detail — worked examples of a transform node, a branching control node and an action node with an error port — is the Integration Node SDK. This page is the map: the four types you implement against, what the runtime gives you, and how the package is built.

The four types

TypeWhat it is
INodeThe node itself: a description, an execute, and up to three optional author-time hooks.
INodeDescriptionThe declaration: slug, version, category, name and description, inputs, output ports, and configuration fields.
INodeContextWhat the runtime hands execute: an AbortSignal, a logger, and resolvers for secrets and credentials.
INodeResultWhat execute returns: the outputs, and — for a branching node — which branch fired.

INodeDescription is where most of the design work happens, because it is what the editor renders. The category decides where the node sits in the palette, the output ports decide what an author can wire, and the configuration fields decide what they can fill in. See Node configuration for the field-type grammar the editor understands, and the node catalog for how the shipped nodes use it.

What the runtime hands you

INodeContext is deliberately small:

  • signal — an AbortSignal. Propagate it to every I/O call you make. A cancelled or terminated run, and a node that hit its timeoutSeconds, both come through this signal; a node that ignores it keeps working after the run has stopped caring.
  • loggerinfo, warn and error. What you log here is what an author reads in the node's test panel and in a step's detail, so log the things that explain a failure: the URL you called, the number of rows you read, the field that was missing.
  • secrets.get(key) — resolves a secret referenced by one of your secret-ref fields.
  • credentials.get(id) — resolves the credential instance chosen in one of your credentials-ref fields into live access data, including a freshly minted token where the credential type authenticates with OAuth.

Resolve access data inside execute, every time. Do not cache it across executions: a token resolved on a first attempt may be expired by a retry, and the whole point of resolving late is that a retried step gets current material.

Return an INodeResult. For a node with branch ports, name the branch that fired; for an action with an error port, that port is how an expected failure leaves the node — see Failure handling.

Project layout

A node package is an ordinary TypeScript package. The shape that works:

Layout
my-nodes/
  package.json
  tsup.config.ts
  src/
    index.ts            exports NODES, and optionally CREDENTIALS and TEMPLATES
    nodes/
      greet/GreetNode.ts
    credentials/
      MyApiCredential.ts
    templates/
      myTemplate.ts

One class per node, one file per class, and a single index.ts that exports the arrays the build reads. Keep the description and the execute in the same file — an author debugging a node's behaviour wants to read both at once.

The build

Two steps, in this order:

package.json
{
  "scripts": {
    "build": "tsup && rvnxx-nodes manifest"
  }
}

tsup compiles the package. Then the SDK's CLI, rvnxx-nodes, runs its manifest subcommand: it imports your built entry point, reads the exported arrays, and writes the manifest the registry consumes.

manifest is the CLI's only subcommand. There is deliberately no publish — packages are not published from their own repo. See Registration.

Run the build before you hand anything over. A missing manifest is the most common reason a package cannot be registered, and the CLI fails loudly if the compiled entry point is not there or does not export what it expects.

Where to go next

Was this page helpful?