n8n

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

n8n Workflow Automation Skill

n8n工作流自动化技能

Master n8n for visual workflow automation, API integrations, and self-hosted automation pipelines. This skill covers workflow design, node configuration, triggers, credentials management, custom nodes, and production deployment patterns.
掌握n8n的可视化工作流自动化、API集成和自托管自动化流水线能力。本技能涵盖工作流设计、节点配置、触发器、凭证管理、自定义节点及生产部署模式。

When to Use This Skill

何时使用此技能

USE when:

适用场景:

  • Building integrations between 400+ services (Slack, Gmail, Notion, Airtable, etc.)
  • Creating visual workflows accessible to non-developers
  • Self-hosting is required for data sovereignty and compliance
  • Need webhook-triggered automations with real-time processing
  • Building internal tool automations and business process automation
  • Connecting APIs without writing extensive code
  • Rapid prototyping of automation workflows
  • Need human-in-the-loop approval workflows
  • 在Slack、Gmail、Notion、Airtable等400+服务之间构建集成
  • 创建非开发人员也能轻松使用的可视化工作流
  • 因数据主权和合规要求需要自托管部署
  • 需要基于Webhook的实时处理自动化
  • 构建内部工具自动化和业务流程自动化
  • 无需编写大量代码即可连接API
  • 快速原型化自动化工作流
  • 需要人工参与的审批工作流

DON'T USE when:

不适用场景:

  • Orchestrating complex data pipelines with dependencies (use Airflow)
  • CI/CD pipelines tightly coupled with git (use GitHub Actions)
  • Need sub-second latency requirements (use direct API calls)
  • Processing massive datasets (use dedicated ETL tools)
  • Require enterprise audit compliance out-of-box (evaluate requirements)
  • Simple single-trigger cron jobs (use systemd timers)
  • 编排带有依赖关系的复杂数据管道(建议使用Airflow)
  • 与Git紧密耦合的CI/CD流水线(建议使用GitHub Actions)
  • 要求亚秒级延迟的场景(建议使用直接API调用)
  • 处理大规模数据集(建议使用专用ETL工具)
  • 开箱即需符合企业审计合规要求(需评估具体需求)
  • 简单的单触发器定时任务(建议使用systemd定时器)

Prerequisites

前置条件

Installation Options

安装选项

Option 1: npm (Development)
bash
undefined
选项1:npm(开发环境)
bash
undefined

Install globally

全局安装

npm install n8n -g
npm install n8n -g

Start n8n

启动n8n

n8n start
n8n start

**Option 2: Docker (Recommended)**
```bash

**选项2:Docker(推荐)**
```bash

Quick start with Docker

使用Docker快速启动

docker run -it --rm
--name n8n
-p 5678:5678
-v ~/.n8n:/home/node/.n8n
n8nio/n8n
docker run -it --rm \ --name n8n \ -p 5678:5678 \ -v ~/.n8n:/home/node/.n8n \ n8nio/n8n

**Option 3: Docker Compose (Production)**
```yaml

**选项3:Docker Compose(生产环境)**
```yaml

docker-compose.yml

docker-compose.yml

version: '3.8'
services: n8n: image: n8nio/n8n:latest restart: always ports: - "5678:5678" environment: - N8N_BASIC_AUTH_ACTIVE=true - N8N_BASIC_AUTH_USER=admin - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD} - N8N_HOST=n8n.example.com - N8N_PORT=5678 - N8N_PROTOCOL=https - WEBHOOK_URL=https://n8n.example.com/ - GENERIC_TIMEZONE=UTC - TZ=UTC - DB_TYPE=postgresdb - DB_POSTGRESDB_HOST=postgres - DB_POSTGRESDB_PORT=5432 - DB_POSTGRESDB_DATABASE=n8n - DB_POSTGRESDB_USER=n8n - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD} - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} volumes: - n8n_data:/home/node/.n8n - ./custom-nodes:/home/node/.n8n/custom depends_on: - postgres
postgres: image: postgres:15 restart: always environment: - POSTGRES_USER=n8n - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=n8n volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U n8n"] interval: 10s timeout: 5s retries: 5
volumes: n8n_data: postgres_data:

```bash
version: '3.8'
services: n8n: image: n8nio/n8n:latest restart: always ports: - "5678:5678" environment: - N8N_BASIC_AUTH_ACTIVE=true - N8N_BASIC_AUTH_USER=admin - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD} - N8N_HOST=n8n.example.com - N8N_PORT=5678 - N8N_PROTOCOL=https - WEBHOOK_URL=https://n8n.example.com/ - GENERIC_TIMEZONE=UTC - TZ=UTC - DB_TYPE=postgresdb - DB_POSTGRESDB_HOST=postgres - DB_POSTGRESDB_PORT=5432 - DB_POSTGRESDB_DATABASE=n8n - DB_POSTGRESDB_USER=n8n - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD} - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} volumes: - n8n_data:/home/node/.n8n - ./custom-nodes:/home/node/.n8n/custom depends_on: - postgres
postgres: image: postgres:15 restart: always environment: - POSTGRES_USER=n8n - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=n8n volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U n8n"] interval: 10s timeout: 5s retries: 5
volumes: n8n_data: postgres_data:

```bash

Start with docker-compose

使用docker-compose启动

docker compose up -d
docker compose up -d

View logs

查看日志

docker compose logs -f n8n

**Option 4: Kubernetes with Helm**
```bash
docker compose logs -f n8n

**选项4:Kubernetes与Helm**
```bash

Add n8n Helm repo

添加n8n Helm仓库

helm repo add n8n https://n8n-io.github.io/n8n-helm helm repo update
helm repo add n8n https://n8n-io.github.io/n8n-helm helm repo update

Install n8n

安装n8n

helm install n8n n8n/n8n
--namespace n8n
--create-namespace
--set ingress.enabled=true
--set ingress.hosts[0].host=n8n.example.com
helm install n8n n8n/n8n \ --namespace n8n \ --create-namespace \ --set ingress.enabled=true \ --set ingress.hosts[0].host=n8n.example.com

Get initial admin password

获取初始管理员密码

kubectl get secret -n n8n n8n -o jsonpath="{.data.password}" | base64 --decode
undefined
kubectl get secret -n n8n n8n -o jsonpath="{.data.password}" | base64 --decode
undefined

Development Setup

开发环境设置

bash
undefined
bash
undefined

Clone for custom node development

克隆仓库用于自定义节点开发

Install dependencies

安装依赖

pnpm install
pnpm install

Build

构建

pnpm build
pnpm build

Start development server

启动开发服务器

pnpm dev
undefined
pnpm dev
undefined

Core Capabilities

核心能力

1. Basic Workflow Structure

1. 基础工作流结构

