surface

Writing custom nodes.

Declare a node's interface with comment directives, implement its run function, and use the supported runtime rather than backend internals.

Written for
Developers authoring or adapting user nodes.
You need
An account with the required node-authoring capability

A user node is Python with comment-based schema directives and a top-level run() function. The directives declare the pins and presentation that Studio discovers. They are not Python decorators.

Design the boundary around the computation

Declared inputs, controls and outputs make a custom node usable.

A node accepts declared inputs and controls, computes its result and returns declared outputs. Keep names, types and output shape consistent with the implementation. Validate the definition before execution and handle missing or invalid input explicitly. A visual preview and a saved schema are different evidence from a tested computation.

The same value, factor and result names are highlighted in declarations, function arguments and the return map. The example inputs 3 and 2 multiply to the result 6 at the node’s interface.

A small deterministic node

# @node title="Scale a Number" category="Math" icon="calculator" preview=true
# @task category="Analyze" subcategory="Data" filter="Math"
# @help Multiply a number by a factor.
# @style minHeight=260 widgetSpacing=60
# @input float value label="Value" default=1.0
# @input float factor label="Factor" default=2.0
# @output float result label="Result"

def run(value, factor):
    return {"result": value * factor}

Save it through the supported user-node editor or authoring tool, place it in a graph, and run a known input. A value of 3 and a factor of 2 should return 6. This example needs no provider key or additional package.

Preserve the contract

Use the declared pin names in the function and output dictionary. Every normal return path must map output names to values. For an actionable failure, raise an appropriate exception or return a useful @error; do not fabricate a successful result when required work failed.

Node discovery requires valid metadata. Keep matching category/filter metadata, help, style, and correct pin declarations. Test after changing the interface; existing graphs can still refer to its earlier pin names.

Use the public runtime

User nodes have a restricted runtime. Built-in source can use privileges and imports that user code does not have, so copying a built-in implementation may need adaptation. Use supported injectables such as files, output, package, log, and progress for the operations they provide. Do not depend on backend modules, direct filesystem access, or internal library paths.

Request helpers by their exact supported parameter names. An undeclared helper name is not injected automatically and can make the function fail to run.

Declare external packages with supported @require metadata. Package approval and runtime policy still apply. Web previews and custom widgets also need the documented node-preview integration rather than unrestricted access to Studio.

Test before publishing

Test valid, empty, and invalid inputs and any failure path. For media, inspect a real produced file; an empty path or placeholder file is not a usable output. Keep keys and private data out of source, diagnostics, and bundled assets.

Separate schema, runtime, and behavior checks

Declare: use single-line comment directives, top-level run(), and output names matching the return mapping. Runtime: use exact supported helper names and managed file representations; dependencies still face policy. Test: verify real values/files and error paths, not just a successful save.

Checks and next steps

Keep each metadata directive on its own line and preserve exact pin names when editing an existing interface. @require declares a dependency; it does not bypass the installation's package allowlist or guarantee that a package can run in the selected environment.

The outer return dictionary maps output names to values. A file-backed object output needs the managed JSON-file representation; returning an arbitrary Python dictionary as that pin's value is not the same contract. Use the supported files helper and current data-type reference for the value. Likewise, allocating an output path does not create valid media by itself.

Review generated-source diagnostics and implementation warnings, then test a small known example and failure cases. Static acceptance does not prove provider availability, media quality, complete implementation, or safe reuse with different inputs. See Node and data reference and Creating nodes with Penny.

Last reviewed against Kaitoi on 24 October 2026. View this page as Markdown

Type at least two characters.