This document describes the core workflow execution engine in n8n, which is responsible for orchestrating the execution of workflow nodes, managing data flow between nodes, and handling the execution lifecycle. The engine implements a stack-based execution model that processes nodes sequentially while maintaining execution state.
For detailed information about specific aspects of execution:
The workflow execution engine orchestrates workflow execution through multiple layers, from entry point to completion. The WorkflowRunner service is the main entry point, which decides between regular in-process execution and distributed queue-based execution. Once execution begins, the WorkflowExecute class coordinates node scheduling, data routing, and lifecycle management.
The diagram below maps the key code classes to their roles in the execution pipeline.
Workflow Execution Engine ā Class and Data Flow
Sources: packages/cli/src/workflow-runner.ts51-213 packages/cli/src/scaling/scaling.service.ts39-110 packages/core/src/execution-engine/workflow-execute.ts86-187 packages/cli/src/active-executions.ts35-53
n8n supports two execution modes configured via executions.mode:
| Mode | Description | Use Case |
|---|---|---|
regular | In-process execution in the main process | Single-server deployments, development |
queue | Distributed execution via Bull queue and Redis | Multi-worker scaling, high availability |
In regular mode, WorkflowRunner.runMainProcess() directly instantiates WorkflowExecute and runs the workflow in the current process.
In queue mode, WorkflowRunner.enqueueExecution() adds a job to the Bull queue. Worker processes pick up jobs via JobProcessor.processJob(), which then instantiates WorkflowExecute on the worker.
Sources: packages/cli/src/workflow-runner.ts139-188 packages/cli/src/workflow-runner.ts378-545 packages/cli/src/scaling/job-processor.ts71-140
The WorkflowRunner class is the primary entry point for workflow execution. It handles execution mode selection, credential checks, and error recovery.
Key Responsibilities:
ActiveExecutions.add()CredentialsPermissionCheckerprocessError()Key Methods:
Sources: packages/cli/src/workflow-runner.ts51-69 packages/cli/src/workflow-runner.ts139-213 packages/cli/src/workflow-runner.ts217-376 packages/cli/src/workflow-runner.ts72-134
The ActiveExecutions service tracks all currently running executions in the process. It manages execution state, response promises, and provides cancellation capabilities.
Key Responsibilities:
add() and finalizeExecution()Key Methods:
Sources: packages/cli/src/active-executions.ts35-53 packages/cli/src/active-executions.ts62-145 packages/cli/src/active-executions.ts244-290
The WorkflowExecute class is the main orchestrator for workflow execution. It maintains the execution state and processes nodes from the execution stack.
Key Responsibilities:
nodeExecutionStack)processRunExecutionData()addNodeToBeExecuted()Class Structure:
Sources: packages/core/src/execution-engine/workflow-execute.ts86-96 packages/core/src/execution-engine/workflow-execute.ts98-165
The ScalingService manages the Bull queue for distributed execution. It runs on main/webhook processes for job submission and on worker processes for job consumption.
Key Responsibilities:
addJob()Key Methods:
Sources: packages/cli/src/scaling/scaling.service.ts40-56 packages/cli/src/scaling/scaling.service.ts60-109 packages/cli/src/scaling/scaling.service.ts111-137 packages/cli/src/scaling/scaling.service.ts226-249
The JobProcessor runs on worker processes to execute jobs from the queue. It fetches execution data from the database, instantiates WorkflowExecute, and reports results back.
Key Responsibilities:
ExecutionRepositoryWorkflowExecuteKey Methods:
Sources: packages/cli/src/scaling/job-processor.ts55-69 packages/cli/src/scaling/job-processor.ts71-220
Execution contexts provide the runtime environment for node execution. They implement different interfaces depending on the node type:
| Context Class | Interface | Use Case |
|---|---|---|
ExecuteContext | IExecuteFunctions | Standard nodes with execute() method |
SupplyDataContext | ISupplyDataFunctions | AI nodes with supplyData() method |
PollContext | IPollFunctions | Poll trigger nodes |
WebhookContext | IWebhookFunctions | Webhook nodes |
Common Context Features:
getNodeParameter()getInputData()Sources: packages/core/src/execution-engine/node-execution-context/execute-context.ts48-134 packages/core/src/execution-engine/node-execution-context/supply-data-context.ts43-121
The execution stack is an array of IExecuteData objects representing nodes to be executed. The engine processes this stack in order (FIFO for v1, LIFO for v0).
IExecuteData Structure:
Stack Operations:
nodeExecutionStack.push() or .unshift() (depending on execution order).shift()waitingExecution mapSources: packages/workflow/src/interfaces.ts439-446 packages/core/src/execution-engine/workflow-execute.ts394-795
The main execution state container, defined in packages/workflow. It holds three top-level sections:
| Field | Type | Purpose |
|---|---|---|
resultData.runData | IRunData | Execution results keyed by node name |
resultData.pinData | IPinData? | Manually pinned test data |
resultData.error | ExecutionBaseError? | Terminal error if execution failed |
executionData.nodeExecutionStack | IExecuteData[] | Nodes pending execution |
executionData.waitingExecution | IWaitingForExecution | Nodes with partial input data |
executionData.waitingExecutionSource | IWaitingForExecutionSource | Source tracking for waiting nodes |
startData.destinationNode | IDestinationNode? | Target node for partial runs |
startData.runNodeFilter | string[]? | Allowlist of nodes to execute |
waitTill | Date? | Timestamp when a wait node should resume |
See packages/workflow/src/interfaces.ts for the full interface definition.
Execution result for a single node invocation. Each entry in runData[nodeName] is an ITaskData[] (one per loop iteration or trigger emission).
| Field | Type | Purpose |
|---|---|---|
startTime | number | Unix ms when node started |
executionTime | number | Duration in ms |
executionStatus | ExecutionStatus? | success, error, skipped, etc. |
data | ITaskDataConnections? | Output data per connection type |
error | ExecutionBaseError? | Node error if execution failed |
source | ISourceData[] | Which upstream node/output sent data |
metadata | ITaskMetadata? | Sub-run relationships, tool call refs |
hints | NodeExecutionHint[]? | User-visible hints from the node |
Sources: packages/workflow/src/interfaces.ts1550-1700 packages/core/src/execution-engine/workflow-execute.ts296-340
The complete execution flow from trigger to completion, showing both regular and queue mode paths.
End-to-End Execution ā Regular vs Queue Mode
Sources: packages/cli/src/workflow-runner.ts139-213 packages/cli/src/workflow-runner.ts217-376 packages/cli/src/workflow-runner.ts378-545 packages/cli/src/scaling/job-processor.ts71-220 packages/cli/src/active-executions.ts244-290
Once WorkflowExecute is instantiated, the main execution loop in processRunExecutionData() processes nodes from the stack.
WorkflowExecute.processRunExecutionData() ā Inner Loop
Sources: packages/core/src/execution-engine/workflow-execute.ts1282-2200 packages/core/src/execution-engine/requests-response.ts
The engine uses nodeExecutionStack: IExecuteData[] to track pending nodes. The executionOrder workflow setting controls the stack operation:
executionOrder setting | Stack operation | Effect |
|---|---|---|
v1 (default) | push / shift (FIFO) | Breadth-first; siblings execute before children |
v0 (legacy) | unshift / shift (LIFO) | Depth-first; children execute before siblings |
The isLegacyExecutionOrder() method in WorkflowExecute checks workflow.settings.executionOrder !== 'v1' to decide which mode applies.
Example execution order (v1):
Initial stack: [Trigger]
After Trigger: [Node1, Node2] push both children
After Node1: [Node2, Node3] push Node3 at end
After Node2: [Node3, Node4] push Node4 at end
After Node3: [Node4]
After Node4: [] done
Nodes with multiple inputs go into waitingExecution until all upstream data arrives, then are promoted back to nodeExecutionStack by addNodeToBeExecuted().
Sources: packages/core/src/execution-engine/workflow-execute.ts1282-1400 packages/core/src/execution-engine/workflow-execute.ts189-191 packages/core/src/execution-engine/workflow-execute.ts406-417
Most nodes implement the execute() method which is called via ExecuteContext:
Execution Path:
WorkflowExecute.runNode() creates ExecuteContextnode.execute.call(executeContext)this.getInputData()INodeExecutionData[][] (array per output)runData[nodeName]Sources: packages/core/src/execution-engine/workflow-execute.ts1100-1200 packages/core/src/execution-engine/node-execution-context/execute-context.ts48-246
AI nodes (LangChain nodes) use supplyData() to provide objects (tools, models, etc.) rather than data:
Key Differences:
SupplyDataContext instead of ExecuteContextresponse field, not data arraysgetNextRunIndex()Sources: packages/core/src/execution-engine/node-execution-context/supply-data-context.ts43-144 packages/core/src/execution-engine/node-execution-context/utils/get-input-connection-data.ts96-202
Trigger nodes use different contexts and don't run in the normal execution loop:
ITriggerFunctions, emit data via emit()IPollFunctions, check for new data periodicallyIWebhookFunctions, activated via HTTP requestsThese are managed by ActiveWorkflowManager rather than WorkflowExecute. See the Node System section (page 4) for how node types are discovered and registered.
Sources: packages/workflow/src/interfaces.ts1183-1201 packages/core/src/execution-engine/triggers-and-pollers.ts </old_str> <new_str>
Trigger nodes use different contexts and don't run in the normal execution loop:
ITriggerFunctions, emit data via emit()IPollFunctions, check for new data periodicallyIWebhookFunctions, activated via HTTP requestsThese are managed by ActiveWorkflowManager rather than WorkflowExecute. See the Node System section (page 4) for how node types are discovered and registered.
Sources: packages/workflow/src/interfaces.ts1183-1201 packages/core/src/execution-engine/triggers-and-pollers.ts
AI Agent nodes can request tool execution by returning an EngineRequest instead of normal data. This allows agents to dynamically invoke workflow nodes as tools.
Sources: packages/workflow/src/interfaces.ts2800-2850 packages/core/src/execution-engine/requests-response.ts1-20
AI Agent Tool Loop ā EngineRequest/EngineResponse Protocol
Sources: packages/core/src/execution-engine/workflow-execute.ts1900-2100 packages/core/src/execution-engine/requests-response.ts28-200
When WorkflowExecute.runNode() detects an EngineRequest:
isEngineRequest(result) checks if output is a request objectprepareRequestedNodesForExecution() creates IExecuteData for each toolmakeEngineResponse() collects tool results into EngineResponsesubNodeExecutionResults in contextKey Code Paths:
Sources: packages/core/src/execution-engine/workflow-execute.ts1900-2100 packages/core/src/execution-engine/requests-response.ts1-200
Tools can be regular workflow nodes that are invoked by AI agents. The WorkflowToolService wraps workflow execution as a LangChain tool:
The tool handler:
context.executeWorkflow() to run the tool's workflowretryOnFail is enabledsubExecutionId for linkingSources: packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts43-220
All execution results are stored in runData, a map from node names to task data arrays:
Each ITaskData represents one execution of the node (nodes can run multiple times in loops).
When a node has multiple inputs and not all data has arrived, it goes into waitingExecution:
Wait Resolution:
incomingConnectionIsEmpty() checks if all inputs have dataprepareWaitingToExecution() creates waiting entryaddNodeToBeExecuted() adds data to waiting entrywaitingExecution to nodeExecutionStackSources: packages/core/src/execution-engine/workflow-execute.ts337-381 packages/core/src/execution-engine/workflow-execute.ts384-795
Execution metadata is tracked separately and merged at the end:
This allows tracking:
Sources: packages/core/src/execution-engine/workflow-execute.ts296-318
The WorkflowRunner.processError() method handles execution errors and ensures proper cleanup:
Error Handling Logic:
ExecutionNotFoundErrorIRun with error detailsActiveExecutions and mark as failedworkflowExecuteAfter hook for error workflowsSources: packages/cli/src/workflow-runner.ts72-134
Errors during node execution are captured and stored:
Error Types:
NodeOperationError - Node-specific errorsNodeApiError - API request errorsExecutionCancelledError - User cancelledTimeoutExecutionCancelledError - TimeoutManualExecutionCancelledError - Manual cancellationContinue on Fail: If node.continueOnFail === true, execution continues with error stored in task data.
Sources: packages/core/src/execution-engine/workflow-execute.ts938-970 packages/workflow/src/interfaces.ts92-98
Timeouts are enforced in WorkflowRunner.runMainProcess():
The timeout:
executions.maxTimeout configurationActiveExecutions.stopExecution() to cancelSources: packages/cli/src/workflow-runner.ts235-343
Execution can be cancelled via AbortController:
Nodes receive the abort signal via getExecutionCancelSignal() and can check:
abortSignal.aborted - Is execution cancelled?The ActiveExecutions.stopExecution() method cancels running workflows:
Sources: packages/core/src/execution-engine/workflow-execute.ts89-90 packages/core/src/execution-engine/node-execution-context/base-execute-context.ts100-120 packages/cli/src/active-executions.ts244-290
In queue mode, worker errors are reported back to main via job progress messages:
The main process listens for these messages and logs them:
Sources: packages/cli/src/scaling/job-processor.ts139-161 packages/cli/src/scaling/scaling.service.ts398-411
The engine supports multiple execution modes affecting behavior:
| Mode | Description | Use Case |
|---|---|---|
manual | User-triggered in UI | Testing, development |
trigger | Activated by trigger | Production workflows |
webhook | HTTP webhook trigger | API integrations |
integrated | Called from another system | Embedded n8n |
chat | Chat interface execution | AI chat workflows |
internal | Internal system use | Queue workers |
Mode Effects:
manual mode sends data to UI via hooksmanual, webhook, integrated, chatmanual shows detailed errors, trigger logs to databaseSources: packages/workflow/src/execution-context.ts1-50 packages/core/src/execution-engine/node-execution-context/execute-context.ts136-149
Lifecycle hooks allow external systems to observe and react to execution events. The ExecutionLifecycleHooks class extends the base LifecycleHooks from n8n-core with execution-specific functionality.
The ExecutionLifecycleHooks class supports the following hook types:
| Hook Name | When Called | Parameters | Purpose |
|---|---|---|---|
workflowExecuteBefore | Before workflow starts | [Workflow?, IRunExecutionData?] | Initialize workflow context |
workflowExecuteAfter | After workflow completes | [IRun] | Save results, trigger events |
nodeExecuteBefore | Before each node | [nodeName] | Track node start, log |
nodeExecuteAfter | After each node | [nodeName] | Track node completion |
sendChunk | Streaming chunk sent | [StructuredChunk] | Real-time streaming updates |
sendResponse | Response sent | [IExecuteResponsePromiseData] | Webhook response handling |
Sources: packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts28-76
Different execution contexts require different hook configurations. All factory functions are defined in packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts
| Factory Function | Used By | Key Responsibilities |
|---|---|---|
getLifecycleHooksForRegularMain() | WorkflowRunner.runMainProcess() | Save to DB, send UI push, telemetry, error workflows |
getLifecycleHooksForScalingMain() | WorkflowRunner.enqueueExecution() | Minimal ā only workflowExecuteBefore (worker does the rest) |
getLifecycleHooksForScalingWorker() | JobProcessor.processJob() | Save to DB, report job result, MCP broadcast |
getLifecycleHooksForSubExecutions() | executeWorkflow() in workflow-execute-additional-data.ts | Save with parent relationship, external hooks |
Sources: packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts78-287 packages/cli/src/workflow-runner.ts277-294
Hooks are used for:
Sources: packages/cli/src/workflow-runner.ts277-294 packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts1-287
| Class/File | Purpose | Location |
|---|---|---|
WorkflowExecute | Main execution orchestrator | packages/core/src/execution-engine/workflow-execute.ts |
ExecuteContext | Runtime context for execute() nodes | packages/core/src/execution-engine/node-execution-context/execute-context.ts |
SupplyDataContext | Runtime context for supplyData() nodes | packages/core/src/execution-engine/node-execution-context/supply-data-context.ts |
BaseExecuteContext | Shared context functionality | packages/core/src/execution-engine/node-execution-context/base-execute-context.ts |
| Request/Response handling | AI tool execution | packages/core/src/execution-engine/requests-response.ts |
| Partial execution utils | Dirty node detection, graph traversal | packages/core/src/execution-engine/partial-execution-utils/ |
| Interface definitions | Type system | packages/workflow/src/interfaces.ts |
Sources: packages/core/src/execution-engine/workflow-execute.ts1-100 packages/core/src/execution-engine/node-execution-context/
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.