json
{
  "name": "Basic Data Pipeline",
  "nodes": [
    {
      "parameters": {},
      "id": "start-node",
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "url": "https://api.example.com/data",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpHeaderAuth",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "http-request",
      "name": "Fetch Data",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [460, 300],
      "credentials": {
        "httpHeaderAuth": {
          "id": "1",
          "name": "API Key"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Transform each item\nconst item = $input.item.json;\n\nreturn {\n  id: item.id,\n  name: item.name.toUpperCase(),\n  processed_at: new Date().toISOString(),\n  source: 'n8n-pipeline'\n};"
      },
      "id": "transform",
      "name": "Transform Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "resource": "row",
        "operation": "create",
        "tableId": "={{ $env.AIRTABLE_TABLE_ID }}",
        "options": {}
      },
      "id": "airtable",
      "name": "Save to Airtable",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 2,
      "position": [900, 300],
      "credentials": {
        "airtableTokenApi": {
          "id": "2",
          "name": "Airtable Token"
        }
      }
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [{ "node": "Fetch Data", "type": "main", "index": 0 }]
      ]
    },
    "Fetch Data": {
      "main": [
        [{ "node": "Transform Data", "type": "main", "index": 0 }]
      ]
    },
    "Transform Data": {
      "main": [
        [{ "node": "Save to Airtable", "type": "main", "index": 0 }]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
json
{
  "name": "Basic Data Pipeline",
  "nodes": [
    {
      "parameters": {},
      "id": "start-node",
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "url": "https://api.example.com/data",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpHeaderAuth",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "http-request",
      "name": "Fetch Data",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [460, 300],
      "credentials": {
        "httpHeaderAuth": {
          "id": "1",
          "name": "API Key"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Transform each item\
const item = $input.item.json;\
\
return {\
  id: item.id,\
  name: item.name.toUpperCase(),\
  processed_at: new Date().toISOString(),\
  source: 'n8n-pipeline'\
};"
      },
      "id": "transform",
      "name": "Transform Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "resource": "row",
        "operation": "create",
        "tableId": "={{ $env.AIRTABLE_TABLE_ID }}",
        "options": {}
      },
      "id": "airtable",
      "name": "Save to Airtable",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 2,
      "position": [900, 300],
      "credentials": {
        "airtableTokenApi": {
          "id": "2",
          "name": "Airtable Token"
        }
      }
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [{ "node": "Fetch Data", "type": "main", "index": 0 }]
      ]
    },
    "Fetch Data": {
      "main": [
        [{ "node": "Transform Data", "type": "main", "index": 0 }]
      ]
    },
    "Transform Data": {
      "main": [
        [{ "node": "Save to Airtable", "type": "main", "index": 0 }]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}

2. Webhook Triggers

2. Webhook触发器

json
{
  "name": "Webhook Handler",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "incoming-webhook",
        "responseMode": "responseNode",
        "options": {
          "rawBody": true
        }
      },
      "id": "webhook",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [240, 300],
      "webhookId": "unique-webhook-id"
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.event_type }}",
              "operation": "equals",
              "value2": "payment.completed"
            }
          ]
        }
      },
      "id": "filter",
      "name": "Filter Payment Events",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "jsCode": "// Validate webhook signature\nconst crypto = require('crypto');\n\nconst payload = $input.item.json;\nconst signature = $input.item.headers['x-signature'];\nconst secret = $env.WEBHOOK_SECRET;\n\nconst expectedSignature = crypto\n  .createHmac('sha256', secret)\n  .update(JSON.stringify(payload))\n  .digest('hex');\n\nif (signature !== expectedSignature) {\n  throw new Error('Invalid webhook signature');\n}\n\nreturn {\n  ...payload,\n  validated: true,\n  processed_at: new Date().toISOString()\n};"
      },
      "id": "validate",
      "name": "Validate Signature",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "channel": "#payments",
        "text": "=Payment received!\n\nAmount: ${{ $json.amount }}\nCustomer: {{ $json.customer_email }}\nTransaction ID: {{ $json.transaction_id }}",
        "otherOptions": {}
      },
      "id": "slack",
      "name": "Notify Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [900, 300],
      "credentials": {
        "slackApi": {
          "id": "3",
          "name": "Slack Bot"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'processed', id: $json.transaction_id }) }}"
      },
      "id": "respond",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [1120, 300]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [{ "node": "Filter Payment Events", "type": "main", "index": 0 }]
      ]
    },
    "Filter Payment Events": {
      "main": [
        [{ "node": "Validate Signature", "type": "main", "index": 0 }]
      ]
    },
    "Validate Signature": {
      "main": [
        [{ "node": "Notify Slack", "type": "main", "index": 0 }]
      ]
    },
    "Notify Slack": {
      "main": [
        [{ "node": "Respond to Webhook", "type": "main", "index": 0 }]
      ]
    }
  }
}
json
{
  "name": "Webhook Handler",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "incoming-webhook",
        "responseMode": "responseNode",
        "options": {
          "rawBody": true
        }
      },
      "id": "webhook",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [240, 300],
      "webhookId": "unique-webhook-id"
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.event_type }}",
              "operation": "equals",
              "value2": "payment.completed"
            }
          ]
        }
      },
      "id": "filter",
      "name": "Filter Payment Events",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "jsCode": "// Validate webhook signature\
const crypto = require('crypto');\
\
const payload = $input.item.json;\
const signature = $input.item.headers['x-signature'];\
const secret = $env.WEBHOOK_SECRET;\
\
const expectedSignature = crypto\
  .createHmac('sha256', secret)\
  .update(JSON.stringify(payload))\
  .digest('hex');\
\
if (signature !== expectedSignature) {\
  throw new Error('Invalid webhook signature');\
}\
\
return {\
  ...payload,\
  validated: true,\
  processed_at: new Date().toISOString()\
};"
      },
      "id": "validate",
      "name": "Validate Signature",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "channel": "#payments",
        "text": "=Payment received!\
\
Amount: ${{ $json.amount }}\
Customer: {{ $json.customer_email }}\
Transaction ID: {{ $json.transaction_id }}",
        "otherOptions": {}
      },
      "id": "slack",
      "name": "Notify Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [900, 300],
      "credentials": {
        "slackApi": {
          "id": "3",
          "name": "Slack Bot"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'processed', id: $json.transaction_id }) }}"
      },
      "id": "respond",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [1120, 300]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [{ "node": "Filter Payment Events", "type": "main", "index": 0 }]
      ]
    },
    "Filter Payment Events": {
      "main": [
        [{ "node": "Validate Signature", "type": "main", "index": 0 }]
      ]
    },
    "Validate Signature": {
      "main": [
        [{ "node": "Notify Slack", "type": "main", "index": 0 }]
      ]
    },
    "Notify Slack": {
      "main": [
        [{ "node": "Respond to Webhook", "type": "main", "index": 0 }]
      ]
    }
  }
}

3. Scheduled Workflows

3. 定时工作流

json
{
  "name": "Daily Report Generator",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1-5"
            }
          ]
        }
      },
      "id": "schedule",
      "name": "Daily Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT \n  DATE(created_at) as date,\n  COUNT(*) as total_orders,\n  SUM(amount) as revenue,\n  AVG(amount) as avg_order_value\nFROM orders\nWHERE created_at >= CURRENT_DATE - INTERVAL '7 days'\nGROUP BY DATE(created_at)\nORDER BY date DESC",
        "options": {}
      },
      "id": "postgres",
      "name": "Query Sales Data",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.3,
      "position": [460, 300],
      "credentials": {
        "postgres": {
          "id": "4",
          "name": "Production DB"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Generate report summary\nconst data = $input.all();\n\nconst totalRevenue = data.reduce((sum, row) => sum + parseFloat(row.json.revenue), 0);\nconst totalOrders = data.reduce((sum, row) => sum + parseInt(row.json.total_orders), 0);\nconst avgOrderValue = totalRevenue / totalOrders;\n\nconst reportDate = new Date().toISOString().split('T')[0];\n\nreturn {\n  report_date: reportDate,\n  period: 'Last 7 Days',\n  summary: {\n    total_revenue: totalRevenue.toFixed(2),\n    total_orders: totalOrders,\n    avg_order_value: avgOrderValue.toFixed(2)\n  },\n  daily_breakdown: data.map(row => row.json)\n};"
      },
      "id": "transform",
      "name": "Generate Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "sendTo": "team@example.com",
        "subject": "=Weekly Sales Report - {{ $json.report_date }}",
        "emailType": "html",
        "html": "=<h1>Weekly Sales Report</h1>\n<p>Period: {{ $json.period }}</p>\n<h2>Summary</h2>\n<ul>\n  <li>Total Revenue: ${{ $json.summary.total_revenue }}</li>\n  <li>Total Orders: {{ $json.summary.total_orders }}</li>\n  <li>Average Order Value: ${{ $json.summary.avg_order_value }}</li>\n</ul>\n<h2>Daily Breakdown</h2>\n<table border=\"1\">\n  <tr><th>Date</th><th>Orders</th><th>Revenue</th></tr>\n  {{ $json.daily_breakdown.map(d => `<tr><td>${d.date}</td><td>${d.total_orders}</td><td>$${d.revenue}</td></tr>`).join('') }}\n</table>",
        "options": {}
      },
      "id": "email",
      "name": "Send Report Email",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [900, 300],
      "credentials": {
        "smtp": {
          "id": "5",
          "name": "SMTP Server"
        }
      }
    }
  ],
  "connections": {
    "Daily Schedule": {
      "main": [
        [{ "node": "Query Sales Data", "type": "main", "index": 0 }]
      ]
    },
    "Query Sales Data": {
      "main": [
        [{ "node": "Generate Report", "type": "main", "index": 0 }]
      ]
    },
    "Generate Report": {
      "main": [
        [{ "node": "Send Report Email", "type": "main", "index": 0 }]
      ]
    }
  }
}
json
{
  "name": "Daily Report Generator",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1-5"
            }
          ]
        }
      },
      "id": "schedule",
      "name": "Daily Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT \
  DATE(created_at) as date,\
  COUNT(*) as total_orders,\
  SUM(amount) as revenue,\
  AVG(amount) as avg_order_value\
FROM orders\
WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'\
GROUP BY DATE(created_at)\
ORDER BY date DESC",
        "options": {}
      },
      "id": "postgres",
      "name": "Query Sales Data",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.3,
      "position": [460, 300],
      "credentials": {
        "postgres": {
          "id": "4",
          "name": "Production DB"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Generate report summary\
const data = $input.all();\
\
const totalRevenue = data.reduce((sum, row) => sum + parseFloat(row.json.revenue), 0);\
const totalOrders = data.reduce((sum, row) => sum + parseInt(row.json.total_orders), 0);\
const avgOrderValue = totalRevenue / totalOrders;\
\
const reportDate = new Date().toISOString().split('T')[0];\
\
return {\
  report_date: reportDate,\
  period: 'Last 7 Days',\
  summary: {\
    total_revenue: totalRevenue.toFixed(2),\
    total_orders: totalOrders,\
    avg_order_value: avgOrderValue.toFixed(2)\
  },\
  daily_breakdown: data.map(row => row.json)\
};"
      },
      "id": "transform",
      "name": "Generate Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "sendTo": "team@example.com",
        "subject": "=Weekly Sales Report - {{ $json.report_date }}",
        "emailType": "html",
        "html": "=<h1>Weekly Sales Report</h1>\
<p>Period: {{ $json.period }}</p>\
<h2>Summary</h2>\
<ul>\
  <li>Total Revenue: ${{ $json.summary.total_revenue }}</li>\
  <li>Total Orders: {{ $json.summary.total_orders }}</li>\
  <li>Average Order Value: ${{ $json.summary.avg_order_value }}</li>\
</ul>\
<h2>Daily Breakdown</h2>\
<table border=\\"1\\">\
  <tr><th>Date</th><th>Orders</th><th>Revenue</th></tr>\
  {{ $json.daily_breakdown.map(d => `<tr><td>${d.date}</td><td>${d.total_orders}</td><td>$${d.revenue}</td></tr>`).join('') }}\
</table>",
        "options": {}
      },
      "id": "email",
      "name": "Send Report Email",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [900, 300],
      "credentials": {
        "smtp": {
          "id": "5",
          "name": "SMTP Server"
        }
      }
    }
  ],
  "connections": {
    "Daily Schedule": {
      "main": [
        [{ "node": "Query Sales Data", "type": "main", "index": 0 }]
      ]
    },
    "Query Sales Data": {
      "main": [
        [{ "node": "Generate Report", "type": "main", "index": 0 }]
      ]
    },
    "Generate Report": {
      "main": [
        [{ "node": "Send Report Email", "type": "main", "index": 0 }]
      ]
    }
  }
}

