n8n workflow JSON: a full example, explained
Updated 2026-10-21 · 6 min read
Every n8n workflow is a JSON document. You see it when you Download a workflow, paste nodes between instances, call the public API, or generate workflows with a script, which is exactly what a converter from another platform does. n8n doesn't publish a prose spec of the format; the authoritative description is the workflow schema in the public API reference. This page walks through a real example and explains each part.
A complete example
A workflow that receives a webhook, checks an amount, and responds:
{
"name": "Order check",
"nodes": [
{
"id": "3f1c0b8e-1a2b-4c3d-9e8f-000000000001",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [0, 0],
"webhookId": "3f1c0b8e-1a2b-4c3d-9e8f-0000000000aa",
"parameters": {
"httpMethod": "POST",
"path": "orders",
"responseMode": "responseNode",
"options": {}
}
},
{
"id": "3f1c0b8e-1a2b-4c3d-9e8f-000000000002",
"name": "Big order?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [220, 0],
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "typeValidation": "strict" },
"conditions": [
{
"leftValue": "={{ $json.body.total }}",
"rightValue": 1000,
"operator": { "type": "number", "operation": "gt" }
}
],
"combinator": "and"
},
"options": {}
}
},
{
"id": "3f1c0b8e-1a2b-4c3d-9e8f-000000000003",
"name": "Respond",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [440, 0],
"parameters": {
"respondWith": "json",
"responseBody": "={{ { \"received\": true, \"total\": $('Webhook').first().json.body.total } }}",
"options": {}
}
}
],
"connections": {
"Webhook": { "main": [[{ "node": "Big order?", "type": "main", "index": 0 }]] },
"Big order?": {
"main": [
[{ "node": "Respond", "type": "main", "index": 0 }],
[{ "node": "Respond", "type": "main", "index": 0 }]
]
}
},
"settings": { "executionOrder": "v1" }
}
The exact parameter names inside each node depend on the node and its typeVersion. The easiest way to get them right is to build the node once in the editor and copy its JSON (select the node, Ctrl/Cmd+C, paste into a text editor).
Top-level keys
From the API's workflow schema:
| Key | Required on create | Meaning |
|---|---|---|
name |
Yes | Workflow name |
nodes |
Yes | Array of node objects |
connections |
Yes | Wiring between nodes, keyed by source node name |
settings |
Yes | Execution settings (can be {} in practice for files) |
id, versionId, active, createdAt, updatedAt |
No | Read-only; set by n8n. Sending them to the API returns a 400 |
staticData |
No | Workflow static data |
pinData |
No | Pinned test data per node |
projectId, parentFolderId, description |
No | Organization (writable on create) |
tags, meta |
No | Read-only on create; tags are set through a separate endpoint |
nodeGroups |
No | Visual frames on the canvas |
When posting to POST /api/v1/workflows, send only name, nodes, connections and settings (plus optional fields the schema accepts). Read-only fields from a downloaded file can make the API reject the request; strip them in your script.
Node objects
| Field | Meaning |
|---|---|
id |
Unique ID (UUID) of the node within the workflow |
name |
Display name, unique within the workflow; used in connections and $('Name') |
type |
Node type, e.g. n8n-nodes-base.httpRequest, n8n-nodes-base.code, n8n-nodes-base.splitInBatches |
typeVersion |
Version of that node's implementation; parameters differ between versions |
position |
[x, y] on the canvas |
parameters |
The node's configuration; expressions are strings starting with = |
credentials |
{ "<credentialType>": { "id": "…", "name": "…" } } |
webhookId |
For webhook-based nodes |
disabled, notes, notesInFlow |
Editor flags and notes |
retryOnFail, maxTries, waitBetweenTries, onError, alwaysOutputData, executeOnce |
The node's Settings tab |
Two details people miss:
- Expressions are stored with a leading
=:"={{ $json.email }}". Without the=, the value is a literal string containing curly braces. - Node type names are camelCase in JSON (
n8n-nodes-base.httpRequest,n8n-nodes-base.splitInBatches), even though docs URLs use lowercase.
Connections
connections maps each source node name to its outputs:
"Big order?": {
"main": [
[ { "node": "Notify sales", "type": "main", "index": 0 } ],
[ { "node": "Log", "type": "main", "index": 0 } ]
]
}
mainis an array with one entry per output. For an If node, index 0 is true and index 1 is false. For a Switch node, one entry per route.- Each entry is an array of targets: one output can feed several nodes.
indexon the target is the input number: a Merge node has inputs 0, 1, 2…- AI sub-nodes use other connection types (for example
ai_languageModel) instead ofmain.
Because connections are keyed by name, renaming a node in JSON means renaming it in connections and in every expression that references it.
Credentials
A node that uses a credential stores only a reference:
"credentials": {
"httpHeaderAuth": { "id": "12", "name": "Acme API key" }
}
Exported files include credential names and IDs, never secrets (export and import). On another instance the ID won't exist; open the node and pick or create the credential. See n8n credentials.
Settings
Common keys: executionOrder (v1 for current behavior), timezone, errorWorkflow (the error workflow's ID), saveDataErrorExecution and saveDataSuccessExecution (all, none or DEFAULT), saveManualExecutions, saveExecutionProgress, executionTimeout, callerPolicy (who may call it as a sub-workflow). They mirror the Workflow Settings dialog.
pinData
Pinned test data per node name:
"pinData": {
"Webhook": [ { "json": { "body": { "total": 1200 } } } ]
}
Useful in shared templates. Pinned data is shown in place of real output during manual runs, so remove it if you don't want collaborators testing with stale data.
Validate before importing
| Problem | Symptom | Fix |
|---|---|---|
Unknown type |
"Unrecognized node type" / question-mark node | Use a type that exists on the target, or install the community node |
Unsupported typeVersion |
Node opens with missing fields | Use a version the target supports |
Name in connections doesn't match a node |
Nodes unconnected after import | Keep names in sync |
| Duplicate node names | Import fails or wiring is wrong | Make names unique |
Expression missing = |
Literal {{ }} text in output |
Prefix with = |
| Read-only fields sent to API | 400 from POST /workflows |
Send only accepted fields |
Importing itself (editor, API and CLI) is covered in import workflow JSON into n8n.
Generated JSON from Pipedream
A Pipedream export is also JSON, but a different shape: triggers, steps and deployments instead of nodes and connections (Pipedream export; details in Pipedream export format). A converter's job is to map one onto the other: each trigger becomes a trigger node, each step a node, the step order becomes connections, and steps.x.$return_value references must resolve to the right node (by rewriting them as $('x') or by providing a steps object in the code, which is what our converter does).
Our converter produces n8n workflow JSON in exactly the structure above, keeps step names as node names so references survive, and lists what it couldn't map. Steps inside branches are rebuilt where possible and always flagged for review. The code step checker shows how a single code step will look. The report and 1 workflow are free. Full export: $3 per workflow, $19 minimum — coming soon.
Not affiliated with Pipedream or n8n.
FAQ
- What fields does an n8n workflow JSON need?
- To create a workflow through the n8n API, name, nodes, connections and settings are required. The editor's Import from File is more forgiving, but a file with those four keys imports everywhere.
- How are connections stored in n8n workflow JSON?
- As an object keyed by the source node's name. Each entry has a main array with one inner array per output, listing the target node name, type main and input index.
- Does n8n workflow JSON contain my credentials?
- No. Nodes reference credentials by name and ID only. The secrets stay in the instance's credential store.
- Can I generate n8n workflow JSON with a script?
- Yes. Build the nodes and connections objects and POST them to /api/v1/workflows, or save them to a file and import it. Node type names and typeVersion must exist on the target instance.