n8n Workflow Patterns
Build robust workflow automations with n8n - the open-source workflow automation tool.
Overview
n8n is a self-hostable workflow automation platform that connects apps and services. Key features:
Visual workflow builder with 400+ integrations Self-hosted or cloud deployment Code nodes for custom logic (JavaScript/Python) Webhook triggers for real-time automation Sub-workflows for modular design Core Concepts Workflow Structure ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Trigger │───▶│ Node │───▶│ Output │ │ (Start) │ │ (Process) │ │ (Action) │ └─────────────┘ └─────────────┘ └─────────────┘
Node Types 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 Trigger Patterns Webhook Trigger { "nodes": [ { "name": "Webhook", "type": "n8n-nodes-base.webhook", "parameters": { "httpMethod": "POST", "path": "my-webhook", "responseMode": "responseNode", "options": { "rawBody": true } } } ] }
Best Practices:
Use responseNode for custom responses Enable rawBody for signature verification Add authentication (Header Auth, Basic Auth) Schedule Trigger { "name": "Schedule Trigger", "type": "n8n-nodes-base.scheduleTrigger", "parameters": { "rule": { "interval": [ { "field": "cronExpression", "expression": "0 9 * * 1-5" } ] } } }
Common Schedules:
0 * * * * - Every hour 0 9 * * 1-5 - Weekdays at 9 AM 0 0 * * 0 - Weekly on Sunday midnight */15 * * * * - Every 15 minutes App Trigger (Polling) { "name": "GitHub Trigger", "type": "n8n-nodes-base.githubTrigger", "parameters": { "owner": "{{$env.GITHUB_OWNER}}", "repository": "{{$env.GITHUB_REPO}}", "events": ["issues", "pull_request"] } }
Data Transformation Patterns Set Node (Transform Data) { "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" } ] } } }
Code Node (JavaScript) // 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;
Code Node (Python)
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
Control Flow Patterns IF Node (Conditional) { "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" } } }
Switch Node (Multi-branch) { "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" } }
Loop Over Items { "name": "Loop Over Items", "type": "n8n-nodes-base.splitInBatches", "parameters": { "batchSize": 10, "options": { "reset": false } } }
Merge Node (Combine Data) { "name": "Merge Results", "type": "n8n-nodes-base.merge", "parameters": { "mode": "combine", "mergeByFields": { "values": [ { "field1": "id", "field2": "userId" } ] }, "options": {} } }
Error Handling Patterns Try/Catch with Error Trigger { "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 }}" } } ] }
Retry on Failure { "name": "HTTP Request", "type": "n8n-nodes-base.httpRequest", "parameters": { "url": "https://api.example.com/data", "options": {} }, "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 1000 }
Stop and Error Node { "name": "Validation Failed", "type": "n8n-nodes-base.stopAndError", "parameters": { "errorMessage": "Invalid input: {{ $json.error }}" } }
Sub-Workflow Pattern Execute Workflow Node { "name": "Process Order", "type": "n8n-nodes-base.executeWorkflow", "parameters": { "source": "database", "workflowId": "order-processing-workflow-id", "mode": "each", "options": { "waitForSubWorkflow": true } } }
Best Practices:
Use sub-workflows for reusable logic Pass minimal data between workflows Set waitForSubWorkflow based on needs Use workflow tags for organization HTTP Request Patterns REST API Call { "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" } } } } }
Pagination Pattern // 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 }));
Credential Management Environment Variables // Access in expressions {{ $env.API_KEY }} {{ $env.DATABASE_URL }}
// Access in Code node const apiKey = $env.API_KEY;
Credential Types Type Use Case httpBasicAuth Basic authentication httpHeaderAuth API key in header oAuth2Api OAuth 2.0 flows httpQueryAuth API key in query string Self-Hosting Patterns Docker Compose 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:
Environment Variables (.env)
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
Queue Mode (Scaling)
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
Common Workflow Templates Webhook to Database { "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 } }}" } } ] }
Scheduled Sync { "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" } } ] }
Event-Driven Notification { "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 Cheat Sheet 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 Luxon DateTime Examples // 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
Best Practices Naming: Use descriptive node names (verb + noun) Error Handling: Always add error workflows Credentials: Never hardcode secrets Batching: Use splitInBatches for large datasets Timeouts: Set appropriate timeouts on HTTP nodes Logging: Use console.log in Code nodes for debugging Testing: Use manual execution before activating Version Control: Export workflows as JSON to git Documentation: Add sticky notes for complex logic Modular Design: Use sub-workflows for reusability Debugging Tips // 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() } }];
Resources n8n Documentation n8n Community Workflow Templates Node Reference