Skip to main content

Number

Per-instance numeric holder: stores one number locally, optionally latching new values until a Set signal fires.

Number holds a single numeric value that belongs to this node instance alone — it is not named or shared (for an app-wide named value use Variable2). The value input sets it and savedValue (Value) reads it back. While the saveValue (Set) signal input is unconnected, values written to value apply immediately; once saveValue is connected the node latches: incoming values are held pending and only applied when Set fires. Incoming values are cast with JavaScript Number(), so non-numeric strings become NaN. changed fires only when the stored value actually changes; stored fires after every Set trigger.

When to use it​

Use it to hold a local number — a counter, an index, a captured measurement — especially with the latch: feed a computed value into value and commit it with Set at the moment of an action. For simple increment/decrement a Counter is less wiring; for app-wide numbers use Variable2 + Set Variable.

At a glance​

CategoryVariables
Type nameNumber
Available inbrowser, cloud
SSR compatibilitysafe
Provided bynoodl-runtime
Behaviour specversion 2 (under the hood)

Inputs​

Values​

NameTypeDefaultDescription
runOnChange-valueBooleantrueWhether a new value on Value re-runs this node. On by default; untick to make this input passive so only the control signal runs it
treatEmptyAsEnum (null, zero)nullBack-compat for graphs written before Variables were nullable. null (default) keeps a cleared value distinguishable from a real 0. Choosing another option restores the pre-NDA-003 coercion for authors who relied on it.
treatUnchangedAsEnum (unchanged, done)unchangedWhat this node reports when the action was valid and there was nothing to do. Unchanged (the default) keeps it a third outcome of its own. Done suits a project whose chains should carry on either way. Completed fires whatever this is set to.
valueNumber0The empty-value contract (dev-docs/reference/EMPTY-VALUE-CONTRACT.md): undefined abstains and leaves the Variable's stored value untouched. null is a real value — it clears the Variable, is stored, and fires Changed. What "cleared" is stored as is controlled by Treat empty as (null by default).

Signals​

NameTypeDefaultDescription
saveValueSignal—Stores the latest value now. This is additional to Value storing on change; untick Value under Run On Value Change to stop that

What each input does​

Value (value)

  • undefined: ignored → Changed stays silent
  • the same as the last one, or Run On Value Change unticked: held for the next Set → Changed stays silent
  • a new value that converts to what is already stored: held, nothing changes → Changed stays silent
  • a new value, with Run On Value Change ticked: stored straight away (an empty one as Treat Empty As says) → Changed

Set (saveValue)

  • always: at the end of the frame the last Value is stored — Changed and Done if that changed it, otherwise Unchanged (or what Treat Unchanged As says)

Outputs​

Values​

