n8n logon8nv2INTERMEDIATE

n8n

n8n workflow automation cheat sheet with nodes, expressions, triggers, code patterns, error handling, and self-hosting deployment tips.

5 min read
n8nworkflowautomationno-codelow-codeintegrationapiwebhookstriggers
Loading your progress

Core Concepts

Basic building blocks of n8n automation

javascript
# Workflow Structure
- Workflows: Visual automation sequences
- Trigger Nodes: Start workflow (webhook, schedule, app event)
- Action Nodes: Perform operations (HTTP, Code, Transform)

# Data Format (Items)
[
  { json: { id: 1, name: "Alice" } },
  { json: { id: 2, name: "Bob" } }
]
💡 Items are always arrays of objects with a "json" key
⚡ Sub-workflow executions do not count toward limits
📌 Trigger nodes start workflows, action nodes do the work
🎯 Use core nodes (IF, Merge, Set) for flow control

Credentials

Managing authentication for external services

bash
# Credential Types
- OAuth2: Auto token refresh (Authorization Code, Client Credentials)
- API Key: Header Auth, Query Param, Custom Header
- Basic Auth: Username + Password (Base64 encoded)
- JWT: JSON Web Token validation

# Common Auth Headers
Authorization: Bearer TOKEN
X-API-Key: YOUR_KEY
🔐 All credentials encrypted at rest with AES256
💡 Prefer OAuth2 over API keys for auto token refresh
⚠️ Never hardcode secrets in Code nodes or expressions
🎯 Rotate credentials regularly for security

Expression Syntax

Accessing Data

Expression variables for accessing workflow data

javascript
# Current Item Data (inline expressions)
{{ $json.user.email }}
{{ $json['order']['items'][0]['name'] }}

# Reference Other Nodes
{{ $node["Node Name"].json.field }}
{{ $('Node Name').item.json.field }}
{{ $('HTTP Request').all() }}  // All items

# Workflow & Execution Metadata
{{ $workflow.id }}
{{ $workflow.name }}
{{ $execution.id }}
{{ $execution.mode }}  // 'manual', 'trigger', 'webhook'
💡 Use $json for inline expressions, $input for Code nodes
⚡ $() and $node[] both work for referencing other nodes
📌 $execution.mode tells you if manual, trigger, or webhook
🔍 Always wrap expressions in {{ }} in node fields

Built-in date/time manipulation using Luxon

javascript
# Current Time
{{ $now.toISO() }}                    // 2024-01-15T14:30:00.000Z
{{ $now.toFormat('yyyy-MM-dd') }}     // 2024-01-15
{{ $today.toISO() }}                  // Midnight today

# Date Arithmetic
{{ $now.plus({ days: 7 }).toISO() }}  // 7 days from now
{{ $now.minus({ hours: 2 }) }}        // 2 hours ago

# Parsing & Formatting
{{ '2024-01-15'.toDateTime('yyyy-MM-dd') }}
{{ $now.toFormat('MMMM dd, yyyy') }}  // January 15, 2024
{{ $now.toRelative() }}               // "2 hours ago"
⚡ $now and $today are Luxon DateTime objects
💡 Use .plus() and .minus() for date arithmetic
📌 .toFormat() accepts custom format strings
🎯 Chain methods: $now.plus({days:7}).toFormat("yyyy-MM-dd")

String, array, and object manipulation in expressions

javascript
# String Methods
{{ $json.email.toLowerCase() }}
{{ $json.name.trim() }}
{{ $json.text.replace('old', 'new') }}

# Array Methods
{{ $json.tags.join(', ') }}
{{ $json.items.length }}
{{ $json.prices.map(p => p * 1.1) }}

# JMESPath Queries
{{ $json.jmespath('users[0].email') }}
{{ $json.jmespath('users[*].email') }}
{{ $json.jmespath('users[?age > \`18\`].name') }}
💡 JMESPath is powerful for complex JSON queries
⚡ Use || for fallback values when field might be empty
📌 Ternary operator works: condition ? true : false
🔍 .map(), .filter(), .reduce() work on arrays

Essential Nodes

HTTP Request

Make API calls to any service

yaml
# Basic GET Request
Method: GET
URL: https://api.example.com/users

# POST with JSON Body
Method: POST
URL: https://api.example.com/users
Body Content Type: JSON
Body: { "name": "John", "email": "john@example.com" }

# With Authentication
Authentication: Predefined Credential Type
Credential Type: Header Auth
Header Auth: X-API-Key = your-key
💡 Use predefined credentials instead of hardcoding auth
⚡ Built-in pagination handles offset/cursor automatically
📌 Enable "Full Response" to access status codes
⚠️ Add Wait nodes between requests to avoid rate limits

