> For the complete documentation index, see llms.txt.
Skip to main content

Check out Port for yourself ➜ 

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:

FieldDescription
identifierUnique identifier for the node within the workflow
titleDisplay name for the node
iconOptional icon for the node
descriptionOptional description of what the node does
configThe action configuration (type-specific)
variablesOptional key-value pairs for reusable expressions
linksOptional array of URL templates (max 3) evaluated when the node run completes. See Attaching links to node runs.
verboseWhen true, enables verbose logging for the node. Default: false
timeoutMinutesOptional 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.

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:

VariableDescription
.resultThe node's output object
.outputsOutputs from all previous nodes, keyed by node identifier
.workflowWorkflow-level variables
.workflowRunWorkflow run metadata
.workflowNodeRunCurrent node run metadata (e.g. identifier, createdAt, url)
.portPort 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:

FieldTypeDefaultDescription
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.

FAILED status persists

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:

FieldDescription
output.error.messageHuman-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.

LimitValue
Minimum60 minutes (1 hour)
Maximum43200 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 typeDefault timeout
CONDITION, UPSERT_ENTITY, KAFKA1 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_AGENT24 hours
INTEGRATION_ACTION - Cursor agent (any action of the cursor-cloud-agents provider)72 hours
CURSOR_AGENT72 hours
INPUT1 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.

Older node runs

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 FAILED result (or CANCELLED if 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_ACTION and UPSERT_ENTITY nodes follow their onFailure setting: with terminate the workflow run fails, and with continue the workflow moves on to the next nodes.
  • AI, AI_AGENT, CURSOR_AGENT, CONDITION and EXECUTE_WORKFLOW nodes have no onFailure setting, 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.

Separate from the webhook HTTP request timeout

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>:

ContextDescription
.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"
}
]
}