4. Conditional Branching and Error Handling

4. 条件分支与错误处理

json
{
  "name": "Order Processing with Error Handling",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "process-order",
        "responseMode": "responseNode"
      },
      "id": "webhook",
      "name": "Order Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "condition-1",
              "leftValue": "={{ $json.order_total }}",
              "rightValue": 1000,
              "operator": {
                "type": "number",
                "operation": "gte"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "switch",
      "name": "High Value Order?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "url": "https://api.payment.com/process",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "order_id",
              "value": "={{ $json.order_id }}"
            },
            {
              "name": "amount",
              "value": "={{ $json.order_total }}"
            },
            {
              "name": "priority",
              "value": "high"
            }
          ]
        },
        "options": {}
      },
      "id": "high-value-payment",
      "name": "Process High Value",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [680, 200],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "url": "https://api.payment.com/process",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "order_id",
              "value": "={{ $json.order_id }}"
            },
            {
              "name": "amount",
              "value": "={{ $json.order_total }}"
            },
            {
              "name": "priority",
              "value": "normal"
            }
          ]
        }
      },
      "id": "normal-payment",
      "name": "Process Normal",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [680, 400],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "// Handle payment error\nconst error = $input.item.json;\n\nreturn {\n  status: 'failed',\n  error_message: error.message || 'Payment processing failed',\n  order_id: $('Order Webhook').item.json.order_id,\n  timestamp: new Date().toISOString(),\n  retry_eligible: true\n};"
      },
      "id": "error-handler",
      "name": "Handle Error",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [900, 500]
    },
    {
      "parameters": {
        "channel": "#alerts",
        "text": "=Payment Failed!\n\nOrder ID: {{ $json.order_id }}\nError: {{ $json.error_message }}\nRetry Eligible: {{ $json.retry_eligible }}",
        "otherOptions": {}
      },
      "id": "slack-alert",
      "name": "Alert Team",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [1120, 500],
      "credentials": {
        "slackApi": {
          "id": "3",
          "name": "Slack Bot"
        }
      }
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      },
      "id": "merge",
      "name": "Merge Results",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 2.1,
      "position": [900, 300]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'processed', order_id: $json.order_id }) }}"
      },
      "id": "respond-success",
      "name": "Success Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [1120, 300]
    }
  ],
  "connections": {
    "Order Webhook": {
      "main": [
        [{ "node": "High Value Order?", "type": "main", "index": 0 }]
      ]
    },
    "High Value Order?": {
      "main": [
        [{ "node": "Process High Value", "type": "main", "index": 0 }],
        [{ "node": "Process Normal", "type": "main", "index": 0 }]
      ]
    },
    "Process High Value": {
      "main": [
        [{ "node": "Merge Results", "type": "main", "index": 0 }],
        [{ "node": "Handle Error", "type": "main", "index": 0 }]
      ]
    },
    "Process Normal": {
      "main": [
        [{ "node": "Merge Results", "type": "main", "index": 1 }],
        [{ "node": "Handle Error", "type": "main", "index": 0 }]
      ]
    },
    "Handle Error": {
      "main": [
        [{ "node": "Alert Team", "type": "main", "index": 0 }]
      ]
    },
    "Merge Results": {
      "main": [
        [{ "node": "Success Response", "type": "main", "index": 0 }]
      ]
    }
  }
}
json
{
  "name": "Order Processing with Error Handling",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "process-order",
        "responseMode": "responseNode"
      },
      "id": "webhook",
      "name": "Order Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "condition-1",
              "leftValue": "={{ $json.order_total }}",
              "rightValue": 1000,
              "operator": {
                "type": "number",
                "operation": "gte"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "switch",
      "name": "High Value Order?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "url": "https://api.payment.com/process",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "order_id",
              "value": "={{ $json.order_id }}"
            },
            {
              "name": "amount",
              "value": "={{ $json.order_total }}"
            },
            {
              "name": "priority",
              "value": "high"
            }
          ]
        },
        "options": {}
      },
      "id": "high-value-payment",
      "name": "Process High Value",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [680, 200],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "url": "https://api.payment.com/process",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "order_id",
              "value": "={{ $json.order_id }}"
            },
            {
              "name": "amount",
              "value": "={{ $json.order_total }}"
            },
            {
              "name": "priority",
              "value": "normal"
            }
          ]
        }
      },
      "id": "normal-payment",
      "name": "Process Normal",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [680, 400],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "// Handle payment error\
const error = $input.item.json;\
\
return {\
  status: 'failed',\
  error_message: error.message || 'Payment processing failed',\
  order_id: $('Order Webhook').item.json.order_id,\
  timestamp: new Date().toISOString(),\
  retry_eligible: true\
};"
      },
      "id": "error-handler",
      "name": "Handle Error",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [900, 500]
    },
    {
      "parameters": {
        "channel": "#alerts",
        "text": "=Payment Failed!\
\
Order ID: {{ $json.order_id }}\
Error: {{ $json.error_message }}\
Retry Eligible: {{ $json.retry_eligible }}",
        "otherOptions": {}
      },
      "id": "slack-alert",
      "name": "Alert Team",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [1120, 500],
      "credentials": {
        "slackApi": {
          "id": "3",
          "name": "Slack Bot"
        }
      }
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      },
      "id": "merge",
      "name": "Merge Results",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 2.1,
      "position": [900, 300]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'processed', order_id: $json.order_id }) }}"
      },
      "id": "respond-success",
      "name": "Success Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [1120, 300]
    }
  ],
  "connections": {
    "Order Webhook": {
      "main": [
        [{ "node": "High Value Order?", "type": "main", "index": 0 }]
      ]
    },
    "High Value Order?": {
      "main": [
        [{ "node": "Process High Value", "type": "main", "index": 0 }],
        [{ "node": "Process Normal", "type": "main", "index": 0 }]
      ]
    },
    "Process High Value": {
      "main": [
        [{ "node": "Merge Results", "type": "main", "index": 0 }],
        [{ "node": "Handle Error", "type": "main", "index": 0 }]
      ]
    },
    "Process Normal": {
      "main": [
        [{ "node": "Merge Results", "type": "main", "index": 1 }],
        [{ "node": "Handle Error", "type": "main", "index": 0 }]
      ]
    },
    "Handle Error": {
      "main": [
        [{ "node": "Alert Team", "type": "main", "index": 0 }]
      ]
    },
    "Merge Results": {
      "main": [
        [{ "node": "Success Response", "type": "main", "index": 0 }]
      ]
    }
  }
}

5. Data Transformation with Code Node

5. 代码节点的数据转换

javascript
// Code Node: Advanced Data Transformation
// Mode: Run Once for All Items

// Access all input items
const items = $input.all();

// Group items by category
const groupedByCategory = items.reduce((acc, item) => {
  const category = item.json.category || 'uncategorized';
  if (!acc[category]) {
    acc[category] = [];
  }
  acc[category].push(item.json);
  return acc;
}, {});

