Design and implement n8n workflow automations with best practices
Design and implement n8n workflow automations with best practices for triggers, data transformation, and error handling. Use this when building webhook handlers, scheduled syncs, or API integrations that need robust automation patterns.
/plugin marketplace add mindmorass/reflex/plugin install reflex@mindmorass-reflexThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Build robust workflow automations with n8n - the open-source workflow automation tool.
n8n is a self-hostable workflow automation platform that connects apps and services. Key features:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Trigger │───▶│ Node │───▶│ Output │
│ (Start) │ │ (Process) │ │ (Action) │
└─────────────┘ └─────────────┘ └─────────────┘
| Type | Purpose | Examples |
|---|---|---|
| Trigger | Start workflow | Webhook, Schedule, App trigger |
| Action | Perform operations | HTTP Request, Database, Email |
| Transform | Modify data | Set, Code, IF, Switch |
| Flow | Control execution | Merge, Split, Wait, Loop |
{
"nodes": [
{
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"parameters": {
"httpMethod": "POST",
"path": "my-webhook",
"responseMode": "responseNode",
"options": {
"rawBody": true
}
}
}
]
}
Best Practices:
responseNode for custom responsesrawBody for signature verification{
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 * * 1-5"
}
]
}
}
}
Common Schedules:
0 * * * * - Every hour0 9 * * 1-5 - Weekdays at 9 AM0 0 * * 0 - Weekly on Sunday midnight*/15 * * * * - Every 15 minutes{
"name": "GitHub Trigger",
"type": "n8n-nodes-base.githubTrigger",
"parameters": {
"owner": "{{$env.GITHUB_OWNER}}",
"repository": "{{$env.GITHUB_REPO}}",
"events": ["issues", "pull_request"]
}
}
{
"name": "Transform Data",
"type": "n8n-nodes-base.set",
"parameters": {
"mode": "manual",
"duplicateItem": false,
"assignments": {
"assignments": [
{
"name": "fullName",
"value": "={{ $json.firstName }} {{ $json.lastName }}",
"type": "string"
},
{
"name": "timestamp",
"value": "={{ DateTime.now().toISO() }}",
"type": "string"
}
]
}
}
}
// Process items with custom logic
const results = [];
for (const item of $input.all()) {
const data = item.json;
// Transform data
results.push({
json: {
id: data.id,
processed: true,
score: calculateScore(data),
timestamp: new Date().toISOString()
}
});
}
function calculateScore(data) {
return data.value * 0.8 + data.bonus * 0.2;
}
return results;
# Enable Python in n8n settings
import json
from datetime import datetime
results = []
for item in _input.all():
data = item.json
# Transform data
results.append({
"json": {
"id": data.get("id"),
"processed": True,
"timestamp": datetime.now().isoformat()
}
})
return results
{
"name": "Check Status",
"type": "n8n-nodes-base.if",
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $json.status }}",
"rightValue": "active",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
}
}
}
{
"name": "Route by Type",
"type": "n8n-nodes-base.switch",
"parameters": {
"mode": "rules",
"rules": {
"values": [
{
"outputKey": "order",
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.type }}",
"rightValue": "order",
"operator": { "type": "string", "operation": "equals" }
}
]
}
},
{
"outputKey": "refund",
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.type }}",
"rightValue": "refund",
"operator": { "type": "string", "operation": "equals" }
}
]
}
}
]
},
"fallbackOutput": "extra"
}
}
{
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"parameters": {
"batchSize": 10,
"options": {
"reset": false
}
}
}
{
"name": "Merge Results",
"type": "n8n-nodes-base.merge",
"parameters": {
"mode": "combine",
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "userId"
}
]
},
"options": {}
}
}
{
"nodes": [
{
"name": "Error Trigger",
"type": "n8n-nodes-base.errorTrigger",
"parameters": {}
},
{
"name": "Send Alert",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#alerts",
"text": "Workflow failed: {{ $json.workflow.name }}\nError: {{ $json.execution.error.message }}"
}
}
]
}
{
"name": "HTTP Request",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://api.example.com/data",
"options": {}
},
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 1000
}
{
"name": "Validation Failed",
"type": "n8n-nodes-base.stopAndError",
"parameters": {
"errorMessage": "Invalid input: {{ $json.error }}"
}
}
{
"name": "Process Order",
"type": "n8n-nodes-base.executeWorkflow",
"parameters": {
"source": "database",
"workflowId": "order-processing-workflow-id",
"mode": "each",
"options": {
"waitForSubWorkflow": true
}
}
}
Best Practices:
waitForSubWorkflow based on needs{
"name": "API Request",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"method": "POST",
"url": "https://api.example.com/v1/resource",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "data",
"value": "={{ JSON.stringify($json) }}"
}
]
},
"options": {
"timeout": 30000,
"response": {
"response": {
"fullResponse": false,
"responseFormat": "json"
}
}
}
}
}
// Code node for API pagination
const allResults = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await this.helpers.httpRequest({
method: 'GET',
url: `https://api.example.com/items?page=${page}&limit=100`,
headers: {
'Authorization': `Bearer ${$env.API_TOKEN}`
}
});
allResults.push(...response.data);
hasMore = response.hasNextPage;
page++;
// Rate limiting
await new Promise(r => setTimeout(r, 100));
}
return allResults.map(item => ({ json: item }));
// Access in expressions
{{ $env.API_KEY }}
{{ $env.DATABASE_URL }}
// Access in Code node
const apiKey = $env.API_KEY;
| Type | Use Case |
|---|---|
httpBasicAuth | Basic authentication |
httpHeaderAuth | API key in header |
oAuth2Api | OAuth 2.0 flows |
httpQueryAuth | API key in query string |
version: '3.8'
services:
n8n:
image: n8nio/n8n:latest
restart: unless-stopped
ports:
- "5678:5678"
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=${N8N_USER}
- N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
- N8N_HOST=${N8N_HOST}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- NODE_ENV=production
- WEBHOOK_URL=https://${N8N_HOST}/
- GENERIC_TIMEZONE=UTC
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=${POSTGRES_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- EXECUTIONS_DATA_PRUNE=true
- EXECUTIONS_DATA_MAX_AGE=168
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- postgres
postgres:
image: postgres:15
restart: unless-stopped
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=n8n
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
n8n_data:
postgres_data:
# n8n Configuration
N8N_HOST=n8n.example.com
N8N_USER=admin
N8N_PASSWORD=secure-password-here
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
# Database
POSTGRES_USER=n8n
POSTGRES_PASSWORD=secure-db-password
# Optional: Queue mode for scaling
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis
# docker-compose.queue.yml
services:
n8n:
environment:
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- QUEUE_HEALTH_CHECK_ACTIVE=true
n8n-worker:
image: n8nio/n8n:latest
command: worker
environment:
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
deploy:
replicas: 3
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
{
"name": "Webhook to Database",
"nodes": [
{
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"parameters": {
"httpMethod": "POST",
"path": "ingest",
"responseMode": "responseNode"
}
},
{
"name": "Validate",
"type": "n8n-nodes-base.if",
"parameters": {
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.id }}",
"rightValue": "",
"operator": { "type": "string", "operation": "notEmpty" }
}
]
}
}
},
{
"name": "Insert",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "insert",
"table": "events",
"columns": "id,type,data,created_at"
}
},
{
"name": "Success Response",
"type": "n8n-nodes-base.respondToWebhook",
"parameters": {
"respondWith": "json",
"responseBody": "={{ { \"success\": true, \"id\": $json.id } }}"
}
}
]
}
{
"name": "Daily Data Sync",
"nodes": [
{
"name": "Schedule",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [{ "field": "cronExpression", "expression": "0 2 * * *" }]
}
}
},
{
"name": "Fetch Source",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://api.source.com/data",
"authentication": "predefinedCredentialType"
}
},
{
"name": "Transform",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "return $input.all().map(item => ({ json: { ...item.json, synced_at: new Date().toISOString() } }));"
}
},
{
"name": "Upsert Destination",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "upsert",
"table": "synced_data"
}
}
]
}
{
"name": "Alert Pipeline",
"nodes": [
{
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"parameters": { "path": "alert" }
},
{
"name": "Route by Severity",
"type": "n8n-nodes-base.switch",
"parameters": {
"rules": {
"values": [
{ "outputKey": "critical", "conditions": { "conditions": [{ "leftValue": "={{ $json.severity }}", "rightValue": "critical" }] } },
{ "outputKey": "warning", "conditions": { "conditions": [{ "leftValue": "={{ $json.severity }}", "rightValue": "warning" }] } }
]
}
}
},
{
"name": "Page On-Call",
"type": "n8n-nodes-base.pagerDuty"
},
{
"name": "Slack Alert",
"type": "n8n-nodes-base.slack"
}
]
}
| Expression | Description |
|---|---|
{{ $json.field }} | Access field from current item |
{{ $json["field-name"] }} | Access field with special chars |
{{ $('NodeName').item.json.field }} | Access data from specific node |
{{ $input.first().json }} | First input item |
{{ $input.all() }} | All input items |
{{ $env.VAR_NAME }} | Environment variable |
{{ $now }} | Current datetime |
{{ $today }} | Current date |
{{ $runIndex }} | Current execution run index |
{{ $itemIndex }} | Current item index |
{{ $workflow.id }} | Workflow ID |
{{ $execution.id }} | Execution ID |
// n8n uses Luxon for dates
{{ $now.toISO() }} // ISO format
{{ $now.toFormat('yyyy-MM-dd') }} // Custom format
{{ $now.plus({ days: 7 }).toISO() }} // Add 7 days
{{ $now.startOf('month').toISO() }} // Start of month
{{ DateTime.fromISO($json.date) }} // Parse ISO string
splitInBatches for large datasetsconsole.log in Code nodes for debugging// In Code node - log to n8n console
console.log('Debug:', JSON.stringify($json, null, 2));
// Return debug info
return [{
json: {
debug: true,
input: $json,
env: $env.NODE_ENV,
timestamp: new Date().toISOString()
}
}];
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.