| name | workflow-creation |
| description | Workflow creation — node definitions, connections, triggers, workflow structure. This skill should be used when the user asks to create a new workflow, build an automation, set up triggers, or scaffold a workflow structure. |
Workflow Creation
This skill covers creating new n8n workflows — choosing triggers, adding nodes, wiring connections, and configuring the workflow structure.
Available Tools
| Tool | Description |
|---|
list_workflows | List existing workflows |
get_workflow | Get full workflow details |
create_workflow | Create a new workflow |
activate_workflow | Activate a workflow |
Workflow Structure
Every n8n workflow consists of:
- name — descriptive workflow name
- nodes — array of node objects (trigger + processing)
- connections — object mapping node outputs to inputs
{
"name": "My Workflow",
"nodes": [...],
"connections": {...}
}
Creating a Workflow
Step-by-step Process
1. list_workflows() -> Check for duplicates
2. create_workflow({ name, nodes, connections }) -> Create
3. get_workflow(id) -> Verify structure
4. activate_workflow(id) -> Enable (when ready)
Minimal Workflow (Manual Trigger)
Tool: create_workflow
Input: {
"name": "Test Workflow",
"nodes": [
{
"name": "Manual Trigger",
"type": "manual-trigger", // platform-specific: e.g. "n8n-nodes-base.manualTrigger"
"position": [250, 300],
"parameters": {}
},
{
"name": "Set Data",
"type": "set-data", // platform-specific: e.g. "n8n-nodes-base.set"
"position": [450, 300],
"parameters": {
"values": {
"string": [{ "name": "message", "value": "Hello World" }]
}
}
}
],
"connections": {
"Manual Trigger": {
"main": [[{ "node": "Set Data", "type": "main", "index": 0 }]]
}
}
}
Trigger Nodes
Every workflow needs exactly ONE trigger node.
Webhook Trigger
Receives HTTP requests from external systems.
{
"name": "Webhook",
"type": "webhook",
"position": [250, 300],
"parameters": {
"path": "my-endpoint",
"httpMethod": "POST",
"responseMode": "onReceived"
}
}
Schedule Trigger
Runs on a cron/interval schedule.
{
"name": "Schedule",
"type": "schedule-trigger",
"position": [250, 300],
"parameters": {
"rule": {
"interval": [{ "field": "hours", "hoursInterval": 1 }]
}
}
}
Schedule options:
- Seconds:
{ "field": "seconds", "secondsInterval": 30 }
- Minutes:
{ "field": "minutes", "minutesInterval": 5 }
- Hours:
{ "field": "hours", "hoursInterval": 1 }
- Cron:
{ "field": "cronExpression", "expression": "0 9 * * 1-5" }
Manual Trigger
For testing and on-demand execution.
{
"name": "Manual Trigger",
"type": "manual-trigger",
"position": [250, 300],
"parameters": {}
}
Processing Nodes
HTTP Request
{
"name": "API Call",
"type": "http-request",
"position": [450, 300],
"parameters": {
"url": "https://api.example.com/data",
"method": "GET",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
}
}
Set (Transform Data)
{
"name": "Transform",
"type": "set-data",
"position": [450, 300],
"parameters": {
"values": {
"string": [{ "name": "status", "value": "={{ $json.state }}" }],
"number": [{ "name": "total", "value": "={{ $json.price * $json.qty }}" }]
}
}
}
Code (Custom Logic)
{
"name": "Custom Code",
"type": "code",
"position": [450, 300],
"parameters": {
"language": "javaScript",
"jsCode": "const items = $input.all();\nreturn items.map(item => ({ json: { ...item.json, processed: true } }));"
}
}
If (Conditional)
{
"name": "Check Status",
"type": "conditional",
"position": [450, 300],
"parameters": {
"conditions": {
"string": [{
"value1": "={{ $json.status }}",
"operation": "equals",
"value2": "active"
}]
}
}
}
Connection Patterns
Linear: A → B → C
{
"A": { "main": [[{ "node": "B", "type": "main", "index": 0 }]] },
"B": { "main": [[{ "node": "C", "type": "main", "index": 0 }]] }
}
Branch: A → If → B (true) / C (false)
{
"A": { "main": [[{ "node": "If", "type": "main", "index": 0 }]] },
"If": {
"main": [
[{ "node": "B", "type": "main", "index": 0 }],
[{ "node": "C", "type": "main", "index": 0 }
Merge: A + B → Merge → C
{
"A": { "main": [[{ "node": "Merge", "type": "main", "index": 0 }]] },
"B": { "main": [[{ "node": "Merge", "type": "main", "index": 1 }]] },
"Merge": { "main": [[{ "node": "C",
Node Positioning
Lay out nodes on a grid for readability:
- Start:
[250, 300]
- Horizontal spacing: 200px
- Vertical spacing (branches): 150px
- Flow: left → right
Additional Trigger Types
Email Trigger (IMAP)
{
"name": "Email Received",
"type": "email-trigger",
"position": [250, 300],
"parameters": {
"mailbox": "INBOX",
"options": { "unseen": true }
}
}
Form Trigger
{
"name": "Form Submitted",
"type": "form-trigger",
"position": [250, 300],
"parameters": {
"formTitle": "Contact Form",
"formFields": {
"values": [
{ "fieldLabel": "Name", "fieldType": "text", "requiredField": true },
{ "fieldLabel": "Email", "fieldType": "email", "requiredField":
Chat Trigger
{
"name": "Chat Message",
"type": "chat-trigger",
"position": [250, 300],
"parameters": {}
}
Additional Processing Nodes
Filter
{
"name": "Filter Active",
"type": "filter",
"position": [450, 300],
"parameters": {
"conditions": {
"string": [{
"value1": "={{ $json.status }}",
"operation": "equals",
"value2": "active"
}]
}
}
}
Switch
{
"name": "Route by Type",
"type": "switch",
"position": [450, 300],
"parameters": {
"mode": "rules",
"rules": {
"values": [
{ "conditions": { "string": [{ "value1": "={{ $json.type }}", "operation": "equals", "value2": "order" }] } },
{ "conditions":
Merge
{
"name": "Combine Data",
"type": "merge",
"position": [650, 300],
"parameters": {
"mode": "combine",
"mergeByFields": {
"values": [{ "field1": "id", "field2": "userId" }]
},
"joinMode": "inner"
}
}
Split In Batches
{
"name": "Process in Batches",
"type": "split-in-batches",
"position": [450, 300],
"parameters": {
"batchSize": 10
}
}
Wait
{
"name": "Wait 5 seconds",
"type": "wait",
"position": [450, 300],
"parameters": {
"amount": 5,
"unit": "seconds"
}
}
Respond to Webhook
{
"name": "Send Response",
"type": "respond-to-webhook",
"position": [650, 300],
"parameters": {
"respondWith": "json",
"responseBody": "={{ { success: true, id: $json.id } }}"
}
}
Advanced Connection Patterns
Loop: Split In Batches → Process → [loop back]
{
"Split In Batches": {
"main": [
[{ "node": "Process Item", "type": "main", "index": 0 }],
[{ "node": "Done", "type": "main", "index": 0 }]
]
},
"Process Item": {
"main": [[{ "node": "Split In Batches", "type": "main", "index": 0
Parallel Execution: A → [B, C] → Merge → D
{
"A": {
"main": [
[
{ "node": "B", "type": "main", "index": 0 },
{ "node": "C", "type": "main", "index": 0 }
]
]
},
"B": { "main": [[{ "node": "Merge", "type": "main", "index": 0 }]
Error Handling: Node → Error Trigger → Notification
{
"nodes": [
{ "name": "Error Trigger", "type": "error-trigger", "position": [250, 300] },
{ "name": "Send Alert", "type": "http-request", "position": [450, 300], "parameters": {
"url": "https://hooks.slack.com/services/xxx",
"method": "POST",
"body": "={{ JSON.stringify({ text: `Workflow failed: ${$json.workflow.name} - ${$json.execution.error.message}` }) }}"
}}
Debugging Failed Workflows
Common Failure Scenarios
- Invalid credentials → check credential type and configuration; re-authenticate if expired
- Wrong URL in HTTP Request → verify URL is reachable and correct; check for trailing slashes
- Missing input data → previous node returned empty; add If node to check for data before processing
- Expression error →
$json.field doesn't exist; use $json?.field or check data structure first
- Rate limited by external API → add Wait node between requests; use Split In Batches with smaller batch size
- Timeout → increase timeout in HTTP Request settings; consider async approach
Debugging Steps
- Get execution details → find the exact node that failed
- Check the node's input data → was the expected data passed in?
- Check the node's error message → what specifically went wrong?
- Check previous node's output → did it produce the expected data format?
- Fix the issue → update workflow
- Re-test with the same input data if possible
Debugging Code Nodes
- Syntax error → check language setting (JavaScript vs Python); verify correct syntax for chosen language
- Undefined variable → Code nodes have their own scope; data from previous nodes is in
$input.all() or items
- Wrong output format → Code nodes must return array of objects with
json property: return [{json: {key: value}}]
- Module not found → only built-in modules available; cannot import external packages in standard Code node
Systematic Debugging for Complex Workflows
For workflows with 10+ nodes:
- Bisect — disable half the nodes, test; narrow down which half contains the failure
- Isolate — run the failing node alone with mock input data
- Inspect data shape — add a temporary Set node before the failing node to log the exact data structure
- Check execution order — for parallel branches, verify merge node receives data from all branches
Best Practices
- One trigger per workflow — don't mix triggers
- Name nodes descriptively — "Fetch Orders" not "HTTP Request 1"
- Test before activating — use
execute_workflow first
- Use expressions —
={{ $json.field }} for dynamic values
- Handle errors — add error paths for critical workflows
- Start simple — build minimal workflow, test, then add complexity
- Use Split In Batches — for API rate limiting and large datasets
- Add Wait nodes — between API calls to respect rate limits
- Use Merge after parallel branches — always merge parallel paths before continuing
- Set up error workflows — configure Error Trigger workflow for critical automations