// Calculate statistics per category
const categoryStats = Object.entries(groupedByCategory).map(([category, items]) => {
  const values = items.map(i => parseFloat(i.value) || 0);

  return {
    category,
    count: items.length,
    total: values.reduce((a, b) => a + b, 0),
    average: values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0,
    min: Math.min(...values),
    max: Math.max(...values),
    items: items
  };
});

// Sort by total value descending
categoryStats.sort((a, b) => b.total - a.total);

// Add metadata
const result = {
  generated_at: new Date().toISOString(),
  total_items: items.length,
  total_categories: categoryStats.length,
  categories: categoryStats
};

return result;
javascript
// Code Node: HTTP Request with Retry Logic
// Mode: Run Once for Each Item

const maxRetries = 3;
const baseDelay = 1000;

async function fetchWithRetry(url, options, attempt = 1) {
  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    if (attempt >= maxRetries) {
      throw error;
    }

    const delay = baseDelay * Math.pow(2, attempt - 1);
    await new Promise(resolve => setTimeout(resolve, delay));

    return fetchWithRetry(url, options, attempt + 1);
  }
}

const item = $input.item.json;

try {
  const result = await fetchWithRetry(
    `https://api.example.com/process/${item.id}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${$env.API_TOKEN}`
      },
      body: JSON.stringify(item)
    }
  );

  return {
    ...item,
    api_response: result,
    status: 'success'
  };
} catch (error) {
  return {
    ...item,
    error: error.message,
    status: 'failed'
  };
}
javascript
// Code Node: Advanced Data Transformation
// Mode: Run Once for All Items

// Access all input items
const items = $input.all();

// Group items by category
const groupedByCategory = items.reduce((acc, item) => {
  const category = item.json.category || 'uncategorized';
  if (!acc[category]) {
    acc[category] = [];
  }
  acc[category].push(item.json);
  return acc;
}, {});

// Calculate statistics per category
const categoryStats = Object.entries(groupedByCategory).map(([category, items]) => {
  const values = items.map(i => parseFloat(i.value) || 0);

  return {
    category,
    count: items.length,
    total: values.reduce((a, b) => a + b, 0),
    average: values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0,
    min: Math.min(...values),
    max: Math.max(...values),
    items: items
  };
});

// Sort by total value descending
categoryStats.sort((a, b) => b.total - a.total);

// Add metadata
const result = {
  generated_at: new Date().toISOString(),
  total_items: items.length,
  total_categories: categoryStats.length,
  categories: categoryStats
};

return result;
javascript
// Code Node: HTTP Request with Retry Logic
// Mode: Run Once for Each Item

const maxRetries = 3;
const baseDelay = 1000;

async function fetchWithRetry(url, options, attempt = 1) {
  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    if (attempt >= maxRetries) {
      throw error;
    }

    const delay = baseDelay * Math.pow(2, attempt - 1);
    await new Promise(resolve => setTimeout(resolve, delay));

    return fetchWithRetry(url, options, attempt + 1);
  }
}

const item = $input.item.json;

try {
  const result = await fetchWithRetry(
    `https://api.example.com/process/${item.id}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${$env.API_TOKEN}`
      },
      body: JSON.stringify(item)
    }
  );

  return {
    ...item,
    api_response: result,
    status: 'success'
  };
} catch (error) {
  return {
    ...item,
    error: error.message,
    status: 'failed'
  };
}

6. Credentials Management

6. 凭证管理

bash
undefined
bash
undefined

Environment variables for n8n

Environment variables for n8n

export N8N_ENCRYPTION_KEY="your-32-char-encryption-key-here" export N8N_USER_MANAGEMENT_JWT_SECRET="your-jwt-secret"
export N8N_ENCRYPTION_KEY="your-32-char-encryption-key-here" export N8N_USER_MANAGEMENT_JWT_SECRET="your-jwt-secret"

Database configuration

Database configuration

export DB_TYPE=postgresdb export DB_POSTGRESDB_HOST=localhost export DB_POSTGRESDB_PORT=5432 export DB_POSTGRESDB_DATABASE=n8n export DB_POSTGRESDB_USER=n8n export DB_POSTGRESDB_PASSWORD=secure_password
export DB_TYPE=postgresdb export DB_POSTGRESDB_HOST=localhost export DB_POSTGRESDB_PORT=5432 export DB_POSTGRESDB_DATABASE=n8n export DB_POSTGRESDB_USER=n8n export DB_POSTGRESDB_PASSWORD=secure_password

External service credentials (set in n8n UI)

External service credentials (set in n8n UI)

These are stored encrypted in the database

These are stored encrypted in the database


```json
{
  "name": "Using Credentials Securely",
  "nodes": [
    {
      "parameters": {
        "url": "={{ $env.API_BASE_URL }}/users",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Custom-Header",
              "value": "={{ $env.CUSTOM_HEADER_VALUE }}"
            }
          ]
        }
      },
      "id": "http",
      "name": "API Call",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [460, 300],
      "credentials": {
        "httpHeaderAuth": {
          "id": "1",
          "name": "API Key Auth"
        }
      }
    }
  ]
}

```json
{
  "name": "Using Credentials Securely",
  "nodes": [
    {
      "parameters": {
        "url": "={{ $env.API_BASE_URL }}/users",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Custom-Header",
              "value": "={{ $env.CUSTOM_HEADER_VALUE }}"
            }
          ]
        }
      },
      "id": "http",
      "name": "API Call",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [460, 300],
      "credentials": {
        "httpHeaderAuth": {
          "id": "1",
          "name": "API Key Auth"
        }
      }
    }
  ]
}

7. Custom Node Development

7. 自定义节点开发

typescript
// packages/nodes-custom/nodes/MyCustomNode/MyCustomNode.node.ts
import {
  IExecuteFunctions,
  INodeExecutionData,
  INodeType,
  INodeTypeDescription,
  NodeOperationError,
} from 'n8n-workflow';

export class MyCustomNode implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'My Custom Node',
    name: 'myCustomNode',
    icon: 'file:myicon.svg',
    group: ['transform'],
    version: 1,
    subtitle: '={{$parameter["operation"]}}',
    description: 'Custom node for specific business logic',
    defaults: {
      name: 'My Custom Node',
    },
    inputs: ['main'],
    outputs: ['main'],
    credentials: [
      {
        name: 'myCustomApi',
        required: true,
      },
    ],
    properties: [
      {
        displayName: 'Operation',
        name: 'operation',
        type: 'options',
        noDataExpression: true,
        options: [
          {
            name: 'Process',
            value: 'process',
            description: 'Process data through custom logic',
          },
          {
            name: 'Validate',
            value: 'validate',
            description: 'Validate data against rules',
          },
        ],
        default: 'process',
      },
      {
        displayName: 'Input Field',
        name: 'inputField',
        type: 'string',
        default: 'data',
        required: true,
        description: 'Field to process',
      },
      {
        displayName: 'Options',
        name: 'options',
        type: 'collection',
        placeholder: 'Add Option',
        default: {},
        options: [
          {
            displayName: 'Strict Mode',
            name: 'strictMode',
            type: 'boolean',
            default: false,
            description: 'Enable strict validation',
          },
          {
            displayName: 'Output Format',
            name: 'outputFormat',
            type: 'options',
            options: [
              { name: 'JSON', value: 'json' },
              { name: 'Array', value: 'array' },
            ],
            default: 'json',
          },
        ],
      },
    ],
  };

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const items = this.getInputData();
    const returnData: INodeExecutionData[] = [];

    const operation = this.getNodeParameter('operation', 0) as string;
    const inputField = this.getNodeParameter('inputField', 0) as string;
    const options = this.getNodeParameter('options', 0, {}) as {
      strictMode?: boolean;
      outputFormat?: string;
    };

    // Get credentials
    const credentials = await this.getCredentials('myCustomApi');

    for (let i = 0; i < items.length; i++) {
      try {
        const item = items[i].json;
        const inputData = item[inputField];

        if (!inputData && options.strictMode) {
          throw new NodeOperationError(
            this.getNode(),
            `Field "${inputField}" not found in item ${i}`,
            { itemIndex: i }
          );
        }

        let result: any;

        if (operation === 'process') {
          result = await this.processData(inputData, credentials);
        } else if (operation === 'validate') {
          result = this.validateData(inputData, options.strictMode);
        }

        returnData.push({
          json: {
            ...item,
            processed: result,
            timestamp: new Date().toISOString(),
          },
        });
      } catch (error) {
        if (this.continueOnFail()) {
          returnData.push({
            json: {
              error: (error as Error).message,
              itemIndex: i,
            },
          });
          continue;
        }
        throw error;
      }
    }

    return [returnData];
  }

  private async processData(data: any, credentials: any): Promise<any> {
    // Custom processing logic
    return {
      original: data,
      processed: true,
      api_key_length: credentials.apiKey?.length || 0,
    };
  }

  private validateData(data: any, strict: boolean): any {
    const isValid = data !== null && data !== undefined;
    return {
      valid: isValid,
      strict_mode: strict,
      type: typeof data,
    };
  }
}
typescript
// packages/nodes-custom/credentials/MyCustomApi.credentials.ts
import {
  ICredentialType,
  INodeProperties,
} from 'n8n-workflow';

