Native Nodes
Canonical Octonode repository documentation.
Native Nodes
Native nodes are Octonode's built-in palette — an n8n-style library of pre-written nodes covering math, logic, flow control, data transforms, text/string manipulation, date & time, and free-form code.
Unlike marketplace plugins (which are installed as self-contained folders),
native nodes are materialized directly into your project: running
octonode add math.add writes a real, fully-editable TypeScript source file
into nodes/ and registers a deterministic CommonJS runtime artifact in
.octonode. You edit nodes/add_1.ts; Octonode rebuilds and executes
nodes/add_1.cjs, so source and runtime remain distinct in either module mode.
Adding a native node
Native nodes and plugins are different: built-ins live in packages/native/src/catalog/;
installable plugins follow plugins.md. To register a new built-in:
- Add one
NativeNodeSpecto the matching catalog file. Declare its palette metadata, input/output JSON Schemas, and a TypeScript template usingtsNodeFilefromcatalog/scaffold.ts. - For a new category file, import its exported spec array into
catalog/index.tsand spread it intoCATALOG. Existing category files are already registered there. - Use
paramsonly for values baked into generated source. A parameterized node is described after generation; a static node uses its catalog signature directly. - Add one runnable test covering materialization and the node's non-trivial behavior. If the node changes Studio rendering, add/update the component story and visual baseline.
- Add the node to the catalog table below, then run
yarn gen,yarn build, and the targeted test (plus the Storybook visual gate for Studio changes).
Generated node source must keep stdout reserved for the IPC envelope; log only to stderr.
Octonode integrations (3 nodes)
These native nodes use the active Octonode API, so the same node works with the
local filesystem store and hosted Cloudflare workspace records. Set
OCTONODE_API_URL when the API is not at http://127.0.0.1:8787/api.
| ID | Operations |
|---|---|
octonode.data-tables | List/get/create/update/delete tables and rows; read-only SQL query |
octonode.variables | List, set, and delete variables |
octonode.metadata | List plugins, native nodes, templates, projects, and workflows |
The Catalog
Math (31 nodes)
The 13 binary operators take two operands a and b via incoming edges and
emit one result. The generated code inlines the operator symbol — no function
call, no indirection.
| ID | Symbol | Description |
|---|---|---|
math.add | + | Addition |
math.subtract | − | Subtraction |
math.multiply | × | Multiplication |
math.divide | ÷ | Division (float) |
math.floor-divide | // | Floor division |
math.modulo | % | Modulo |
math.power | ** | Exponentiation |
math.equals | = | Equality |
math.not-equals | ≠ | Inequality |
math.greater | > | Greater-than |
math.less | < | Less-than |
math.greater-equals | ≥ | Greater-or-equal |
math.less-equals | ≤ | Less-or-equal |
The 10 function nodes are unary (input a) or binary (a, b) and emit one
numeric result:
| ID | Symbol | Description |
|---|---|---|
math.abs | |x| | Absolute value |
math.negate | −x | Negation |
math.round | ≈ | Round to nearest integer (half away from zero) |
math.floor | ⌊x⌋ | Round down |
math.ceil | ⌈x⌉ | Round up |
math.sqrt | √ | Square root |
math.square | x² | Square (a · a) |
math.sign | sgn | Sign (−1, 0, 1) |
math.min | min | Minimum of a, b |
math.max | max | Maximum of a, b |
The 8 advanced nodes work over a list (values) or take params:
| ID | Symbol | Description |
|---|---|---|
math.sum | Σ | Sum a list of numbers |
math.product | ∏ | Multiply a list together |
math.average | x̄ | Arithmetic mean of a list |
math.median | M | Middle value of a list |
math.round-to | ≈n | Round a to params.decimals places |
math.clamp | [ ] | Constrain a to [min, max] |
math.random | ? | Random number in [min, max) |
math.to-fixed | 0.0 | Format a with fixed decimals → string |
Logic (6 nodes)
Boolean combinators and null/empty checks.
| ID | Symbol | Description |
|---|---|---|
logic.and | ∧ | a && b |
logic.or | ∨ | a || b |
logic.not | ¬ | !a |
logic.xor | ⊕ | a XOR b |
logic.coalesce | ?? | First non-null of a, b |
logic.is-empty | ∅ | True for null / "" / [] / {} |
Generated TypeScript excerpt for math.add:
// nodes/add_1.ts (compiled to nodes/add_1.cjs)
const { defineNode, start } = require("@octonode/sdk");
const node = defineNode({
id: "add-1",
inputs: { type: "object", properties: { a: { type: "number" }, b: { type: "number" } } },
outputs: { type: "object", properties: { result: { type: "number" } } },
async run({ inputs }) {
const { a, b } = inputs;
return { result: a + b }; // the + symbol, not a function call
},
});
start(node);Flow (11 nodes)
| ID | Description |
|---|---|
flow.start | Output-only manual trigger: defines the workflow input signature from params.inputs and passes it through |
flow.webhook | Output-only HTTP trigger for active workflows; payload schema comes from params.outputs |
flow.if | Emits { true: { value } } or { false: { value } } — only the taken branch key is present |
flow.switch | Routes value to one of N named output keys based on key (falls back to default); emits only the matching key. Declares each case + a default output port, so octonode validate warns when the default branch is left unwired |
flow.filter | Passes { passed: { value } } or { dropped: { value } } |
flow.merge | Merges two upstream objects a and b into { merged: { ...a, ...b } } |
flow.promise | Joins Promise branches using all, allSettled, any, race, settled, then, catch, or finally semantics |
flow.wait | Delays for delay_ms ms then emits { value } unchanged |
flow.stop-error | Always returns status: "error" with the incoming message — use as a guard |
flow.loop | Map Over Items — applies a 1:1 per-item transform to items (edit the transform in code). Not n8n's batching/sub-branch loop; the DAG engine has no sub-graph execution model |
flow.workflow-ref | Runs another workflow as a single node (workflow-in-workflow). Studio lists existing workflows directly in the node picker and labels the canvas node with its target. A runtime OCTONODE_REF_STACK env guard throws on cycles. Requires a .octonode config file in the project root (file-mode projects only). |
Console (1 node)
| ID | Description |
|---|---|
console.log | Logs message to the run output (stderr, so it never collides with the node IPC channel on stdout) and passes message through unchanged. params.level (log/info/warn/error) prefixes the line |
Data (24 nodes)
| ID | Description |
|---|---|
data.set-fields | Merge params.fields into item |
data.rename-keys | Rename keys in item according to a map |
data.sort | Sort items by params.key, ascending or descending |
data.limit | Keep the first params.count items |
data.remove-duplicates | Deduplicate items by params.key |
data.split-out | Unwrap item.items into a flat items array |
data.aggregate | Collect items[].value into a values array |
data.summarize | Sum/count/min/max/avg items[].field into result |
data.filter-items | Keep items where params.key equals params.value |
data.flatten | Flatten a list of lists by one level |
data.group-by | Group items into buckets keyed by params.key |
data.map-field | Set a constant params.value on every item's params.key |
data.map | Run a synchronous callback for every item |
data.filter | Keep items accepted by a synchronous callback |
data.find / data.find-last | Return explicit { found, value } first/last-match results |
data.find-index / data.find-last-index | Return the matching index or -1 |
data.flat-map | Map items and flatten one level |
data.for-each | Run a side effect and return the resulting items |
data.some / data.every | Test callback predicates |
data.reduce / data.reduce-right | Fold items with optional initial values |
Text (13 nodes)
String manipulation nodes — most take text and emit result.
| ID | Description |
|---|---|
text.uppercase | Convert text to UPPERCASE |
text.lowercase | Convert text to lowercase |
text.trim | Strip leading/trailing whitespace |
text.length | Character count of text → numeric result |
text.replace | Replace every params.find with params.replace |
text.split | Split text on params.separator → items array |
text.join | Join an items array with params.separator → result |
text.concat | Concatenate two strings a + b |
text.substring | Slice text from params.start for params.length |
text.contains | Whether text contains params.search → boolean |
text.template | Fill a {{placeholder}} template from data |
text.regex-extract | First params.pattern match in text |
text.pad | Pad text to params.length with params.char |
Date & Time (5 nodes)
Timestamps are epoch milliseconds — pure integer arithmetic with zero dependencies.
parse follows an explicit UTC contract: an ISO 8601 string with no timezone
offset is interpreted as UTC.
| ID | Symbol | Description |
|---|---|---|
datetime.now | ⏱ | Current time as epoch ms |
datetime.add | +Δ | Add params.ms to a timestamp |
datetime.diff | Δt | a − b between two timestamps (ms) |
datetime.format | 📅 | Format an epoch-ms timestamp as a UTC ISO 8601 string |
datetime.parse | →ms | Parse an ISO 8601 string to epoch ms (no-offset = UTC) |
Code (2 nodes)
| ID | Description |
|---|---|
code.custom | Free-form node — provide a function body via --params '{"body":"..."}' |
code.source-function | Compiled whole-function adapter used when source contains runtime syntax outside the semantic graph grammar |
The body receives inputs and must return an outputs object:
octonode add code.custom --params '{"body":"return { doubled: inputs.n * 2 };"}'For the full TypeScript Handbook/JavaScript runtime matrix and JSON boundary, see TypeScript and JavaScript coverage.
CLI
# Add a node with defaults
octonode add math.add
# Specify language and workflow
octonode add flow.if --lang typescript --workflow main
# Pass params (e.g. switch cases, or a code body)
octonode add flow.switch --params '{"cases":["high","med","low"]}'
octonode add code.custom --params '{"body":"return { out: inputs.value * 2 };"}'
# List the full catalog
octonode add --listInstance IDs are auto-incremented: the first math.add becomes add-1, the
second add-2, and so on. Collision detection is safe across concurrent adds.
defaults.language
Set a project-wide default language so --lang can be omitted:
# .octonode.yaml
defaults:
language: typescriptAPI Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/native-nodes | List catalog metadata (templates stripped) |
POST | /api/native-nodes/:id/materialize | Materialize a node; body: { language?, workflowId?, params?, position? } |
POST | /api/webhooks/:workflowId | Trigger an active workflow whose root is flow.webhook (SSE run stream) |
GET | /api/nodes/:id/source | Read a node's source file ({ path, language, content, mtime }) |
PUT | /api/nodes/:id/source | Write a node's source; body: { content, baseMtime } — returns { ok: true } or { ok: false, conflict: true, content } on 409 |
GET | /api/nodes/:id/source/watch | SSE stream of source-file change events for live editing |
Branching Semantics
An edge from: { node, output: K } fires only when the node's outputs contain
key K. A non-root node whose incoming edges all resolved inactive is skipped
with reason no-active-branch; the skip propagates, but a fan-in node with at
least one active incoming edge still runs.
This means flow.if (which emits either { true: ... } or { false: ... })
automatically prunes the untaken branch without any special-casing in the
scheduler. The taken branch runs; the other is skipped with
reason: "no-active-branch". Branch-skips do not count as failures and do not
affect the run's status.
Studio
In edit mode the left palette shows all catalog entries grouped by category (Math / Flow / Data / Code). Drag a node onto the canvas or click Add to materialize it into the project and place it visually.
Existing workflows appear under Workflows as nodes in the node picker. Selecting one
materializes flow.workflow-ref with its id, and the canvas renders a distinct workflow-call
card so nested orchestration remains visible without opening generated code.
Math nodes display their operator symbol (e.g. +, ÷) as the node glyph on the canvas.
The live code panel opens when a native node is selected. It:
- Shows the generated source file in a read-only-by-default editor.
- Watches for external changes via the SSE
/source/watchendpoint and refreshes automatically. - Displays a conflict banner when a write is rejected due to a concurrent
edit (stale
baseMtime).