IF & Switch

Conditional branching in workflows

yaml
# IF Node (Two-way branching)
Condition: {{ $json.status }} equals "active"
- True branch → Process active items
- False branch → Handle inactive

# Switch Node (Multi-way routing)
Mode: Rules
Rule 1: {{ $json.type }} equals "order" → Order path
Rule 2: {{ $json.type }} equals "refund" → Refund path
Fallback → Default path
💡 Use Switch node instead of nested IF nodes
⚡ IF node is data type aware (string, number, boolean)
📌 Switch has fallback route for unmatched cases
🎯 Name your output paths descriptively for clarity

Transform and reshape data structure

yaml
# Add/Modify Fields
Mode: Manual Mapping
Fields to Set:
  fullName: {{ $json.first }} {{ $json.last }}
  email: {{ $json.email.toLowerCase() }}
  processed: true

# Important Setting!
Include Other Input Fields: Yes (or data gets lost!)
⚠️ Enable "Include Other Input Fields" or unmapped data disappears!
💡 Use dot notation for nested fields: user.name.first
📌 Mode: Manual Mapping for GUI, JSON Output for code
🔍 Transform fields with expressions in values

Combine data from multiple sources

yaml
# Merge Node Modes
- Append: Combine all items into one list
- Merge By Fields: Join by matching key (like SQL JOIN)
- Merge By Position: Combine by array index
- Multiplex: Cartesian product
- SQL Query: Custom SQL (v1.49.0+)

# Aggregate Node
Operation: Aggregate Individual Fields
Fields: items (creates array from all items)
⚠️ Merge node can execute BOTH IF branches even if one is empty
💡 Use "Merge By Fields" for SQL-like joins
📌 Aggregate is opposite of Split Out (items → array)
⚡ SQL Query mode (v1.49.0+) enables custom join logic

Code Node

Common code patterns for data transformation

javascript
// Run Once for All Items (default)
const items = $input.all();