export class MyCustomApi implements ICredentialType {
  name = 'myCustomApi';
  displayName = 'My Custom API';
  documentationUrl = 'https://docs.example.com/api';
  properties: INodeProperties[] = [
    {
      displayName: 'API Key',
      name: 'apiKey',
      type: 'string',
      typeOptions: {
        password: true,
      },
      default: '',
      required: true,
    },
    {
      displayName: 'Base URL',
      name: 'baseUrl',
      type: 'string',
      default: 'https://api.example.com',
    },
    {
      displayName: 'Environment',
      name: 'environment',
      type: 'options',
      options: [
        { name: 'Production', value: 'production' },
        { name: 'Staging', value: 'staging' },
        { name: 'Development', value: 'development' },
      ],
      default: 'production',
    },
  ];
}
typescript
// packages/nodes-custom/nodes/MyCustomNode/MyCustomNode.node.ts
import {
  IExecuteFunctions,
  INodeExecutionData,
  INodeType,
  INodeTypeDescription,
  NodeOperationError,
} from 'n8n-workflow';

export class MyCustomNode implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'My Custom Node',
    name: 'myCustomNode',
    icon: 'file:myicon.svg',
    group: ['transform'],
    version: 1,
    subtitle: '={{$parameter["operation"]}}',
    description: 'Custom node for specific business logic',
    defaults: {
      name: 'My Custom Node',
    },
    inputs: ['main'],
    outputs: ['main'],
    credentials: [
      {
        name: 'myCustomApi',
        required: true,
      },
    ],
    properties: [
      {
        displayName: 'Operation',
        name: 'operation',
        type: 'options',
        noDataExpression: true,
        options: [
          {
            name: 'Process',
            value: 'process',
            description: 'Process data through custom logic',
          },
          {
            name: 'Validate',
            value: 'validate',
            description: 'Validate data against rules',
          },
        ],
        default: 'process',
      },
      {
        displayName: 'Input Field',
        name: 'inputField',
        type: 'string',
        default: 'data',
        required: true,
        description: 'Field to process',
      },
      {
        displayName: 'Options',
        name: 'options',
        type: 'collection',
        placeholder: 'Add Option',
        default: {},
        options: [
          {
            displayName: 'Strict Mode',
            name: 'strictMode',
            type: 'boolean',
            default: false,
            description: 'Enable strict validation',
          },
          {
            displayName: 'Output Format',
            name: 'outputFormat',
            type: 'options',
            options: [
              { name: 'JSON', value: 'json' },
              { name: 'Array', value: 'array' },
            ],
            default: 'json',
          },
        ],
      },
    ],
  };

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const items = this.getInputData();
    const returnData: INodeExecutionData[] = [];

    const operation = this.getNodeParameter('operation', 0) as string;
    const inputField = this.getNodeParameter('inputField', 0) as string;
    const options = this.getNodeParameter('options', 0, {}) as {
      strictMode?: boolean;
      outputFormat?: string;
    };

    // Get credentials
    const credentials = await this.getCredentials('myCustomApi');

    for (let i = 0; i < items.length; i++) {
      try {
        const item = items[i].json;
        const inputData = item[inputField];

        if (!inputData && options.strictMode) {
          throw new NodeOperationError(
            this.getNode(),
            `Field "${inputField}" not found in item ${i}`,
            { itemIndex: i }
          );
        }

        let result: any;

        if (operation === 'process') {
          result = await this.processData(inputData, credentials);
        } else if (operation === 'validate') {
          result = this.validateData(inputData, options.strictMode);
        }

        returnData.push({
          json: {
            ...item,
            processed: result,
            timestamp: new Date().toISOString(),
          },
        });
      } catch (error) {
        if (this.continueOnFail()) {
          returnData.push({
            json: {
              error: (error as Error).message,
              itemIndex: i,
            },
          });
          continue;
        }
        throw error;
      }
    }

    return [returnData];
  }

  private async processData(data: any, credentials: any): Promise<any> {
    // Custom processing logic
    return {
      original: data,
      processed: true,
      api_key_length: credentials.apiKey?.length || 0,
    };
  }

  private validateData(data: any, strict: boolean): any {
    const isValid = data !== null && data !== undefined;
    return {
      valid: isValid,
      strict_mode: strict,
      type: typeof data,
    };
  }
}
typescript
// packages/nodes-custom/credentials/MyCustomApi.credentials.ts
import {
  ICredentialType,
  INodeProperties,
} from 'n8n-workflow';

export class MyCustomApi implements ICredentialType {
  name = 'myCustomApi';
  displayName = 'My Custom API';
  documentationUrl = 'https://docs.example.com/api';
  properties: INodeProperties[] = [
    {
      displayName: 'API Key',
      name: 'apiKey',
      type: 'string',
      typeOptions: {
        password: true,
      },
      default: '',
      required: true,
    },
    {
      displayName: 'Base URL',
      name: 'baseUrl',
      type: 'string',
      default: 'https://api.example.com',
    },
    {
      displayName: 'Environment',
      name: 'environment',
      type: 'options',
      options: [
        { name: 'Production', value: 'production' },
        { name: 'Staging', value: 'staging' },
        { name: 'Development', value: 'development' },
      ],
      default: 'production',
    },
  ];
}

8. Workflow Templates and Subworkflows

8. 工作流模板与子工作流

json
{
  "name": "Main Orchestrator Workflow",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "orchestrate",
        "responseMode": "lastNode"
      },
      "id": "webhook",
      "name": "Start",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "workflowId": "={{ $env.DATA_VALIDATION_WORKFLOW_ID }}",
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "data": "={{ $json.payload }}",
            "rules": "={{ $json.validation_rules }}"
          }
        }
      },
      "id": "validate",
      "name": "Run Validation Workflow",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1,
      "position": [460, 300]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.is_valid }}",
              "value2": true
            }
          ]
        }
      },
      "id": "check-valid",
      "name": "Is Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [680, 300]
    },
    {
      "parameters": {
        "workflowId": "={{ $env.PROCESSING_WORKFLOW_ID }}",
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "validated_data": "={{ $json.data }}"
          }
        }
      },
      "id": "process",
      "name": "Run Processing Workflow",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1,
      "position": [900, 200]
    },
    {
      "parameters": {
        "workflowId": "={{ $env.ERROR_HANDLER_WORKFLOW_ID }}",
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "error_data": "={{ $json }}",
            "source": "orchestrator"
          }
        }
      },
      "id": "error-workflow",
      "name": "Run Error Handler",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1,
      "position": [900, 400]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [{ "node": "Run Validation Workflow", "type": "main", "index": 0 }]
      ]
    },
    "Run Validation Workflow": {
      "main": [
        [{ "node": "Is Valid?", "type": "main", "index": 0 }]
      ]
    },
    "Is Valid?": {
      "main": [
        [{ "node": "Run Processing Workflow", "type": "main", "index": 0 }],
        [{ "node": "Run Error Handler", "type": "main", "index": 0 }]
      ]
    }
  }
}
json
{
  "name": "Main Orchestrator Workflow",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "orchestrate",
        "responseMode": "lastNode"
      },
      "id": "webhook",
      "name": "Start",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "workflowId": "={{ $env.DATA_VALIDATION_WORKFLOW_ID }}",
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "data": "={{ $json.payload }}",
            "rules": "={{ $json.validation_rules }}"
          }
        }
      },
      "id": "validate",
      "name": "Run Validation Workflow",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1,
      "position": [460, 300]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.is_valid }}",
              "value2": true
            }
          ]
        }
      },
      "id": "check-valid",
      "name": "Is Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [680, 300]
    },
    {
      "parameters": {
        "workflowId": "={{ $env.PROCESSING_WORKFLOW_ID }}",
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "validated_data": "={{ $json.data }}"
          }
        }
      },
      "id": "process",
      "name": "Run Processing Workflow",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1,
      "position": [900, 200]
    },
    {
      "parameters": {
        "workflowId": "={{ $env.ERROR_HANDLER_WORKFLOW_ID }}",
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "error_data": "={{ $json }}",
            "source": "orchestrator"
          }
        }
      },
      "id": "error-workflow",
      "name": "Run Error Handler",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1,
      "position": [900, 400]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [{ "node": "Run Validation Workflow", "type": "main", "index": 0 }]
      ]
    },
    "Run Validation Workflow": {
      "main": [
        [{ "node": "Is Valid?", "type": "main", "index": 0 }]
      ]
    },
    "Is Valid?": {
      "main": [
        [{ "node": "Run Processing Workflow", "type": "main", "index": 0 }],
        [{ "node": "Run Error Handler", "type": "main", "index": 0 }]
      ]
    }
  }
}

