Nodes: Overview
A node is a single step in a workflow. This page covers the concepts shared by every node — how they're identified, configured, and executed. For the full list of nodes shipped with the package, see Built-in Nodes. To write your own, see Custom Nodes.
Identity
Every node class declares a unique type string and a human-readable label:
LogNode::type(); // '@wf::action.log'
LogNode::label(); // 'Log'
A workflow's nodes array references nodes by this type string — see Workflow Definitions.
Configuration (schema())
A node declares its configuration form declaratively, using the fluent Schema API:
use Qanna\WorkflowEngine\Engine\Schema\Schema;
public static function schema(): array
{
return [
Schema::text('message')->required()->help('Supports {{variables}}'),
Schema::select('level')->options(['info' => 'Info', 'error' => 'Error'])->default('info'),
];
}
This is what the interactive builder reads to prompt for a node's config, and what a workflow's nodes[].config is validated/shaped against. See Custom Nodes for the full field reference.
Categories
Nodes are grouped into categories for organization (used by the interactive builder's "Node category" picker):
action, logic, variables, collections, text, datetime, math, data, files, model. Custom nodes can use any category name — see Custom Nodes § Registering.
Execution
When a node runs, the engine:
- Resolves every
{{ }}expression in itsconfigagainst the current execution context (see Expressions) — your node receives already-resolved values. - Adds
config['_node_id']— the node's own id in the workflow, in case your node needs it (the built-in Log node uses this). - Calls your node's
handle(WorkflowContext $context, array $config): NodeResult.
public function handle(WorkflowContext $context, array $config): NodeResult
{
// ...
return NodeResult::success(['result' => $value]);
}
NodeResult
Every handle() returns a NodeResult, built via one of its named constructors:
| Constructor | Meaning |
|---|---|
NodeResult::success(mixed $output = [], string $branch = 'main') | The node completed. $output becomes {{nodes.<id>.*}} for later steps. $branch selects which outgoing edge to follow next (see Branching). |
NodeResult::fail(string $message) | The node failed. The execution ends with status Failed and this message as errorMessage. |
NodeResult::stop(mixed $output = []) | Ends the workflow intentionally (status Cancelled), as the built-in Stop node does on its "success" outcome. |
NodeResult::suspend(ResumeToken $token) | Pauses the execution until resumed — see Custom Nodes § Suspending and resuming. |
Inspect a result with $result->ok(), ->failed(), ->stopped(), ->suspended(), ->getOutput(), ->getErrorMessage(), ->getBranch().
Retries and timeouts
Any node can be retried on failure or capped with a timeout, by adding a reserved __advanced__ block to its config in the workflow definition:
'config' => [
'message' => 'Hello',
'__advanced__' => [
'max_retries' => 3, // default 0 (no retry)
'retry_delay' => 2, // seconds, default 1
'retry_backoff' => 'exponential', // 'fixed' (default) | 'exponential'
'timeout' => 30, // seconds, default 0 (no timeout)
],
],
This works for every node without any code changes on the node's part. The interactive builder doesn't currently prompt for these — set them directly in the definition if you need them.
Next
- Built-in Nodes — the full reference.
- Custom Nodes — building and registering your own.