Skip to content

n8n Workflow Patterns & Best Practices

Comprehensive guide for building reliable, maintainable n8n workflows based on 63+ production workflows.

Core Principles

  1. Sequential over Parallel - Chain HTTP requests sequentially to avoid race conditions
  2. Always handle errors - Every workflow needs error handling
  3. Test thoroughly - Test with real data before activating
  4. Document credentials - Track which workflows use which credentials
  5. Version control gotchas - Note n8n-specific issues in workflow notes

Common Patterns

Pattern 1: Sequential HTTP Chain

Use when: Making multiple API calls where later calls depend on earlier responses

Webhook → HTTP 1 → HTTP 2 (refs $('HTTP 1')) → HTTP 3 (refs $('HTTP 2'))

Why: Avoids race conditions, ensures data availability

Example:

javascript
// HTTP Node 2 - Reference previous node
{
  "salesforceId": "{{ $('Get Account').item.json.Id }}",
  "cargoWiseData": "{{ $('Get Shipment').item.json }}"
}

Pattern 2: Error Handling

Use when: Every workflow (always!)

Main Flow → [On Error] → Error Handler → Notify Admin

Implementation:

javascript
// Error Handler Node
{
  "error": "{{ $json.message }}",
  "workflow": "{{ $workflow.name }}",
  "timestamp": "{{ $now }}",
  "data": "{{ $json }}"
}

Pattern 3: Webhook Authentication

Use when: Exposing webhooks to external services

Webhook → IF (check api_key) → [true] → Process → Response
                               → [false] → 401 Response

API Key Check:

javascript
// IF node condition
{{ $json.body.api_key === 'wsi_848bf8aa2a1b4a4bf45a99a4acc609c4' }}

Pattern 4: Data Transformation

Use when: Formatting API responses for downstream use

HTTP Request → Code Node → Next Step

Code Node Example:

javascript
// Transform CargoWise response
const items = $input.all();

return items.map(item => ({
  json: {
    blNumber: item.json.BLNumber,
    pol: item.json.PortOfLoading,
    pod: item.json.PortOfDischarge,
    eta: new Date(item.json.ETA).toISOString()
  }
}));

Pattern 5: Batch Processing

Use when: Processing arrays of items

Trigger → HTTP Get List → Split In Batches → Process Item → Loop

Split In Batches Settings:

  • Batch Size: 10 (adjust based on API rate limits)
  • Reset: true (for multiple workflow runs)

Pattern 6: Conditional Routing

Use when: Different logic based on data values

HTTP Request → Switch (on status) → [case 1] → Handle Case 1
                                  → [case 2] → Handle Case 2
                                  → [default] → Handle Default

Switch Node:

javascript
// Switch on shipment status
Output: {{ $json.status }}

// Cases:
// - "in_transit"
// - "delivered"
// - "delayed"
// - default

Pattern 7: Scheduled Data Sync

Use when: Periodic data synchronization

Cron Trigger → Get Remote Data → Transform → Update Local DB → Notify

Cron Schedule Examples:

  • 0 */4 * * * - Every 4 hours
  • 0 2 * * * - Daily at 2 AM
  • 0 9 * * 1 - Every Monday at 9 AM

Pattern 8: Wait for Webhook Response

Use when: Long-running async operations

Webhook 1 (start) → HTTP Request → Store Execution ID
...
Webhook 2 (callback) → Lookup Execution → Complete Flow

Gotchas & Solutions

Gotcha 1: Merge Node Branch Limit

Problem: Merge node fails with 4+ input branches

Solution:

DON'T: Merge with 4+ branches
DO: Chain sequentially with Code nodes

✗ HTTP1 → \
  HTTP2 → Merge (4 inputs) → FAILS
  HTTP3 → /
  HTTP4 → /

✓ HTTP1 → Code (merge 1,2) → HTTP3 → Code (merge 1-3) → HTTP4 → Code (final merge)

Gotcha 2: $json vs $json.body

Problem: Unclear when to use which

Solution:

javascript
// Webhook nodes: data is in body
{{ $json.body.fieldName }}

// HTTP nodes: direct access
{{ $json.fieldName }}