Integration Examples

集成示例

Integration with Slack, Google Sheets, and Email

Slack、Google Sheets与邮件集成

json
{
  "name": "Multi-Channel Notification System",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "notify",
        "responseMode": "responseNode"
      },
      "id": "webhook",
      "name": "Incoming Notification",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "jsCode": "// Determine notification channels\nconst payload = $input.item.json;\n\nconst channels = [];\n\n// Add channels based on priority\nif (payload.priority === 'high' || payload.priority === 'critical') {\n  channels.push('slack');\n  channels.push('email');\n}\n\nif (payload.log_to_sheet) {\n  channels.push('sheets');\n}\n\nif (channels.length === 0) {\n  channels.push('slack'); // Default\n}\n\nreturn {\n  ...payload,\n  channels,\n  processed_at: new Date().toISOString()\n};"
      },
      "id": "router",
      "name": "Route Notification",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.channels.join(',') }}",
              "operation": "contains",
              "value2": "slack"
            }
          ]
        }
      },
      "id": "slack-filter",
      "name": "Send to Slack?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [680, 200]
    },
    {
      "parameters": {
        "channel": "={{ $json.priority === 'critical' ? '#critical-alerts' : '#notifications' }}",
        "text": "=*{{ $json.title }}*\n\n{{ $json.message }}\n\nPriority: {{ $json.priority }}\nSource: {{ $json.source }}",
        "attachments": [],
        "otherOptions": {}
      },
      "id": "slack",
      "name": "Post to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [900, 200],
      "credentials": {
        "slackApi": {
          "id": "3",
          "name": "Slack Bot"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.channels.join(',') }}",
              "operation": "contains",
              "value2": "email"
            }
          ]
        }
      },
      "id": "email-filter",
      "name": "Send Email?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "sendTo": "={{ $json.recipient_email || 'team@example.com' }}",
        "subject": "=[{{ $json.priority.toUpperCase() }}] {{ $json.title }}",
        "emailType": "html",
        "html": "=<h2>{{ $json.title }}</h2>\n<p>{{ $json.message }}</p>\n<hr>\n<p><strong>Priority:</strong> {{ $json.priority }}</p>\n<p><strong>Source:</strong> {{ $json.source }}</p>\n<p><strong>Time:</strong> {{ $json.processed_at }}</p>"
      },
      "id": "email",
      "name": "Send Email",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [900, 300],
      "credentials": {
        "smtp": {
          "id": "5",
          "name": "SMTP"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.channels.join(',') }}",
              "operation": "contains",
              "value2": "sheets"
            }
          ]
        }
      },
      "id": "sheets-filter",
      "name": "Log to Sheets?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [680, 400]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $env.GOOGLE_SHEET_ID }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Notifications",
          "mode": "list"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Timestamp": "={{ $json.processed_at }}",
            "Title": "={{ $json.title }}",
            "Message": "={{ $json.message }}",
            "Priority": "={{ $json.priority }}",
            "Source": "={{ $json.source }}"
          }
        }
      },
      "id": "sheets",
      "name": "Log to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.1,
      "position": [900, 400],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "6",
          "name": "Google Sheets"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, channels: $json.channels }) }}"
      },
      "id": "respond",
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [1120, 300]
    }
  ],
  "connections": {
    "Incoming Notification": {
      "main": [
        [{ "node": "Route Notification", "type": "main", "index": 0 }]
      ]
    },
    "Route Notification": {
      "main": [
        [
          { "node": "Send to Slack?", "type": "main", "index": 0 },
          { "node": "Send Email?", "type": "main", "index": 0 },
          { "node": "Log to Sheets?", "type": "main", "index": 0 }
        ]
      ]
    },
    "Send to Slack?": {
      "main": [
        [{ "node": "Post to Slack", "type": "main", "index": 0 }]
      ]
    },
    "Send Email?": {
      "main": [
        [{ "node": "Send Email", "type": "main", "index": 0 }]
      ]
    },
    "Log to Sheets?": {
      "main": [
        [{ "node": "Log to Google Sheets", "type": "main", "index": 0 }]
      ]
    },
    "Post to Slack": {
      "main": [
        [{ "node": "Respond", "type": "main", "index": 0 }]
      ]
    },
    "Send Email": {
      "main": [
        [{ "node": "Respond", "type": "main", "index": 0 }]
      ]
    },
    "Log to Google Sheets": {
      "main": [
        [{ "node": "Respond", "type": "main", "index": 0 }]
      ]
    }
  }
}
json
{
  "name": "Multi-Channel Notification System",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "notify",
        "responseMode": "responseNode"
      },
      "id": "webhook",
      "name": "Incoming Notification",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "jsCode": "// Determine notification channels\
const payload = $input.item.json;\
\
const channels = [];\
\
// Add channels based on priority\
if (payload.priority === 'high' || payload.priority === 'critical') {\
  channels.push('slack');\
  channels.push('email');\
}\
\
if (payload.log_to_sheet) {\
  channels.push('sheets');\
}\
\
if (channels.length === 0) {\
  channels.push('slack'); // Default\
}\
\
return {\
  ...payload,\
  channels,\
  processed_at: new Date().toISOString()\
};"
      },
      "id": "router",
      "name": "Route Notification",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.channels.join(',') }}",
              "operation": "contains",
              "value2": "slack"
            }
          ]
        }
      },
      "id": "slack-filter",
      "name": "Send to Slack?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [680, 200]
    },
    {
      "parameters": {
        "channel": "={{ $json.priority === 'critical' ? '#critical-alerts' : '#notifications' }}",
        "text": "=*{{ $json.title }}*\
\
{{ $json.message }}\
\
Priority: {{ $json.priority }}\
Source: {{ $json.source }}",
        "attachments": [],
        "otherOptions": {}
      },
      "id": "slack",
      "name": "Post to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [900, 200],
      "credentials": {
        "slackApi": {
          "id": "3",
          "name": "Slack Bot"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.channels.join(',') }}",
              "operation": "contains",
              "value2": "email"
            }
          ]
        }
      },
      "id": "email-filter",
      "name": "Send Email?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "sendTo": "={{ $json.recipient_email || 'team@example.com' }}",
        "subject": "=[{{ $json.priority.toUpperCase() }}] {{ $json.title }}",
        "emailType": "html",
        "html": "=<h2>{{ $json.title }}</h2>\
<p>{{ $json.message }}</p>\
<hr>\
<p><strong>Priority:</strong> {{ $json.priority }}</p>\
<p><strong>Source:</strong> {{ $json.source }}</p>\
<p><strong>Time:</strong> {{ $json.processed_at }}</p>"
      },
      "id": "email",
      "name": "Send Email",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [900, 300],
      "credentials": {
        "smtp": {
          "id": "5",
          "name": "SMTP"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.channels.join(',') }}",
              "operation": "contains",
              "value2": "sheets"
            }
          ]
        }
      },
      "id": "sheets-filter",
      "name": "Log to Sheets?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [680, 400]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $env.GOOGLE_SHEET_ID }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Notifications",
          "mode": "list"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Timestamp": "={{ $json.processed_at }}",
            "Title": "={{ $json.title }}",
            "Message": "={{ $json.message }}",
            "Priority": "={{ $json.priority }}",
            "Source": "={{ $json.source }}"
          }
        }
      },
      "id": "sheets",
      "name": "Log to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.1,
      "position": [900, 400],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "6",
          "name": "Google Sheets"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, channels: $json.channels }) }}"
      },
      "id": "respond",
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [1120, 300]
    }
  ],
  "connections": {
    "Incoming Notification": {
      "main": [
        [{ "node": "Route Notification", "type": "main", "index": 0 }]
      ]
    },
    "Route Notification": {
      "main": [
        [
          { "node": "Send to Slack?", "type": "main", "index": 0 },
          { "node": "Send Email?", "type": "main", "index": 0 },
          { "node": "Log to Sheets?", "type": "main", "index": 0 }
        ]
      ]
    },
    "Send to Slack?": {
      "main": [
        [{ "node": "Post to Slack", "type": "main", "index": 0 }]
      ]
    },
    "Send Email?": {
      "main": [
        [{ "node": "Send Email", "type": "main", "index": 0 }]
      ]
    },
    "Log to Sheets?": {
      "main": [
        [{ "node": "Log to Google Sheets", "type": "main", "index": 0 }]
      ]
    },
    "Post to Slack": {
      "main": [
        [{ "node": "Respond", "type": "main", "index": 0 }]
      ]
    },
    "Send Email": {
      "main": [
        [{ "node": "Respond", "type": "main", "index": 0 }]
      ]
    },
    "Log to Google Sheets": {
      "main": [
        [{ "node": "Respond", "type": "main", "index": 0 }]
      ]
    }
  }
}

Integration with GitHub and Jira