return items.map(item => ({
  json: {
    email: item.json.email.toLowerCase(),
    fullName: \`\${item.json.first} \${item.json.last}\`,
    total: item.json.price * item.json.quantity
  }
}));
⚠️ MUST return { json: {...} } - not raw objects!
💡 Use $input.all() for all items, $input.item for per-item mode
📌 Reference other nodes: $("Node Name").all()
🎯 Always use try/catch for error handling

Persist data between executions

javascript
// Get/Set Workflow Static Data
const staticData = getWorkflowStaticData('global');

// Read last processed ID
const lastId = staticData.lastProcessedId || 0;

// Update for next execution
staticData.lastProcessedId = currentId;

// Node-specific static data
const nodeData = getWorkflowStaticData('node');
nodeData.retryCount = (nodeData.retryCount || 0) + 1;
⚠️ Static data only persists in PRODUCTION executions!
💡 Use for incremental processing (track last ID)
📌 getWorkflowStaticData("global") for workflow-wide storage
🔍 Custom variables ($vars) are read-only, set in UI

Triggers

Webhook Trigger

HTTP endpoints for external events

yaml
# Webhook Configuration
HTTP Method: POST
Path: my-webhook
Authentication: Header Auth
Header Name: X-API-Key
Header Value: your-secret-key

# URLs
Test: https://your-n8n.com/webhook-test/my-webhook
Production: https://your-n8n.com/webhook/my-webhook
🔐 Always enable authentication for production webhooks
💡 Test URL has live debugging, prod URL always listens
⚠️ Changing path changes URL - update external services!
📌 Max payload size is 16MB

Cron-based workflow scheduling

bash
# Common Schedules
Every 5 minutes: */5 * * * *
Hourly at :00:    0 * * * *
Daily at 9 AM:    0 9 * * *
Weekdays 9 AM:    0 9 * * 1-5
Monthly 1st:      0 0 1 * *

# Cron Format
* * * * *
│ │ │ │ └─ Day of Week (0-7, Sun=0 or 7)
│ │ │ └─── Month (1-12)
│ │ └───── Day of Month (1-31)
│ └─────── Hour (0-23)
└───────── Minute (0-59)
💡 Use crontab.guru to generate cron expressions
⚡ Timezone: workflow setting > instance setting
📌 Weekdays = 1-5, Sunday = 0 or 7
🎯 Add random delay to avoid thundering herd

Error Handling

Strategies for handling workflow errors

yaml
# Node-Level Settings
Settings Tab:
  Retry On Fail: Yes
  Max Tries: 3
  Wait Between Tries: 1000ms
  Continue On Fail: Yes (for non-critical operations)

# Error Output (Red Connector)
Connect red output → Slack notification
Connect red output → Log to database
💡 Connect red error output to notification nodes
⚡ Create centralized error workflow with Error Trigger
📌 Use exponential backoff: 1s → 2s → 5s → 13s
🎯 Classify errors: retry vs fix vs fail fast

Self-Hosting

Run n8n with Docker and Docker Compose

bash
# Quick Start (Docker)
docker run -it --rm \
  --name n8n \
  -p 5678:5678 \
  -v ~/.n8n:/home/node/.n8n \
  n8nio/n8n

# Access at http://localhost:5678
💡 Use PostgreSQL for production (not SQLite)
⚡ Mount ~/.n8n volume to persist data
📌 Set WEBHOOK_URL to your public domain
🔐 On first launch n8n prompts you to create an owner account (user management) - the old N8N_BASIC_AUTH_* vars are deprecated/ignored in v2; put n8n behind HTTPS/a reverse proxy.

Essential configuration for self-hosted n8n

bash
# Core Settings
N8N_HOST=n8n.yourcompany.com
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.yourcompany.com/

# Timezone
TZ=America/New_York
GENERIC_TIMEZONE=America/New_York

## Security
# Basic auth (N8N_BASIC_AUTH_*) is removed in v2. Create an owner
# account on first launch (user management) and run behind HTTPS.
N8N_ENCRYPTION_KEY=your-random-32-char-key
# Generate with: openssl rand -hex 32
🔐 N8N_ENCRYPTION_KEY is critical - back it up!
💡 Use PostgreSQL for production, not SQLite
⚠️ WEBHOOK_URL must match your actual public URL
📌 Set both TZ and GENERIC_TIMEZONE to same value

Scaling & Performance

Queue Mode

Scale n8n with workers and Redis

bash
# Architecture
Main Instance: UI, API, triggers, webhook reception
Workers: Execute workflows from queue
Redis: Message broker (Bull queue)
PostgreSQL: Required (SQLite not supported)

# Main Instance
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis

# Worker Instance
docker run n8nio/n8n worker --concurrency=10
⚡ Queue mode: 220+ executions/second possible
💡 PostgreSQL required for queue mode (not SQLite)
📌 Worker concurrency: 10+ parallel workflows each
🎯 1 worker per CPU core is good starting point

Optimize workflow performance

bash
# Batch Processing
Use Split In Batches node for large datasets
Process 100 items at a time instead of 10,000

# Rate Limiting
Add Wait node between API calls (1-2 seconds)
Prevents 429 Too Many Requests errors

# Binary Data
N8N_DEFAULT_BINARY_DATA_MODE=filesystem
Use S3 for large files in queue mode

# Incremental Processing
Store last ID in static data
Query only new records: WHERE id > lastId
💡 Use Split In Batches for large datasets (100 at a time)
⚡ Add Wait nodes between API calls to avoid rate limits
📌 Store last processed ID for incremental ETL
🎯 Break 50+ node workflows into sub-workflows

Common Gotchas

Common pitfalls and how to fix them

bash
# Edit Fields Overwrites Data
❌ Problem: All input fields disappear
✅ Fix: Enable "Include Other Input Fields"

# Code Node Return Format
❌ Wrong: return { result: 'success' }
✅ Right: return { json: { result: 'success' } }

# Merge Executes Both IF Paths
❌ Problem: Both true and false paths run
✅ Understand: Merge triggers all waiting inputs

# Static Data Not Persisting
❌ Problem: Data lost between runs
✅ Note: Only saves in PRODUCTION executions!
⚠️ Code node MUST return { json: {...} } format
💡 Static data only persists in production runs!
📌 Merge node triggers ALL input branches to execute
🔐 Always enable webhook authentication in production

AI Features

Build AI-powered workflows with LLMs

yaml
# AI Agent Node
- Root node for autonomous AI decisions
- Powered by LLMs (OpenAI, Claude, Gemini, etc.)
- Can select tools, maintain conversation context
- Native LangChain integration

# Supported LLMs
- OpenAI (GPT-4, GPT-3.5)
- Anthropic (Claude 3)
- Google (Gemini, Vertex AI)
- Groq (Llama, Mixtral)
- Ollama (local LLMs)
🤖 Native LangChain integration built-in
💡 Use Ollama for free local LLM execution
📌 Workflow Tool lets AI agent trigger n8n workflows
⚡ Use memory sub-nodes for multi-turn conversations