// Manual trigger / Code nodes: depends on previous node
{{ $('NodeName').item.json.fieldName }}

Gotcha 3: Expression Syntax

Problem: Wrong quotes or escaping

Solutions:

javascript
// ✓ Single quotes in expressions
{{ $json['field-with-dash'] }}

// ✗ Double quotes (won't work)
{{ $json["field-with-dash"] }}

// ✓ Safe access with ?
{{ $json.optional?.field }}

// ✓ Array access
{{ $json.items[0].name }}

// ✓ Filters
{{ $json.items.filter(i => i.status === 'active') }}

Gotcha 4: Credential Scope

Problem: Credential works in UI but not in execution

Solution:

  • Credentials are user-scoped
  • Workflow runs as the user who activated it
  • Shared workflows need shared credentials
  • Use project credentials for team workflows

Gotcha 5: DateTime Formatting

Problem: Inconsistent date formats between services

Solution:

javascript
// ✓ Parse to Date object first
new Date('{{ $json.eta }}').toISOString()

// ✓ Format for SQL
new Date('{{ $json.eta }}').toISOString().split('T')[0]

// ✓ Unix timestamp
Math.floor(new Date('{{ $json.eta }}').getTime() / 1000)

// ✓ Use $now for current time
{{ $now.toISO() }}
{{ $now.toFormat('yyyy-MM-dd') }}

Gotcha 6: Empty Arrays

Problem: Workflow fails when API returns empty array

Solution:

javascript
// ✓ Check array length
{{ $json.items?.length > 0 ? $json.items[0] : null }}

// ✓ Use default value
{{ $json.items ?? [] }}

// ✓ IF node to skip processing
{{ $json.items.length > 0 }}

Gotcha 7: Node References Across Branches

Problem: Can't reference node from different branch

Solution:

javascript
// ✗ This won't work if GetAccount is in different branch
{{ $('GetAccount').item.json.id }}

// ✓ Merge branches first, then reference
Merge Node → Then use $('GetAccount')

// ✓ OR: Use Set node to store data
Set → Store Data → Reference later via $json

Gotcha 8: HTTP Request Headers

Problem: API requires specific headers not sent by default

Solution:

json
// Always set Content-Type for POST/PUT
{
  "Content-Type": "application/json"
}

// Authentication headers
{
  "Authorization": "Bearer {{ $credentials.token }}"
}

// Custom headers for specific APIs
{
  "X-API-Key": "{{ $credentials.apiKey }}",
  "User-Agent": "n8n-automation/1.0"
}

Performance Optimization

1. Minimize HTTP Requests

Don't:

Loop → HTTP Request (1 per item)

Do:

Aggregate → Single HTTP Request (batch API)

2. Use Pagination

For large datasets:

javascript
// HTTP Request Node
{
  "page": "{{ $json.currentPage }}",
  "limit": 100
}

// Loop until no more pages
{{ $json.hasMore === true }}

3. Cache Frequently Used Data

Pattern:

Cron (hourly) → Fetch Data → Store in DB
...
Trigger → Read from DB (fast) → Process

4. Parallel Processing for Independent Tasks

When tasks don't depend on each other:

Trigger → Task 1 → \
       → Task 2 → Merge → Continue
       → Task 3 → /

Security Best Practices

1. Never Hardcode Secrets

Don't:

javascript
// ✗ Hardcoded API key
const apiKey = "sk_live_abc123";

Do:

javascript
// ✓ Use credentials
{{ $credentials.apiKey }}

2. Validate Webhook Signatures

For critical webhooks:

javascript
// Code Node - Verify HMAC signature
const crypto = require('crypto');
const signature = $json.headers['x-webhook-signature'];
const body = JSON.stringify($json.body);
const secret = $credentials.webhookSecret;

const computed = crypto
  .createHmac('sha256', secret)
  .update(body)
  .digest('hex');

if (signature !== computed) {
  throw new Error('Invalid signature');
}

return items;

3. Rate Limit Webhooks

Prevent abuse:

Webhook → Check Rate Limit (Redis) → [pass] → Process
                                   → [fail] → 429 Response

4. Sanitize User Input

