Configuration and outputs
Action nodes perform operations in your workflow. They execute after trigger nodes and can be chained together to create complex automation flows.
Common configuration
All action nodes share these common fields:
| Field | Description |
|---|---|
identifier | Unique identifier for the node within the workflow |
title | Display name for the node |
icon | Optional icon for the node |
description | Optional description of what the node does |
config | The action configuration (type-specific) |
variables | Optional key-value pairs for reusable expressions |
links | Optional array of URL templates (max 3) evaluated when the node run completes. See Attaching links to node runs. |
verbose | When true, enables verbose logging for the node. Default: false |
timeoutMinutes | Optional deadline for each run of the node, in minutes (60 to 43200, whole numbers). When empty, the node type's default applies. Not available on triggers or EXECUTE_WORKFLOW nodes. See Node timeout. |
Attaching links to node runs
The links field lets you attach up to 3 URLs to a node run. The URLs are evaluated when the node run completes and are shown in the node details panel of the workflow run page.
Each entry in links is a URL string that can include template expressions using the {{ }} syntax. The available context variables are:
| Variable | Description |
|---|---|
.result | The node's output object |
.outputs | Outputs from all previous nodes, keyed by node identifier |
.workflow | Workflow-level variables |
.workflowRun | Workflow run metadata |
.workflowNodeRun | Current node run metadata (e.g. identifier, createdAt, url) |
.port | Port app metadata (e.g. appUrl) |
See workflow run context for the full list of fields.
If a template expression fails to evaluate or produces an invalid URL, that link is skipped and a warning is written to the node's logs.
Example - link to the resource created by a node:
{
"identifier": "create_service",
"title": "Create Service",
"links": [
"https://app.example.com/services/{{ .result.serviceId }}",
"https://ci.example.com/pipelines/{{ .result.pipelineId }}"
],
"config": {
"type": "WEBHOOK",
"url": "https://api.example.com/services",
"method": "POST",
"body": {
"name": "{{ .outputs.trigger.serviceName }}"
}
}
}
When the node run completes, the expressions are evaluated against the node's output. The resulting URLs appear in the node details panel for that run.
Links can also be attached dynamically when reporting a node run result via the API - see Updating node runs.
On failure
Action nodes that execute operations (WEBHOOK, KAFKA, UPSERT_ENTITY, INTEGRATION_ACTION) support an onFailure configuration option:
| Field | Type | Default | Description |
|---|---|---|---|
onFailure | 'continue' | 'terminate' | 'terminate' | When set to 'continue', the workflow continues to the next nodes even if this node fails. When set to 'terminate', the workflow run is marked as failed and no subsequent nodes are executed. |
By default, when an action node fails, the entire workflow run is marked as failed and no subsequent nodes are executed. Setting onFailure: 'continue' allows the workflow to proceed despite the failure.
The failed node is still marked with a FAILED result. The onFailure option only affects whether subsequent nodes are executed.
Example:
{
"identifier": "optional-notification",
"title": "Send Optional Notification",
"config": {
"type": "WEBHOOK",
"url": "https://hooks.slack.com/services/xxx",
"method": "POST",
"onFailure": "continue",
"body": {
"text": "Deployment started for {{ .outputs.trigger.service }}"
}
}
}
In this example, even if the Slack notification fails, the workflow will continue to execute subsequent nodes.
When a node fails, its output includes an error object with a message field describing the failure. Downstream nodes can reference it (for example when using onFailure: 'continue') to handle or log the error:
| Field | Description |
|---|---|
output.error.message | Human-readable error message describing why the node failed |
Example output from a failed node:
{
"error": {
"message": "Request failed: connection refused"
}
}
Node timeout
Every node run except triggers has a deadline. If a node run is still open (IN_PROGRESS, or WAITING_FOR_INPUT for input nodes) when the deadline passes, Port concludes it as timed out, so a node that never reports a result cannot hold its workflow run open forever.
The deadline is a backstop: it applies only while the node run is still open. When the node's normal completion path already concludes the run - for example a response, a failure, an input submission, or a webhook HTTP timeout, the deadline has no effect. Use it to bound how long a run may wait on an external callback, a human response, or any other path that might never finish.
Set the deadline with the optional timeoutMinutes field in the node config. Triggers do not have a timeout, and EXECUTE_WORKFLOW nodes have no timeoutMinutes field (they use a fixed 24-hour deadline). In the visual editor, use the Timeout (minutes) field in the node form. When it is empty, the form shows the default for the node type.
| Limit | Value |
|---|---|
| Minimum | 60 minutes (1 hour) |
| Maximum | 43200 minutes (30 days) |
The value must be a whole number of minutes. Values outside this range are rejected when you save the workflow.
Defaults
When timeoutMinutes is not set, the deadline depends on the node type:
| Node type | Default timeout |
|---|---|
CONDITION, UPSERT_ENTITY, KAFKA | 1 hour |
WEBHOOK - synchronized (synchronized: true, not an agent) | 1 hour |
WEBHOOK - asynchronous (synchronized: false) | 24 hours |
WEBHOOK - agent (agent is set) | 24 hours |
INTEGRATION_ACTION (other than a Cursor agent), AI, AI_AGENT | 24 hours |
INTEGRATION_ACTION - Cursor agent (any action of the cursor-cloud-agents provider) | 72 hours |
CURSOR_AGENT | 72 hours |
INPUT | 1 week |
EXECUTE_WORKFLOW (fixed, not configurable) | 24 hours |
The deadline is calculated when the node run is created, as the creation time plus the node's timeoutMinutes (or the default), and it never changes afterwards. Editing a workflow while a run is in flight does not move the deadline of node runs that already exist, and later node runs of that run use the timeoutMinutes of the workflow version the run started with.
Node runs created before this field was introduced have no deadline.
What happens on timeout
When the deadline passes and the node run is still open, Port writes the following:
- The node run completes with a
FAILEDresult (orCANCELLEDif the workflow run is being cancelled). - The output is
{ "timeout": true }. - The node run shows the Timed out label.
- A log entry is added:
Node timed out after N minutes.
A synchronized webhook with onTimeout: continue also writes { "timeout": true } when its HTTP request times out, but with a SUCCESS result. Check the node run result to tell the two cases apart.
The node is not retried. Because the result is FAILED, what happens next depends on the node type:
WEBHOOK,KAFKA,INTEGRATION_ACTIONandUPSERT_ENTITYnodes follow theironFailuresetting: withterminatethe workflow run fails, and withcontinuethe workflow moves on to the next nodes.AI,AI_AGENT,CURSOR_AGENT,CONDITIONandEXECUTE_WORKFLOWnodes have noonFailuresetting, so the workflow run fails.- Input nodes continue through their On timeout connection if one is set, and the workflow run fails otherwise.
If the node run has already concluded when the deadline passes, nothing changes. A result reported for a node run after it has concluded (including after a timeout) is rejected with a 409 node_run_not_in_progress error and does not overwrite the stored output.
timeoutMinutes is the backstop deadline for the whole node run. It is separate from the fixed HTTP request timeouts of webhook nodes (10 seconds when synchronized, 5 seconds for the initial request when asynchronous) and their onTimeout setting. For synchronized webhooks the HTTP timeout usually concludes the run within seconds, so the node deadline rarely matters; for asynchronous webhooks it bounds how long the node may wait for the external callback after the initial request.
Example
In this example, the asynchronous webhook waits up to 2 hours for the external service to report back. If the deadline passes first, the node run times out and the workflow continues because onFailure is set to continue:
{
"identifier": "provision-environment",
"title": "Provision environment",
"config": {
"type": "WEBHOOK",
"url": "https://api.example.com/provision",
"method": "POST",
"synchronized": false,
"timeoutMinutes": 120,
"onFailure": "continue"
}
}
Referencing outputs
Action nodes can reference outputs from previous nodes using bracket notation .outputs["<node_identifier>"].<field>:
| Context | Description |
|---|---|
.outputs["<node_identifier>"].<field> | Output from any previous node (including trigger) |
.secrets["<name>"] | Organization secrets |
For self-service triggers, the user inputs are stored directly at .outputs["<trigger-node-identifier>"].<input_key>.
For event triggers, the event data is stored at .outputs["<trigger-node-identifier>"].diff.after, .outputs["<trigger-node-identifier>"].action, etc.
Example
Chaining node outputs
This example demonstrates how to pass data between nodes. A self-service trigger collects a resource name from the user, passes it to a webhook that creates the resource, and then sends a Slack notification with the newly created resource ID.
Chaining node outputs (click to expand)
{
"nodes": [
{
"identifier": "trigger",
"title": "Start",
"config": {
"type": "SELF_SERVE_TRIGGER",
"userInputs": {
"properties": {
"resourceName": { "type": "string" }
}
}
}
},
{
"identifier": "create_resource",
"title": "Create Resource",
"config": {
"type": "WEBHOOK",
"url": "https://api.example.com/resources",
"method": "POST",
"body": {
"name": "{{ .outputs.trigger.resourceName }}"
}
}
},
{
"identifier": "notify",
"title": "Send Notification",
"config": {
"type": "WEBHOOK",
"url": "https://hooks.slack.com/xxx",
"method": "POST",
"body": {
"text": "Created resource with ID: {{ .outputs[\"create_resource\"].response.data.resourceId }}"
}
}
}
],
"connections": [
{
"sourceIdentifier": "trigger",
"targetIdentifier": "create_resource"
},
{
"sourceIdentifier": "create_resource",
"targetIdentifier": "notify"
}
]
}