NameTypeDefaultDescription
savedValueNumber—Can be null — a cleared Variable (see value's description) stores and emits null by default, not this type's zero value, unless Treat empty as says otherwise.

Signals​

NameTypeDefaultDescription
changedSignal—Fires whenever the stored value actually changed, however it was reached — including Value writing straight through under Run On Value Change. It is a value-level event, not the outcome of a Set
completedSignal—Fires after every invocation, whatever the outcome — wire this to carry on regardless. Failure still fires and still carries its reason, so this cannot hide an error
doneSignal—Fires when a Set stored a value the Variable was not already holding
unchangedSignal—Fires when a Set stored the value it already held, so nothing changed. ⚠️ With Value left ticked under Run On Value Change this is the common case, because Value has already stored by the time Set fires

Patterns​

  • Counter loop: savedValue → an Expression (count + 1) → value, with a click signal on saveValue — each click commits the incremented value. The latch prevents an infinite feedback loop.
  • savedValue → a Text's text: numbers cast to strings on delivery, so the count renders directly.

Watch out for​

  • Wiring savedValue through an Expression back into value without connecting saveValue — with no latch the loop applies immediately and the value is unstable. Connect Set and drive it from a signal.

Examples​

Click counter with a latched Number and a Boolean flag

The canonical per-instance counter: a Number node's savedValue feeds an Expression (count + 1) whose result loops back into the Number's value input. Because saveValue (Set) is connected, the incremented value is only committed when the Button's onClick fires — the latch is what makes the feedback loop safe. The count renders directly in a Text (number casts to string on delivery). A Boolean node shows the latch-a-signal-into-a-level shape: its value is pinned to true in parameters, the same click fires its Set, and its savedValue permanently reveals the hint text — a level, unlike the momentary click signal.

Counter, Expression, Number Remapper, Boolean, String, Variable

Code export​

Exports to React. A project that uses it exports this node as React code.

How export works shows what an export writes, with a worked example.

Under the hood​

This node's behaviour is written down as an executable spec in @nodegx/node-spec, version 2. The spec is the reference the conformance tests grade the runtime against. Every rule in it cites the line of the runtime it was read from. The full spec is at the bottom of this page.

What it depends on outside the node​

Nothing. Its outputs depend only on its inputs and its own state.

Starting state​

{
"current": 0,
"hasBeenSet": false,
"sets": 0
}

How inputs are converted​

Inputs not listed arrive as sent.

InputConversionWhat it does
Value (runOnChange-value)not-falseonly an explicit false turns it off; no value, 0 and "" leave it on

How actions report back​

Each time Set is triggered, the node reports exactly one of Done or Unchanged.

The code it replaces​

What you would write by hand instead:

  • a number held in state, set from elsewhere: const [price, setPrice] = useState(0)

The full spec​

The spec's source, as the docs were generated. Helpers it imports live beside it in packages/nodegx-node-spec/src.

packages/nodegx-node-spec/src/nodes/number.ts
/**
* Number — read from `packages/noodl-runtime/src/nodes/std-library/variables/number.ts` on
* 2026-09-30 (NSP-011). The family's rules are in variable-base.ts; this file is the four lines
* number.ts supplies.
*/

import { defineNode } from '../spec';
import { afterSets, CHANGED, onSet, onValue, SAVED_VALUE_DESCRIPTION, VARIABLE_OUTCOMES, variableInputs, variableState, type VariableArgs } from './variable-base';

/** :7-30 — startValue 0 (:10); `cast` is `Number(value)` (:22-24), NaN handled by the base (:17-21); empty options null / 0 (:25-28) */
export const NUMBER: VariableArgs & { type: 'number' } = {
type: 'number',
startValue: 0,
cast: (value) => Number(value),
emptyOptions: [
{ value: 'null', label: 'Null (default)', coerce: null },
{ value: 'zero', label: 'Zero (0)', coerce: 0 }
]
};

export const NumberVariable = defineNode({
type: 'Number',
// v2 (P109 ISL-027, row C5 ruled "Start with no remembered value"): a first Value of 0 is stored and fires
// Changed like any other value, and a Set before any Value reports Unchanged. v1 ignored a first 0 (it equalled
// a hidden seed of 0) and a Set before any Value stored that seed and reported Done.
version: 2,
source: 'packages/noodl-runtime/src/nodes/std-library/variables/number.ts',
// NSP-018 — the hand-written code this node replaces (read by nothing yet; `spec-ledger` counts it)
idioms: [
{ says: 'a number held in state, set from elsewhere', example: 'const [price, setPrice] = useState(0)' }
],
state: variableState(NUMBER),
inspect: (s) => String(s.current),
outcomes: VARIABLE_OUTCOMES,
inputs: variableInputs(NUMBER),
outputs: {
savedValue: { type: 'number', from: (s) => s.current, displayName: 'Value', group: 'Values', description: SAVED_VALUE_DESCRIPTION },
changed: CHANGED
}
}).on(
{ value: (s, v, i) => onValue(NUMBER, s, v, i), saveValue: onSet },
{ afterInputs: (s, i) => afterSets(NUMBER, s, i) }
);
Generated

This page is generated from node-catalog-enriched.json, the node's spec in packages/nodegx-node-spec and the export ledger. Do not edit it by hand — run npm run docs:nodes to regenerate, and fix the source instead.