GitHub与Jira集成

json
{
  "name": "GitHub to Jira Sync",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "github-webhook",
        "options": {}
      },
      "id": "github-webhook",
      "name": "GitHub Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.action }}",
              "operation": "equals",
              "value2": "opened"
            }
          ]
        }
      },
      "id": "filter-opened",
      "name": "Is New Issue?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "jsCode": "// Map GitHub issue to Jira format\nconst issue = $input.item.json.issue;\n\n// Map labels to Jira priority\nconst labels = issue.labels.map(l => l.name);\nlet priority = 'Medium';\nif (labels.includes('critical') || labels.includes('urgent')) {\n  priority = 'Highest';\n} else if (labels.includes('high')) {\n  priority = 'High';\n} else if (labels.includes('low')) {\n  priority = 'Low';\n}\n\n// Map to Jira issue type\nlet issueType = 'Task';\nif (labels.includes('bug')) {\n  issueType = 'Bug';\n} else if (labels.includes('feature')) {\n  issueType = 'Story';\n}\n\nreturn {\n  summary: `[GitHub] ${issue.title}`,\n  description: `${issue.body}\\n\\n---\\nGitHub Issue: ${issue.html_url}\\nCreated by: ${issue.user.login}`,\n  priority: priority,\n  issueType: issueType,\n  labels: labels.filter(l => !['bug', 'feature', 'critical', 'urgent', 'high', 'low'].includes(l)),\n  github_issue_number: issue.number,\n  github_repo: $input.item.json.repository.full_name\n};"
      },
      "id": "map-to-jira",
      "name": "Map to Jira Format",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "resource": "issue",
        "operation": "create",
        "project": "={{ $env.JIRA_PROJECT_KEY }}",
        "issueType": "={{ $json.issueType }}",
        "summary": "={{ $json.summary }}",
        "additionalFields": {
          "description": "={{ $json.description }}",
          "priority": "={{ $json.priority }}",
          "labels": "={{ $json.labels }}"
        }
      },
      "id": "create-jira",
      "name": "Create Jira Issue",
      "type": "n8n-nodes-base.jira",
      "typeVersion": 1,
      "position": [900, 300],
      "credentials": {
        "jiraSoftwareCloudApi": {
          "id": "7",
          "name": "Jira"
        }
      }
    },
    {
      "parameters": {
        "owner": "={{ $('Map to Jira Format').item.json.github_repo.split('/')[0] }}",
        "repository": "={{ $('Map to Jira Format').item.json.github_repo.split('/')[1] }}",
        "issueNumber": "={{ $('Map to Jira Format').item.json.github_issue_number }}",
        "body": "=Jira issue created: {{ $json.key }}\n\nLink: {{ $env.JIRA_BASE_URL }}/browse/{{ $json.key }}"
      },
      "id": "comment-github",
      "name": "Comment on GitHub",
      "type": "n8n-nodes-base.github",
      "typeVersion": 1,
      "position": [1120, 300],
      "credentials": {
        "githubApi": {
          "id": "8",
          "name": "GitHub"
        }
      }
    }
  ],
  "connections": {
    "GitHub Webhook": {
      "main": [
        [{ "node": "Is New Issue?", "type": "main", "index": 0 }]
      ]
    },
    "Is New Issue?": {
      "main": [
        [{ "node": "Map to Jira Format", "type": "main", "index": 0 }]
      ]
    },
    "Map to Jira Format": {
      "main": [
        [{ "node": "Create Jira Issue", "type": "main", "index": 0 }]
      ]
    },
    "Create Jira Issue": {
      "main": [
        [{ "node": "Comment on GitHub", "type": "main", "index": 0 }]
      ]
    }
  }
}
json
{
  "name": "GitHub to Jira Sync",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "github-webhook",
        "options": {}
      },
      "id": "github-webhook",
      "name": "GitHub Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.action }}",
              "operation": "equals",
              "value2": "opened"
            }
          ]
        }
      },
      "id": "filter-opened",
      "name": "Is New Issue?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "jsCode": "// Map GitHub issue to Jira format\
const issue = $input.item.json.issue;\
\
// Map labels to Jira priority\
const labels = issue.labels.map(l => l.name);\
let priority = 'Medium';\
if (labels.includes('critical') || labels.includes('urgent')) {\
  priority = 'Highest';\
} else if (labels.includes('high')) {\
  priority = 'High';\
} else if (labels.includes('low')) {\
  priority = 'Low';\
}\
\
// Map to Jira issue type\
let issueType = 'Task';\
if (labels.includes('bug')) {\
  issueType = 'Bug';\
} else if (labels.includes('feature')) {\
  issueType = 'Story';\
}\
\
return {\
  summary: `[GitHub] ${issue.title}`,\
  description: `${issue.body}\\\
\\\
---\\\
GitHub Issue: ${issue.html_url}\\\
Created by: ${issue.user.login}`,\
  priority: priority,\
  issueType: issueType,\
  labels: labels.filter(l => !['bug', 'feature', 'critical', 'urgent', 'high', 'low'].includes(l)),\
  github_issue_number: issue.number,\
  github_repo: $input.item.json.repository.full_name\
};"
      },
      "id": "map-to-jira",
      "name": "Map to Jira Format",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "resource": "issue",
        "operation": "create",
        "project": "={{ $env.JIRA_PROJECT_KEY }}",
        "issueType": "={{ $json.issueType }}",
        "summary": "={{ $json.summary }}",
        "additionalFields": {
          "description": "={{ $json.description }}",
          "priority": "={{ $json.priority }}",
          "labels": "={{ $json.labels }}"
        }
      },
      "id": "create-jira",
      "name": "Create Jira Issue",
      "type": "n8n-nodes-base.jira",
      "typeVersion": 1,
      "position": [900, 300],
      "credentials": {
        "jiraSoftwareCloudApi": {
          "id": "7",
          "name": "Jira"
        }
      }
    },
    {
      "parameters": {
        "owner": "={{ $('Map to Jira Format').item.json.github_repo.split('/')[0] }}",
        "repository": "={{ $('Map to Jira Format').item.json.github_repo.split('/')[1] }}",
        "issueNumber": "={{ $('Map to Jira Format').item.json.github_issue_number }}",
        "body": "=Jira issue created: {{ $json.key }}\
\
Link: {{ $env.JIRA_BASE_URL }}/browse/{{ $json.key }}"
      },
      "id": "comment-github",
      "name": "Comment on GitHub",
      "type": "n8n-nodes-base.github",
      "typeVersion": 1,
      "position": [1120, 300],
      "credentials": {
        "githubApi": {
          "id": "8",
          "name": "GitHub"
        }
      }
    }
  ],
  "connections": {
    "GitHub Webhook": {
      "main": [
        [{ "node": "Is New Issue?", "type": "main", "index": 0 }]
      ]
    },
    "Is New Issue?": {
      "main": [
        [{ "node": "Map to Jira Format", "type": "main", "index": 0 }]
      ]
    },
    "Map to Jira Format": {
      "main": [
        [{ "node": "Create Jira Issue", "type": "main", "index": 0 }]
      ]
    },
    "Create Jira Issue": {
      "main": [
        [{ "node": "Comment on GitHub", "type": "main", "index": 0 }]
      ]
    }
  }
}

Best Practices

最佳实践

1. Workflow Organization

1. 工作流组织

workflows/
├── core/
│   ├── data-validation.json
│   ├── error-handler.json
│   └── notification-router.json
├── integrations/
│   ├── slack-bot.json
│   ├── github-sync.json
│   └── crm-sync.json
├── scheduled/
│   ├── daily-reports.json
│   └── weekly-cleanup.json
└── webhooks/
    ├── payment-processor.json
    └── form-handler.json
workflows/
├── core/
│   ├── data-validation.json
│   ├── error-handler.json
│   └── notification-router.json
├── integrations/
│   ├── slack-bot.json
│   ├── github-sync.json
│   └── crm-sync.json
├── scheduled/
│   ├── daily-reports.json
│   └── weekly-cleanup.json
└── webhooks/
    ├── payment-processor.json
    └── form-handler.json

2. Error Handling Patterns

2. 错误处理模式

javascript
// Always use try-catch in Code nodes
try {
  const result = await riskyOperation();
  return { success: true, data: result };
} catch (error) {
  // Log error details
  console.error('Operation failed:', error.message);

  // Return structured error for downstream handling
  return {
    success: false,
    error: error.message,
    timestamp: new Date().toISOString(),
    retryable: error.code !== 'PERMANENT_FAILURE'
  };
}
javascript
// Always use try-catch in Code nodes
try {
  const result = await riskyOperation();
  return { success: true, data: result };
} catch (error) {
  // Log error details
  console.error('Operation failed:', error.message);

  // Return structured error for downstream handling
  return {
    success: false,
    error: error.message,
    timestamp: new Date().toISOString(),
    retryable: error.code !== 'PERMANENT_FAILURE'
  };
}

