N8n Workflow Patterns
Romuald CzlonkowskiCommunityCreated by Romuald Czlonkowski
- Skill
- Guidelines
- Automation and integrations
- MIT
- Medium risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured framework for designing n8n workflows based on six core architectural patterns: Webhook Processing, HTTP API Integration, Database Operations, AI Agent Workflows, Scheduled Tasks, and Batch Processing. It offers a comprehensive checklist for the entire workflow lifecycle, from planning and implementation to validation and deployment. The skill emphasizes native node usage, effective error handling, and performance considerations for processing data, ensuring workflows are robust, maintainable, and correctly structured.
What it does
- Defines six core architectural patterns for n8n automation.
- Provides a step-by-step workflow creation and validation checklist.
- Offers guidance on choosing the right trigger and transformation nodes.
- Details strategies for batch processing and handling API rate limits.
- Outlines best practices for error handling and workflow monitoring.
- Includes specific patterns for AI agent integration and database synchronization.
When to use it
- When designing the architecture for a new n8n automation.
- When choosing between different workflow patterns like webhooks or scheduled tasks.
- When optimizing the structure of an existing, complex workflow.
- When troubleshooting data flow or performance issues in a workflow.
- When preparing a workflow for production deployment.
Advantages
- Standardizes workflow design using proven architectural patterns.
- Reduces development time by providing clear implementation paths.
- Improves workflow reliability through structured validation and error handling.
- Helps avoid common pitfalls in node configuration and data mapping.
- Provides clear guidance on when to use specific nodes versus custom code.
Usage examples
- “How should I structure a workflow that receives data from a webhook and updates a database?”
- The agent will recommend the Webhook Processing pattern, detailing the sequence of Webhook trigger, validation, transformation, and database write nodes.
- “I need to process thousands of records from an API without hitting rate limits. What pattern should I use?”
- The agent will suggest the Batch Processing pattern, explaining how to use the SplitInBatches node to chunk data and manage API calls effectively.
- “What is the best way to handle errors in my n8n workflow?”
- The agent will outline the Error Handler pattern, including the use of Error Triggers and the 'Continue On Fail' node setting.
- “How do I build an AI agent that can query my database?”
- The agent will provide the AI Agent Workflow pattern, including the setup for the AI Agent node, memory, and database tools.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/czlonkowski/n8n-workflow-patterns/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip n8n-workflow-patterns.zip -d .agents/skills
Documentation by Romuald Czlonkowski
n8n Workflow Patterns
Proven architectural patterns for building n8n workflows.
The 6 Core Patterns
Based on analysis of real workflow usage:
Webhook Processing (Most Common)
Receive HTTP requests → Process → Output
Pattern: Webhook → Validate → Transform → Respond/Notify
Fetch from REST APIs → Transform → Store/Use
Pattern: Trigger → HTTP Request → Transform → Action → Error Handler
Read/Write/Sync database data
Pattern: Schedule → Query → Transform → Write → Verify
AI agents with tools and memory
Pattern: Trigger → AI Agent (Model + Tools + Memory) → Output
Recurring automation workflows
Pattern: Schedule → Fetch → Process → Deliver → Log
Batch Processing (below)
Process large datasets in chunks with API rate limits
Pattern: Prepare → SplitInBatches → Process per batch → Accumulate → Aggregate
Pattern Selection Guide
When to use each pattern:
Webhook Processing - Use when:
Receiving data from external systems
Building integrations (Slack commands, form submissions, GitHub webhooks)
Need instant response to events
Example: "Receive Stripe payment webhook → Update database → Send confirmation"
HTTP API Integration - Use when:
Fetching data from external APIs
Synchronizing with third-party services
Building data pipelines
Example: "Fetch GitHub issues → Transform → Create Jira tickets"
Database Operations - Use when:
Syncing between databases
Running database queries on schedule
ETL workflows
Example: "Read Postgres records → Transform → Write to MySQL"
AI Agent Workflow - Use when:
Building conversational AI
Need AI with tool access
Multi-step reasoning tasks
Example: "Chat with AI that can search docs, query database, send emails"
Scheduled Tasks - Use when:
Recurring reports or summaries
Periodic data fetching
Maintenance tasks
Example: "Daily: Fetch analytics → Generate report → Email team"
Batch Processing - Use when:
Processing large datasets that exceed API batch limits
Need to accumulate results across multiple API calls
Nested loops (e.g., multiple categories × paginated API calls per category)
Example: "Fetch products for 4 markets × 1000 per API call → Aggregate all results"
Common Workflow Components
All patterns share these building blocks:
1. Triggers
Webhook - HTTP endpoint (instant)
Schedule - Cron-based timing (periodic)
Manual - Click to execute (testing)
Polling - Check for changes (intervals)
2. Data Sources
HTTP Request - REST APIs
Database nodes - Postgres, MySQL, MongoDB
Service nodes - Slack, Google Sheets, etc.
Extract From File - CSV / XLSX / PDF from a downloaded or uploaded file
3. Transformation (native first)
Filter / IF / Switch - Drop items, route
Edit Fields (Set) - Named steps: constant items, payload objects, error and return items
Summarize / Aggregate / Split Out - Group and total, collect into an array, explode an array
Merge / Compare Datasets - Join two lists, diff them for a sync
Remove Duplicates / Sort / Limit - Dedupe (also across runs), top-N
Date & Time, XML, HTML, Crypto - Dates, markup, hashes and signatures
Code - Only the closed list (loops or recursion no node expresses, state across runs, parsing no node handles), with a
Why Code:note
The job-to-node table in using-n8n-mcp-skills maps jobs to these nodes; configs are in n8n-node-configuration → NATIVE_NODES.md. Reading a filtered slice of a nested API response for one field? A single {{ $jmespath($json, "…") }} in that field does it without extra nodes (quoting rules in n8n-expression-syntax).
4. Outputs
HTTP Request - Call APIs
Database - Write data
Communication - Email, Slack, Discord
Storage - Files, cloud storage
5. Error Handling
Error Trigger - Catch workflow errors
IF - Check for error conditions
Stop and Error - Explicit failure
Continue On Fail - Per-node setting
Workflow Creation Checklist
When building ANY workflow, follow this checklist:
Planning Phase
[ ] Identify the pattern (webhook, API, database, AI, scheduled)
[ ] List required nodes (use search_nodes)
[ ] Understand data flow (input → transform → output)
[ ] Plan error handling strategy
Implementation Phase
[ ] Create workflow with appropriate trigger
[ ] Add data source nodes
[ ] Configure authentication/credentials
[ ] Add transformation nodes (native first: Filter, IF, Summarize, Merge…; Code only with a
Why Code:note)[ ] Add output/action nodes
[ ] Configure error handling
Validation Phase
[ ] Validate each node configuration (validate_node)
[ ] Validate complete workflow (validate_workflow)
[ ] Test with sample data
[ ] Handle edge cases (empty data, errors)
Deployment Phase
[ ] Review workflow settings (execution order, timeout, error handling)
[ ] Activate workflow using
activateWorkflowoperation[ ] Monitor first executions
[ ] Document workflow purpose and data flow
Workflow lifecycle: validate, verify, test before activating
Building the nodes is the start, not the finish. Before a workflow goes live, run it through four gates — and remember the headline rule: validation passing is necessary, not sufficient. A workflow can validate clean and still drop items, pick the wrong Merge input, or post Slack messages as plain text. Clean validation means the shapes are right, not that the logic is.
Validate. Run
validate_workflowon the full JSON during build, orn8n_validate_workflow({ id })once the workflow exists on the instance. Fix every error and re-validate. This catches schema, node-config, expression, and reference errors — the structural layer.Verify the connections. Pull the workflow with
n8n_get_workflow({ id })and read theconnectionsobject directly. Validation confirms connections aren't broken; it doesn't confirm they're correct. This is where you catch the valid-but-wrong wiring: a Merge whoseuseDataOfInputdoesn't line up with the connection slot, a Switch fallback that connects to nothing, a fan-out branch that was never wired onward, an error output that goes nowhere. (See the n8n Node Configuration skill's NODE_FAMILY_GOTCHAS.md for the silent ones.)Test. Run
n8n_test_workflowand inspect the output vian8n_executions. Confirm the output shape matches what consumers expect, fan-outs all produced data, and (for webhook APIs) the status/body/headers are right. Real side effects fire during a test — writes commit, messages send, external APIs are called. If any node has a user-visible side effect, confirm with the user before running, or test against safe data first.Activate only after the first three pass — using
n8n_update_partial_workflowwith theactivateWorkflowoperation. Don't activate straight off a clean validation; an active workflow that drops data or double-sends is worse than one that never started.
Skipping any gate trades a few minutes now for debugging a live, possibly stateful, possibly traffic-bearing workflow later. The trade is never worth it.
Data Flow Patterns
Linear Flow
Trigger → Transform → Action → EndUse when: Simple workflows with single path
Branching Flow
Trigger → IF → [True Path]
└→ [False Path]Use when: Different actions based on conditions
Parallel Processing
Trigger → [Branch 1] → Merge
└→ [Branch 2] ↗Use when: Independent operations that can run simultaneously
Loop Pattern
Trigger → Split in Batches → Process → Loop (until done)Use when: Processing large datasets in chunks
Error Handler Pattern
Main Flow → [Success Path]
└→ [Error Trigger → Error Handler]Use when: Need separate error handling workflow
Batch Processing Pattern
SplitInBatches Loop
The SplitInBatches node splits a large dataset into smaller chunks for processing. Understanding its outputs is critical:
main[0]= done — fires ONCE after all batches completemain[1]= each batch — fires per batch (this is the loop body)
Prepare Items → SplitInBatches → [main[1]: Process Batch] → (loops back)
[main[0]: Done] → Limit 1 → AggregateAlways add a Limit 1 node after the done output.
Choosing batchSize (the cost lever)
A SplitInBatches loop re-runs its whole body once per iteration — ~0.8 ms/iteration of engine overhead plus the body's own cost — so total ≈ ⌈items / batchSize⌉ × (overhead + body). batchSize is a direct speed dial:
Pick the largest batch your real constraint allows (API page size, rate limit, memory). Bigger batches = fewer iterations = less overhead; the body still sees every item.
batchSize: 1is the expensive extreme — one full engine pass per item. Use it only when you must act on a single item at a time (nested-loop control, or an API that takes exactly one id).If you're looping only to "go over the items" with no external constraint, you don't need the loop: connect the source to the consumer. Per-item execution handles it, and native transform nodes process all items in one call.
Cross-Iteration Data
After the loop, $('Node Inside Loop').all() returns ONLY the last batch's items. To accumulate across all iterations, use $getWorkflowStaticData('global') in a Code node inside the loop. See the n8n Code JavaScript skill for the full pattern.
Nested Loops
When processing N categories × M items per category (where an API has a batch limit):
Define Categories (N items)
→ Outer Loop (SplitInBatches, batchSize=1)
→ Prepare category data
→ Inner Loop (SplitInBatches, batchSize=1000)
→ API Call → Verify → (loops back to Inner Loop via main[1])
→ Inner done[0] → Rate Limit Delay → back to Outer Loop
→ Outer done[0] → Limit 1 → Final AggregateWiring gotcha: The inner done[0] must connect back to the OUTER loop input, not to the aggregate. The outer done[0] feeds the final aggregate.
API Pagination
Paging through one endpoint is HTTP Request's pagination option, not a loop (page-number, offset, cursor and next-URL APIs, with a page cap — see n8n-node-configuration → NATIVE_NODES.md). The loop below is for sweeps the option can't express, such as an API without multi-ID filtering that you walk with id_from + date windows, each window its own query:
Schedule → Set Date Window → Fetch Page → Process
→ IF has more? → [true] Update id_from → Fetch Page (loop)
→ [false] → Aggregate → OutputDry-Run / Verification Tolerance
When testing with API write nodes disabled (for dry runs), downstream verification nodes receive the request body instead of the response. Make verification tolerant:
// In verification Code node
const body = $input.first().json;
const looksLikeRequest = body.method && body.parameters && !body.status;
if (looksLikeRequest) {
return [{ json: { status: 'SKIPPED', message: 'Upstream disabled for testing' }}];
}
// Normal response verification below...Performance on the hot path
When a workflow processes thousands of items with little I/O, its speed is set by how many times n8n crosses a per-item / per-iteration boundary — each crossing sets up an execution context and copies the items. Four architecture choices dominate:
Consolidating a hot chain into one Code node is an exception, not a default. Every node→node hop re-copies all items, so on a measured hot path — thousands of items per run, transform work rather than I/O dominating — one All-Items Code node can beat a long chain. That is the closed list's "heavy all-items work" case: record it in the node's
Why Code:note. Below that, native nodes win on readability.If Code stays, use "Run Once for All Items," not "Each Item." Each Item pays a full sandbox per item; a chain of Each-Item Code nodes is the worst case, because the per-item tax multiplies by node count.
Maximize batchSize in SplitInBatches loops (see the Batch Processing pattern above) — iterations are the cost.
Don't micro-optimize expressions — complexity is free; node and iteration count are what you pay for.
But profile first. Most production workflows are I/O-bound — sequential HTTP / DB / Sheets calls (hundreds of ms each) dwarf all of the above. These rules matter when transform work is the floor, or when an anti-pattern (Each-Item Code, batchSize 1, long per-item chains) turns a cheap operation into a slow one. Below a few hundred items, none of it matters. The n8n Code JavaScript skill has the full measured model.
Integration-Specific Gotchas
Google Sheets
NEVER use
appendon sheets with formula columns — it breaks formulas. Use Google Sheets APIvalues.update(PUT) via HTTP Request node with agoogleApicredentialWrite numbers, not strings for formula-dependent columns — string "4.98" breaks
ADD()formulas. Convert in the column's expression:{{ $json.price.toNumber() }}or{{ parseFloat($json.price) }}Per-item execution trap: Google Sheets nodes execute once per input item. If you need a single bulk write, collect the rows into one item with an Aggregate node first
UNFORMATTED_VALUE returns numbers, not text like "N/A" — filter explicitly with a Filter node
Google Drive
convertToGoogleDocument: truecreates a Google Doc (text), NOT a Google Sheet — to upload a CSV for download, omit this option entirelyCSV download link format:
https://drive.google.com/uc?id={fileId}&export=download— use instead of/viewlinks
Bidirectional Threshold Checking
When comparing values (prices, quantities, metrics), always check both directions:
// ❌ Only catches increases
if (diff > threshold) { flag(); }
// ✅ Catches both spikes AND crashes — both are data-quality signals
if (Math.abs(diff) > threshold) { flag(); }Common Gotchas
1. Webhook Data Structure
Problem: Can't access webhook payload data
Solution: Data is nested under $json.body
❌ {{$json.email}}
✅ {{$json.body.email}}See: n8n Expression Syntax skill
2. Multiple Input Items
Problem: Node processes all input items, but I only want one
Solution: Use "Execute Once" mode or process first item only
{{$json[0].field}} // First item only3. Authentication Issues
Problem: API calls failing with 401/403
Solution:
Configure credentials properly
Use the "Credentials" section, not parameters
Test credentials before workflow activation
4. Node Execution Order
Problem: Nodes executing in unexpected order
Solution: Check workflow settings → Execution Order
v0: Top-to-bottom (legacy)
v1: Connection-based (recommended)
5. Expression Errors
Problem: Expressions showing as literal text
Solution: Use {{}} around expressions
See n8n Expression Syntax skill for details
Integration with Other Skills
These skills work together with Workflow Patterns:
n8n MCP Tools Expert - Use to:
Find nodes for your pattern (search_nodes)
Understand node operations (get_node)
Create workflows (n8n_create_workflow)
Deploy templates (n8n_deploy_template)
Use
tools_documentation({topic: "ai_agents_guide", depth: "full"})for AI pattern guidanceManage data tables with
n8n_manage_datatableOrganize workflows into folders with
n8n_manage_folders
n8n Expression Syntax - Use to:
Write expressions in transformation nodes
Access webhook data correctly ({{$json.body.field}})
Reference previous nodes ({{$node["Node Name"].json.field}})
n8n Node Configuration - Use to:
Configure specific operations for pattern nodes
Understand node-specific requirements
n8n Validation Expert - Use to:
Validate workflow structure
Fix validation errors
Ensure workflow correctness before deployment
Pattern Statistics
Common workflow patterns:
Most Common Triggers:
Webhook - 35%
Schedule (periodic tasks) - 28%
Manual (testing/admin) - 22%
Service triggers (Slack, email, etc.) - 15%
Most Common Transformations:
Set (field mapping) - 68%
Code (custom logic) - 42%
IF (conditional routing) - 38%
Switch (multi-condition) - 18%
Most Common Outputs:
HTTP Request (APIs) - 45%
Slack - 32%
Database writes - 28%
Email - 24%
Average Workflow Complexity:
Simple (3-5 nodes): 42%
Medium (6-10 nodes): 38%
Complex (11+ nodes): 20%
Quick Start Examples
Example 1: Simple Webhook → Slack
1. Webhook (path: "form-submit", POST)
2. Slack (post message to #notifications; the text maps $json.body fields with expressions)Example 2: Scheduled Report
1. Schedule (daily at 9 AM)
2. HTTP Request (fetch analytics)
3. Summarize (totals per group)
4. Email (send formatted report)
5. Error Trigger → Slack (notify on failure)Example 3: Database Sync
1. Schedule (every 15 minutes)
2. Postgres (query new records)
3. IF (check if records exist)
4. MySQL (insert records)
5. Postgres (update sync timestamp)Example 4: AI Assistant
1. Webhook (receive chat message)
2. AI Agent
├─ OpenAI Chat Model (ai_languageModel)
├─ HTTP Request Tool (ai_tool)
├─ Database Tool (ai_tool)
└─ Window Buffer Memory (ai_memory)
3. Webhook Response (send AI reply)Example 5: API Integration
1. Manual Trigger (for testing)
2. HTTP Request (GET /api/users; pagination option if the API pages)
3. Postgres (upsert users; map the fields in the node's column slots)No loop and no Set: Postgres processes every item, and the column slots take the mapping expressions.
Detailed Pattern Files
For comprehensive guidance on each pattern:
webhook_processing.md - Webhook patterns, data structure, response handling
http_api_integration.md - REST APIs, authentication, pagination, retries
database_operations.md - Queries, sync, transactions, batch processing
ai_agent_workflow.md - AI agents, tools, memory, langchain nodes
scheduled_tasks.md - Cron schedules, reports, maintenance tasks
Real Template Examples
From n8n template library:
Template #2947: Weather to Slack
Pattern: Scheduled Task
Nodes: Schedule → HTTP Request (weather API) → Set → Slack
Complexity: Simple (4 nodes)
Webhook Processing: Most common pattern
Most common: Form submissions, payment webhooks, chat integrations
HTTP API: Common pattern
Most common: Data fetching, third-party integrations
Database Operations: Common pattern
Most common: ETL, data sync, backup workflows
AI Agents: Growing in usage
Most common: Chatbots, content generation, data analysis
Use search_templates and get_template from n8n-mcp tools to find examples!
Best Practices
✅ Do
Start with the simplest pattern that solves your problem
Plan your workflow structure before building
Use error handling on all workflows
Test with sample data before activation
Follow the workflow creation checklist
Use descriptive node names
Document complex workflows (notes field)
Monitor workflow executions after deployment
❌ Don't
Build workflows in one shot (iterate! avg 56s between edits)
Skip validation before activation
Ignore error scenarios
Use complex patterns when simple ones suffice
Hardcode credentials in parameters
Forget to handle empty data cases
Mix multiple patterns without clear boundaries
Deploy without testing
Summary
Key Points:
6 core patterns cover 90%+ of workflow use cases
Webhook processing is the most common pattern
Use the workflow creation checklist for every workflow
Plan pattern → Select nodes → Build → Validate → Deploy
Integrate with other skills for complete workflow development
Next Steps:
Identify your use case pattern
Read the detailed pattern file
Use n8n MCP Tools Expert to find nodes
Follow the workflow creation checklist
Use n8n Validation Expert to validate
Related Skills:
n8n MCP Tools Expert - Find and configure nodes
n8n Expression Syntax - Write expressions correctly
n8n Validation Expert - Validate and fix errors
n8n Node Configuration - Configure specific operations
Files
- ATTRIBUTION.mdAdded by AutoGineer899 B
- LICENSELicence file added by AutoGineer1.1 KiB
- README.md6.9 KiB
- SKILL.md21.5 KiB
- ai_agent_workflow.md9.7 KiB
- database_operations.md16.6 KiB
- http_api_integration.md16.0 KiB
- scheduled_tasks.md16.6 KiB
- webhook_processing.md13.3 KiB
database_operations.md
Database Operations Pattern
Use Case: Read, write, sync, and manage database data in workflows.
Pattern Structure
Trigger → [Query/Read] → [Transform] → [Write/Update] → [Verify/Log]Key Characteristic: Data persistence and synchronization
Core Components
1. Trigger
Options:
Schedule - Periodic sync/maintenance (most common)
Webhook - Event-driven writes
Manual - One-time operations
2. Database Read Nodes
Supported databases:
Postgres
MySQL
MongoDB
Microsoft SQL
SQLite
Redis
And more via community nodes
3. Transform
Purpose: Map between different database schemas or formats
Typical nodes:
None - Map fields in the write node's own column slots
Edit Fields (Set) - A named step when several nodes read the mapped values
Merge / Compare Datasets - Combine sources, diff them for a sync
Summarize / Remove Duplicates - Totals, dedupe
Code - Only the closed list (see
n8n-code-javascript)
4. Database Write Nodes
Operations:
INSERT - Create new records
UPDATE - Modify existing records
UPSERT - Insert or update
DELETE - Remove records
5. Verification
Purpose: Confirm operations succeeded
Methods:
Query to verify records
Count rows affected
Log results
Common Use Cases
1. Data Synchronization
Flow: Schedule → Read Source DB → Transform → Write Target DB → Log
Example (Postgres to MySQL sync):
1. Schedule (every 15 minutes)
2. Postgres "Get changed users" (SELECT * FROM users WHERE updated_at > $1; queryReplacement = {{ $json.last_sync }})
3. MySQL (INSERT or UPDATE users; map columns in the node's column slots)
4. Postgres (UPDATE sync_log SET last_sync = NOW(); executeOnce: true)
5. Slack (notify: "Synced {{ $('Get changed users').all().length }} users"; executeOnce: true)No IF for "records exist": with no items, the downstream nodes don't run.
Incremental sync query:
SELECT *
FROM users
WHERE updated_at > $1
ORDER BY updated_at ASC
LIMIT 1000Parameters:
{
"parameters": [
"={{$node['Get Last Sync'].json.last_sync}}"
]
}2. ETL (Extract, Transform, Load)
Flow: Extract from multiple sources → Transform → Load into warehouse
Example (Consolidate data):
1. Schedule (daily at 2 AM)
2. [Parallel branches]
├─ Postgres (SELECT orders)
├─ MySQL (SELECT customers)
└─ MongoDB (SELECT products)
3. Merge (combine by fields: orders.customer_id = customers.id, then a second Merge for products; or one SQL-mode Merge with numberInputs 3)
4. Postgres (warehouse - INSERT into fact_sales; columns mapped in the node)
6. Email (send summary report)3. Data Validation & Cleanup
Flow: Schedule → Query → Validate → Update/Delete invalid records
Example (Clean orphaned records):
1. Schedule (weekly)
2. Postgres (SELECT users WHERE email IS NULL OR email = '')
3. IF (invalid records exist)
4. Postgres (UPDATE users SET status='inactive' WHERE email IS NULL)
5. Postgres (DELETE FROM users WHERE created_at < NOW() - INTERVAL '1 year' AND status='inactive')
6. Slack (alert: "Cleaned X invalid records")4. Backup & Archive
Flow: Schedule → Query → Export → Store
Example (Archive old records):
1. Schedule (monthly)
2. Postgres (SELECT * FROM orders WHERE created_at < NOW() - INTERVAL '2 years')
3. Convert To File (operation toJson)
4. Read/Write Files from Disk (save to archive.json)
5. Google Drive (upload archive)
6. Postgres (DELETE FROM orders WHERE created_at < NOW() - INTERVAL '2 years')5. Real-time Data Updates
Flow: Webhook → Parse → Update Database
Example (Update user status):
1. Webhook (receive status update)
2. Postgres (UPDATE users SET status = {{$json.body.status}} WHERE id = {{$json.body.user_id}})
3. IF (rows affected > 0)
4. Redis (SET user:{{$json.body.user_id}}:status {{$json.body.status}})
5. Webhook Response ({"success": true})Database Node Configuration
Postgres
SELECT Query
{
operation: "executeQuery",
query: "SELECT id, name, email FROM users WHERE created_at > $1 LIMIT $2",
parameters: [
"={{$json.since_date}}",
"100"
]
}INSERT
{
operation: "insert",
table: "users",
columns: "id, name, email, created_at",
values: [
{
id: "={{$json.id}}",
name: "={{$json.name}}",
email: "={{$json.email}}",
created_at: "={{$now}}"
}
]
}UPDATE
{
operation: "update",
table: "users",
updateKey: "id",
columns: "name, email, updated_at",
values: {
id: "={{$json.id}}",
name: "={{$json.name}}",
email: "={{$json.email}}",
updated_at: "={{$now}}"
}
}UPSERT (INSERT ... ON CONFLICT)
{
operation: "executeQuery",
query: `
INSERT INTO users (id, name, email)
VALUES ($1, $2, $3)
ON CONFLICT (id)
DO UPDATE SET name = $2, email = $3, updated_at = NOW()
`,
parameters: [
"={{$json.id}}",
"={{$json.name}}",
"={{$json.email}}"
]
}MySQL
SELECT with JOIN
{
operation: "executeQuery",
query: `
SELECT u.id, u.name, o.order_id, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > ?
`,
parameters: [
"={{$json.since_date}}"
]
}Bulk INSERT
{
operation: "insert",
table: "orders",
columns: "user_id, total, status",
values: $json.orders // Array of objects
}MongoDB
Find Documents
{
operation: "find",
collection: "users",
query: JSON.stringify({
created_at: { $gt: new Date($json.since_date) },
status: "active"
}),
limit: 100
}Insert Document
{
operation: "insert",
collection: "users",
document: JSON.stringify({
name: $json.name,
email: $json.email,
created_at: new Date()
})
}Update Document
{
operation: "update",
collection: "users",
query: JSON.stringify({ _id: $json.user_id }),
update: JSON.stringify({
$set: {
status: $json.status,
updated_at: new Date()
}
})
}Batch Processing
Pattern 1: Split In Batches
Use when: Processing large datasets to avoid memory issues
Postgres (SELECT 10000 records)
→ Split In Batches (100 items per batch)
→ Transform
→ MySQL (write batch)
→ Loop (until all processed)Pattern 2: Paginated Queries
Use when: Database has millions of records
Set (initialize: offset=0, limit=1000)
→ Loop Start
→ Postgres (SELECT * FROM large_table LIMIT {{$json.limit}} OFFSET {{$json.offset}})
→ IF (records returned)
├─ Process records
├─ Set (increment offset by 1000)
└─ Loop back
└─ [No records] → EndQuery:
SELECT * FROM large_table
ORDER BY id
LIMIT $1 OFFSET $2Pattern 3: Cursor-Based Pagination
Better performance for large datasets:
Set (initialize: last_id=0)
→ Loop Start
→ Postgres (SELECT * FROM table WHERE id > {{$json.last_id}} ORDER BY id LIMIT 1000)
→ IF (records returned)
├─ Process records
├─ Summarize (max of id) → the next query's last_id
└─ Loop back
└─ [No records] → EndQuery:
SELECT * FROM table
WHERE id > $1
ORDER BY id ASC
LIMIT 1000Transaction Handling
Pattern 1: BEGIN/COMMIT/ROLLBACK
For databases that support transactions:
// Node 1: Begin Transaction
{
operation: "executeQuery",
query: "BEGIN"
}
// Node 2-N: Your operations
{
operation: "executeQuery",
query: "INSERT INTO ...",
continueOnFail: true
}
// Node N+1: Commit or Rollback
{
operation: "executeQuery",
query: "={{$node['Operation'].json.error ? 'ROLLBACK' : 'COMMIT'}}"
}Pattern 2: Atomic Operations
Use database features for atomicity:
-- Upsert example (atomic)
INSERT INTO inventory (product_id, quantity)
VALUES ($1, $2)
ON CONFLICT (product_id)
DO UPDATE SET quantity = inventory.quantity + $2Pattern 3: Error Rollback
Manual rollback on error:
Try Operations:
Postgres (INSERT orders)
MySQL (INSERT order_items)
Error Trigger:
Postgres (DELETE FROM orders WHERE id = {{$json.order_id}})
MySQL (DELETE FROM order_items WHERE order_id = {{$json.order_id}})Data Transformation
Schema Mapping, type conversion, aggregation
These are native jobs:
| Job | Native way |
|---|---|
| Rename / map fields | the write node's column slots, each a short expression ({{ $json.first_name + ' ' + $json.last_name }}) |
| Type conversion | in the same expression: {{ $json.user_id.toNumber() }}, {{ $json.created_at.toDateTime().toISO() }}, {{ $json.metadata.parseJson() }}, {{ $json.active === 1 }} |
| Defaults | {{ $ifEmpty($json.status, 'active') }} |
| Totals per day / group | Summarize: split by the date field, count and sum of amount — or GROUP BY in the query itself |
| Group by a derived key (the date part of a timestamp) | Edit Fields adds day = {{ $json.created_at.toDateTime().format('yyyy-MM-dd') }}, then Summarize splits by day |
A Code node is right here only when a mapping needs loops (one source row becomes a variable number of target rows in
ways Split Out can't express) — with a Why Code: note.
Performance Optimization
1. Use Indexes
Ensure database has proper indexes:
-- Add index for sync queries
CREATE INDEX idx_users_updated_at ON users(updated_at);
-- Add index for lookups
CREATE INDEX idx_orders_user_id ON orders(user_id);2. Limit Result Sets
Always use LIMIT:
-- ✅ Good
SELECT * FROM large_table
WHERE created_at > $1
LIMIT 1000
-- ❌ Bad (unbounded)
SELECT * FROM large_table
WHERE created_at > $13. Use Prepared Statements
Parameterized queries are faster:
// ✅ Good - prepared statement
{
query: "SELECT * FROM users WHERE id = $1",
parameters: ["={{$json.id}}"]
}
// ❌ Bad - string concatenation
{
query: "SELECT * FROM users WHERE id = '={{$json.id}}'"
}4. Batch Writes
Write multiple records at once:
// ✅ Good - batch insert
{
operation: "insert",
table: "orders",
values: $json.items // Array of 100 items
}
// ❌ Bad - individual inserts in loop
// 100 separate INSERT statements5. Connection Pooling
Configure in credentials:
{
host: "db.example.com",
database: "mydb",
user: "user",
password: "pass",
// Connection pool settings
min: 2,
max: 10,
idleTimeoutMillis: 30000
}Error Handling
Pattern 1: Check Rows Affected
Database Operation (UPDATE users...)
→ IF ({{$json.rowsAffected === 0}})
└─ Alert: "No rows updated - record not found"Pattern 2: Constraint Violations
// Database operation with continueOnFail: true
{
operation: "insert",
continueOnFail: true
}
// Next node: Check for errors
IF ({{$json.error !== undefined}})
→ IF ({{$json.error.includes('duplicate key')}})
└─ Log: "Record already exists - skipping"
→ ELSE
└─ Alert: "Database error: {{$json.error}}"Pattern 3: Rollback on Error
Try Operations:
→ Database Write 1
→ Database Write 2
→ Database Write 3
Error Trigger:
→ Rollback Operations
→ Alert AdminSecurity Best Practices
1. Use Parameterized Queries (Prevent SQL Injection)
// ✅ SAFE - parameterized
{
query: "SELECT * FROM users WHERE email = $1",
parameters: ["={{$json.email}}"]
}
// ❌ DANGEROUS - SQL injection risk
{
query: "SELECT * FROM users WHERE email = '={{$json.email}}'"
}2. Least Privilege Access
Create dedicated workflow user:
-- ✅ Good - limited permissions
CREATE USER n8n_workflow WITH PASSWORD 'secure_password';
GRANT SELECT, INSERT, UPDATE ON orders TO n8n_workflow;
GRANT SELECT ON users TO n8n_workflow;
-- ❌ Bad - too much access
GRANT ALL PRIVILEGES TO n8n_workflow;3. Validate Input Data
Check before the write with an IF (or Filter) node, one condition per rule — {{ $json.email.isEmail() }} is true,
{{ ($json.name ?? '').trim().length >= 2 }} is true — and send failures to their own branch. Normalize in the write
node's slots: {{ $json.email.toLowerCase().trim() }}. For per-field error messages back to a caller, see the Code
validator exception in n8n-error-handling.
4. Encrypt Sensitive Data
Use the Crypto node (typeVersion 2), action encrypt: the passphrase or key comes from a crypto credential, never from
the workflow JSON ($credentials doesn't exist inside a Code node). Configure its mode and cipher from get_node on the
user's version. Prefer encryption the database provides (pgcrypto, column encryption) when it's available.
Common Gotchas
1. ❌ Wrong: Unbounded queries
SELECT * FROM large_table -- Could return millions✅ Correct: Use LIMIT
SELECT * FROM large_table
ORDER BY created_at DESC
LIMIT 10002. ❌ Wrong: String concatenation in queries
query: "SELECT * FROM users WHERE id = '{{$json.id}}'"✅ Correct: Parameterized queries
query: "SELECT * FROM users WHERE id = $1",
parameters: ["={{$json.id}}"]3. ❌ Wrong: No transaction for multi-step operations
INSERT into orders
INSERT into order_items // Fails → orphaned order record✅ Correct: Use transaction
BEGIN
INSERT into orders
INSERT into order_items
COMMIT (or ROLLBACK on error)4. ❌ Wrong: Processing all items at once
SELECT 1000000 records → Process all → OOM error✅ Correct: Batch processing
SELECT records → Split In Batches (1000) → Process → Loop5. ❌ Wrong: Expecting output items from write operations
INSERT INTO table ... → Next node never executes (0 output items)Database write operations (INSERT, UPDATE, DELETE) may return 0 result rows from the database engine — reliably so for raw query execution (e.g. executeQuery with an INSERT), while some database nodes return the affected rows instead.
When 0 rows come back, n8n translates this to 0 output items, silently breaking any downstream chain.
✅ Correct: Set alwaysOutputData on write nodes
INSERT INTO table ... (alwaysOutputData: true) → Next node executes with 1 empty itemSet alwaysOutputData: true on any node that executes INSERT, UPDATE, or DELETE.
This ensures at least 1 empty item ({json: {}}) flows downstream.
Tip: If downstream nodes need actual data (not the empty passthrough item), reference the upstream node directly:
$('DataSource Node').all()
Real Template Examples
From n8n template library (456 database templates):
Data Sync:
Schedule → Postgres (SELECT new records) → Transform → MySQL (INSERT)ETL Pipeline:
Schedule → [Multiple DB reads] → Merge → Transform → Warehouse (INSERT)Backup:
Schedule → Postgres (SELECT all) → JSON → Google Drive (upload)Use search_templates({query: "database"}) to find more!
Checklist for Database Workflows
Planning
[ ] Identify source and target databases
[ ] Understand schema differences
[ ] Plan transformation logic
[ ] Consider batch size for large datasets
[ ] Design error handling strategy
Implementation
[ ] Use parameterized queries (never concatenate)
[ ] Add LIMIT to all SELECT queries
[ ] Use appropriate operation (INSERT/UPDATE/UPSERT)
[ ] Configure credentials properly
[ ] Test with small dataset first
Performance
[ ] Add database indexes for queries
[ ] Use batch operations
[ ] Implement pagination for large datasets
[ ] Configure connection pooling
[ ] Monitor query execution times
Security
[ ] Use parameterized queries (SQL injection prevention)
[ ] Least privilege database user
[ ] Validate and sanitize input
[ ] Encrypt sensitive data
[ ] Never log sensitive data
Reliability
[ ] Add transaction handling if needed
[ ] Check rows affected
[ ] Handle constraint violations
[ ] Implement retry logic
[ ] Add Error Trigger workflow
Summary
Key Points:
Always use parameterized queries (prevent SQL injection)
Batch processing for large datasets
Transaction handling for multi-step operations
Limit result sets to avoid memory issues
Validate input data before writes
Pattern: Trigger → Query → Transform → Write → Verify
Related:
http_api_integration.md - Fetching data to store in DB
scheduled_tasks.md - Periodic database maintenance
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
Security
What our scanner noticed· Medium risk
- Possible data-exfiltration endpoints
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Romuald Czlonkowski
- Publisher
- Romuald Czlonkowski (Community)
- Source
- czlonkowski/n8n-skills · skills/n8n-workflow-patterns · commit e037563
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Romuald Czlonkowski or Romuald Czlonkowski.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download n8n-workflow-patterns.zip