Before using in SQL or HTTP:

javascript
// Sanitize input
const sanitized = String($json.input)
  .replace(/[^\w\s-]/g, '')
  .trim()
  .substring(0, 255);

Error Handling Patterns

Pattern 1: Retry Logic

HTTP Request → [Error] → Wait (60s) → Retry HTTP → [Error] → Notify Admin

Pattern 2: Fallback Data Source

HTTP Primary API → [Error] → HTTP Backup API → [Error] → Use Cache

Pattern 3: Partial Failure Handling

javascript
// Code Node - Process with error handling
const results = [];
const errors = [];

for (const item of items) {
  try {
    // Process item
    results.push(processItem(item));
  } catch (error) {
    errors.push({ item, error: error.message });
  }
}

return [
  { json: { results, errors } }
];

Testing Workflows

1. Test with Manual Trigger

Before activating:

1. Disable webhook/schedule trigger
2. Add Manual Trigger
3. Test with sample data
4. Verify all nodes execute
5. Check error handling
6. Remove manual trigger
7. Activate workflow

2. Test Error Paths

Simulate failures:

IF Node → [true] → Normal Flow
       → [false] → Error Path (for testing)

3. Use Workflow Notes

Document test cases:

markdown
## Test Cases
1. ✓ Valid webhook payload
2. ✓ Invalid API key
3. ✓ Empty result set
4. ✓ Network timeout
5. ✓ Malformed JSON

## Last Tested: 2026-02-28

Monitoring & Debugging

1. Add Logging Nodes

Critical Step → Set (log data) → Continue

2. Use Webhook Responses for Debugging

javascript
// Return debug info in webhook response
{
  "success": true,
  "processedItems": items.length,
  "debug": {
    "timestamp": $now.toISO(),
    "workflow": $workflow.name,
    "execution": $execution.id
  }
}

3. Track Execution Metrics

Workflow End → HTTP Request → Send Metrics to Monitoring

Credential Management

Standard Credentials

CredentialIDUsed By
Anthropic APIHaU0Ag4EFvXrn0PBAll AI workflows
MS SQL (CargoWise)Qr4AWlNt0hSngnjsWSI workflows
Gmail SMTPxxCVAVKBysDgnuXzEmail workflows
SSH srv1139900ssh-srv1139900-pkDeployment workflows

Credential Best Practices

  1. Use descriptive names - "CargoWise Production" not "SQL1"
  2. Document scope - Which workflows use which credentials
  3. Rotate regularly - Update API keys quarterly
  4. Test after rotation - Verify workflows still work
  5. Use separate creds for prod/staging - Never share

Common n8n SQL Patterns

Execute Query (CargoWise)

sql
-- Use parameterized queries
SELECT
  s.ShipmentNumber,
  s.Status,
  s.POL,
  s.POD
FROM ShipmentHeader s
WHERE s.UpdatedDate >= @since
ORDER BY s.UpdatedDate DESC

Parameters:

json
{
  "since": "{{ $now.minus({ days: 7 }).toISO() }}"
}

Bulk Insert Pattern

javascript
// Code Node - Build bulk insert
const values = items.map(item =>
  `('${item.json.id}', '${item.json.name}')`
).join(',');

return [{
  json: {
    query: `INSERT INTO table (id, name) VALUES ${values}`
  }
}];

Workflow Organization

Naming Convention

[Category] - [Action] - [Target]

Examples:
- Ocean Tracking - Scrape - MSC Container
- WSI - Ingest - CargoWise Updates
- Sales - Sync - Salesforce Accounts
- Notify - Email - Daily Report

Workflow Notes Template

markdown
# [Workflow Name]

## Purpose
Brief description of what this workflow does

## Trigger
- Type: Webhook/Cron/Manual
- Schedule: (if cron)
- Endpoint: (if webhook)

## Dependencies
- Credentials: [list]
- External APIs: [list]
- Database tables: [list]

## Error Handling
- Errors sent to: [email/Telegram/log]
- Retry logic: [yes/no]

## Last Updated
2026-02-28

Last Updated: 2026-02-28 Related Docs: n8n Workflows, Reference: n8n Automation

HandyManny Documentation System