3. Security Best Practices

3. 安全最佳实践

yaml
undefined
yaml
undefined

Production environment variables

Production environment variables

N8N_ENCRYPTION_KEY: "32-character-secure-key" N8N_USER_MANAGEMENT_JWT_SECRET: "secure-jwt-secret" N8N_BASIC_AUTH_ACTIVE: "true" N8N_BASIC_AUTH_USER: "admin" N8N_BASIC_AUTH_PASSWORD: "${SECURE_PASSWORD}"
N8N_ENCRYPTION_KEY: "32-character-secure-key" N8N_USER_MANAGEMENT_JWT_SECRET: "secure-jwt-secret" N8N_BASIC_AUTH_ACTIVE: "true" N8N_BASIC_AUTH_USER: "admin" N8N_BASIC_AUTH_PASSWORD: "${SECURE_PASSWORD}"

Webhook security

Webhook security

WEBHOOK_URL: "https://n8n.example.com/" N8N_WEBHOOK_TUNNEL_URL: "" # Disable tunnel in production
WEBHOOK_URL: "https://n8n.example.com/" N8N_WEBHOOK_TUNNEL_URL: "" # Disable tunnel in production

Database encryption

Database encryption

DB_POSTGRESDB_SSL_ENABLED: "true" DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED: "true"
undefined
DB_POSTGRESDB_SSL_ENABLED: "true" DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED: "true"
undefined

4. Performance Optimization

4. 性能优化

javascript
// Batch processing for large datasets
const BATCH_SIZE = 100;
const items = $input.all();
const results = [];

for (let i = 0; i < items.length; i += BATCH_SIZE) {
  const batch = items.slice(i, i + BATCH_SIZE);

  // Process batch
  const batchResults = await Promise.all(
    batch.map(item => processItem(item.json))
  );

  results.push(...batchResults);

  // Optional: Add delay between batches to avoid rate limits
  if (i + BATCH_SIZE < items.length) {
    await new Promise(resolve => setTimeout(resolve, 100));
  }
}

return results;
javascript
// Batch processing for large datasets
const BATCH_SIZE = 100;
const items = $input.all();
const results = [];

for (let i = 0; i < items.length; i += BATCH_SIZE) {
  const batch = items.slice(i, i + BATCH_SIZE);

  // Process batch
  const batchResults = await Promise.all(
    batch.map(item => processItem(item.json))
  );

  results.push(...batchResults);

  // Optional: Add delay between batches to avoid rate limits
  if (i + BATCH_SIZE < items.length) {
    await new Promise(resolve => setTimeout(resolve, 100));
  }
}

return results;

5. Testing Workflows

5. 工作流测试

bash
undefined
bash
undefined

Export workflow for version control

Export workflow for version control

curl -X GET "http://localhost:5678/api/v1/workflows/1"
-H "X-N8N-API-KEY: your-api-key"
-o workflow-backup.json
curl -X GET "http://localhost:5678/api/v1/workflows/1" \ -H "X-N8N-API-KEY: your-api-key" \ -o workflow-backup.json

Import workflow

Import workflow

curl -X POST "http://localhost:5678/api/v1/workflows"
-H "X-N8N-API-KEY: your-api-key"
-H "Content-Type: application/json"
-d @workflow-backup.json
curl -X POST "http://localhost:5678/api/v1/workflows" \ -H "X-N8N-API-KEY: your-api-key" \ -H "Content-Type: application/json" \ -d @workflow-backup.json

Execute workflow via API

Execute workflow via API

curl -X POST "http://localhost:5678/api/v1/workflows/1/activate"
-H "X-N8N-API-KEY: your-api-key"
undefined
curl -X POST "http://localhost:5678/api/v1/workflows/1/activate" \ -H "X-N8N-API-KEY: your-api-key"
undefined

Troubleshooting

故障排除

Common Issues

常见问题

Issue: Webhook not receiving data
bash
undefined
问题:Webhook无法接收数据
bash
undefined

Check webhook URL

检查Webhook URL

curl -X POST https://n8n.example.com/webhook/your-path
-H "Content-Type: application/json"
-d '{"test": true}'
curl -X POST https://n8n.example.com/webhook/your-path \ -H "Content-Type: application/json" \ -d '{"test": true}'

Verify workflow is active

验证工作流是否处于激活状态

Check n8n logs

查看n8n日志

docker logs n8n-container 2>&1 | grep -i webhook
docker logs n8n-container 2>&1 | grep -i webhook

Ensure WEBHOOK_URL is correctly set

确保WEBHOOK_URL设置正确

echo $WEBHOOK_URL

**Issue: Credentials not working**
```bash
echo $WEBHOOK_URL

**问题:凭证无法正常工作**
```bash

Test credential manually

手动测试凭证

curl -X GET "https://api.example.com/test"
-H "Authorization: Bearer YOUR_TOKEN"
curl -X GET "https://api.example.com/test" \ -H "Authorization: Bearer YOUR_TOKEN"

Check encryption key is set

检查加密密钥是否已设置

Credentials are encrypted - changing key breaks existing credentials

凭证已加密 - 修改密钥会导致现有凭证失效

echo $N8N_ENCRYPTION_KEY | wc -c # Should be 32+ characters

**Issue: Workflow execution timeout**
```yaml
echo $N8N_ENCRYPTION_KEY | wc -c # 长度应不少于32个字符

**问题:工作流执行超时**
```yaml

Increase timeout in docker-compose

在docker-compose中增加超时时间

environment:
  • EXECUTIONS_TIMEOUT=3600 # 1 hour
  • EXECUTIONS_TIMEOUT_MAX=7200 # 2 hours max

**Issue: Memory issues with large datasets**
```javascript
// Stream processing instead of loading all data
// Use pagination in HTTP Request nodes
const PAGE_SIZE = 100;
let page = 1;
let hasMore = true;
const allResults = [];

while (hasMore) {
  const response = await $http.get(
    `https://api.example.com/data?page=${page}&limit=${PAGE_SIZE}`
  );

  allResults.push(...response.data.items);
  hasMore = response.data.has_more;
  page++;
}

return allResults;
environment:
  • EXECUTIONS_TIMEOUT=3600 # 1小时
  • EXECUTIONS_TIMEOUT_MAX=7200 # 最大2小时

**问题:处理大型数据集时出现内存问题**
```javascript
// 使用流处理而非加载全部数据
// 在HTTP Request节点中使用分页
const PAGE_SIZE = 100;
let page = 1;
let hasMore = true;
const allResults = [];

while (hasMore) {
  const response = await $http.get(
    `https://api.example.com/data?page=${page}&limit=${PAGE_SIZE}`
  );

  allResults.push(...response.data.items);
  hasMore = response.data.has_more;
  page++;
}

return allResults;

Debugging Tips

调试技巧

javascript
// Add debugging output in Code nodes
console.log('Input data:', JSON.stringify($input.all(), null, 2));
console.log('Environment:', $env.NODE_ENV);
console.log('Workflow:', $workflow.name);

// Check execution context
console.log('Execution ID:', $execution.id);
console.log('Execution mode:', $execution.mode);

// Inspect previous node output
const previousOutput = $('Previous Node Name').all();
console.log('Previous output:', previousOutput);
bash
undefined
javascript
// Add debugging output in Code nodes
console.log('Input data:', JSON.stringify($input.all(), null, 2));
console.log('Environment:', $env.NODE_ENV);
console.log('Workflow:', $workflow.name);

// Check execution context
console.log('Execution ID:', $execution.id);
console.log('Execution mode:', $execution.mode);

// Inspect previous node output
const previousOutput = $('Previous Node Name').all();
console.log('Previous output:', previousOutput);
bash
undefined

View execution logs

查看执行日志

docker logs -f n8n-container 2>&1 | grep -E "(error|warn|execution)"
docker logs -f n8n-container 2>&1 | grep -E "(error|warn|execution)"

Check database for failed executions

检查数据库中的失败执行记录

docker exec -it postgres psql -U n8n -d n8n -c
"SELECT id, status, started_at FROM execution_entity WHERE status = 'error' ORDER BY started_at DESC LIMIT 10;"
undefined
docker exec -it postgres psql -U n8n -d n8n -c \ "SELECT id, status, started_at FROM execution_entity WHERE status = 'error' ORDER BY started_at DESC LIMIT 10;"
undefined

Version History

版本历史

VersionDateChanges
1.0.02026-01-17Initial release with comprehensive workflow patterns
版本日期变更
1.0.02026-01-17初始版本,包含全面的工作流模式

Resources

资源


This skill provides production-ready patterns for n8n workflow automation, tested across enterprise integration scenarios handling thousands of daily executions.

本技能提供n8n工作流自动化的生产就绪模式,已在每日处理数千次执行的企业集成场景中经过测试。",