From c3f0fc2c12cfaa4cef5900d9bc58abdd7397516e Mon Sep 17 00:00:00 2001 From: "felix.zoesch" Date: Tue, 16 Dec 2025 12:54:23 +0100 Subject: [PATCH] Add n8n workflow specialist skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comprehensive skill for n8n workflow automation including: - Workflow design and node configuration - Expression writing reference (14 KB) - Comprehensive troubleshooting guide (15 KB) - Examples for common use cases - Performance optimization tips 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- skills/n8n-workflow-specialist/EXPRESSIONS.md | 718 ++++++++++++++++++ skills/n8n-workflow-specialist/SKILL.md | 371 +++++++++ .../TROUBLESHOOTING.md | 609 +++++++++++++++ 3 files changed, 1698 insertions(+) create mode 100644 skills/n8n-workflow-specialist/EXPRESSIONS.md create mode 100644 skills/n8n-workflow-specialist/SKILL.md create mode 100644 skills/n8n-workflow-specialist/TROUBLESHOOTING.md diff --git a/skills/n8n-workflow-specialist/EXPRESSIONS.md b/skills/n8n-workflow-specialist/EXPRESSIONS.md new file mode 100644 index 0000000..6488358 --- /dev/null +++ b/skills/n8n-workflow-specialist/EXPRESSIONS.md @@ -0,0 +1,718 @@ +# n8n Expression Reference + +This guide provides comprehensive reference for writing n8n expressions. + +## Table of Contents +- [Basic Syntax](#basic-syntax) +- [Accessing Data](#accessing-data) +- [String Operations](#string-operations) +- [Number Operations](#number-operations) +- [Date & Time](#date--time) +- [Array Operations](#array-operations) +- [Object Operations](#object-operations) +- [Conditional Logic](#conditional-logic) +- [JSON Operations](#json-operations) +- [Common Patterns](#common-patterns) + +## Basic Syntax + +n8n expressions are wrapped in double curly braces: +``` +{{ expression }} +``` + +You can combine text with expressions: +``` +Hello {{ $json.name }}, your order #{{ $json.orderId }} is ready! +``` + +## Accessing Data + +### Current Item Data +```javascript +// Access field in current item +{{ $json.fieldName }} + +// Nested fields +{{ $json.user.email }} +{{ $json.data.items[0].name }} + +// Access by bracket notation +{{ $json["field-with-dashes"] }} +``` + +### Previous Node Data +```javascript +// Access data from a specific node +{{ $node["HTTP Request"].json.data }} + +// Access by node index +{{ $("HTTP Request").item.json.result }} + +// First item from previous node +{{ $node["HTTP Request"].json }} + +// Specific item by index +{{ $items("HTTP Request")[0].json.field }} +``` + +### Input Data +```javascript +// Current input item +{{ $input.item.json.field }} + +// All input items +{{ $input.all() }} + +// First input item +{{ $input.first().json.field }} + +// Last input item +{{ $input.last().json.field }} +``` + +### Binary Data +```javascript +// Access binary data +{{ $binary.data }} + +// Binary data from specific node +{{ $node["Read File"].binary.data }} +``` + +### Workflow & Execution Data +```javascript +// Workflow ID +{{ $workflow.id }} + +// Workflow name +{{ $workflow.name }} + +// Execution ID +{{ $execution.id }} + +// Execution mode (manual, trigger, webhook) +{{ $execution.mode }} + +// Current timestamp +{{ $now }} + +// Today's date at midnight +{{ $today }} +``` + +## String Operations + +### Basic Methods +```javascript +// Convert to lowercase +{{ $json.email.toLowerCase() }} + +// Convert to uppercase +{{ $json.name.toUpperCase() }} + +// Trim whitespace +{{ $json.text.trim() }} + +// Get length +{{ $json.message.length }} +``` + +### Splitting & Joining +```javascript +// Split string into array +{{ $json.email.split('@') }} + +// Get specific part after split +{{ $json.email.split('@')[1] }} + +// Join array into string +{{ $json.tags.join(', ') }} +``` + +### Search & Replace +```javascript +// Replace first occurrence +{{ $json.text.replace('old', 'new') }} + +// Replace all occurrences +{{ $json.text.replaceAll('old', 'new') }} + +// Check if string includes substring +{{ $json.text.includes('keyword') }} + +// Check if string starts with +{{ $json.url.startsWith('https://') }} + +// Check if string ends with +{{ $json.filename.endsWith('.pdf') }} + +// Find position of substring +{{ $json.text.indexOf('word') }} +``` + +### Substring & Slicing +```javascript +// Get substring (start, length) +{{ $json.text.substring(0, 10) }} + +// Slice string (start, end) +{{ $json.text.slice(0, 5) }} + +// Get first character +{{ $json.name[0] }} + +// Get last character +{{ $json.name[$json.name.length - 1] }} +``` + +### Advanced String Operations +```javascript +// Pad start with zeros +{{ $json.id.toString().padStart(5, '0') }} + +// Pad end with spaces +{{ $json.text.padEnd(20, ' ') }} + +// Repeat string +{{ '-'.repeat(10) }} + +// Match regex +{{ $json.text.match(/\d+/) }} + +// Test regex +{{ /^\d{5}$/.test($json.zipCode) }} +``` + +## Number Operations + +### Basic Math +```javascript +// Addition +{{ $json.price + 10 }} + +// Subtraction +{{ $json.total - $json.discount }} + +// Multiplication +{{ $json.quantity * $json.price }} + +// Division +{{ $json.total / $json.items }} + +// Modulo (remainder) +{{ $json.number % 2 }} + +// Exponentiation +{{ $json.base ** $json.exponent }} +``` + +### Parsing & Formatting +```javascript +// Parse string to integer +{{ parseInt($json.value) }} + +// Parse string to float +{{ parseFloat($json.price) }} + +// Round to 2 decimal places +{{ parseFloat($json.amount).toFixed(2) }} + +// Round to nearest integer +{{ Math.round($json.value) }} + +// Round up +{{ Math.ceil($json.value) }} + +// Round down +{{ Math.floor($json.value) }} +``` + +### Math Functions +```javascript +// Absolute value +{{ Math.abs($json.difference) }} + +// Maximum value +{{ Math.max($json.value1, $json.value2, $json.value3) }} + +// Minimum value +{{ Math.min($json.value1, $json.value2, $json.value3) }} + +// Random number (0-1) +{{ Math.random() }} + +// Random integer (0-99) +{{ Math.floor(Math.random() * 100) }} + +// Square root +{{ Math.sqrt($json.number) }} + +// Power +{{ Math.pow($json.base, $json.exponent) }} +``` + +### Number Validation +```javascript +// Check if number +{{ !isNaN($json.value) }} + +// Check if integer +{{ Number.isInteger($json.value) }} + +// Check if finite +{{ isFinite($json.value) }} +``` + +## Date & Time + +### Current Date & Time +```javascript +// Current timestamp (ISO format) +{{ $now }} + +// Today at midnight +{{ $today }} + +// Using DateTime (Luxon) +{{ DateTime.now().toISO() }} + +// Current timestamp in milliseconds +{{ Date.now() }} +``` + +### Creating Dates +```javascript +// From ISO string +{{ DateTime.fromISO($json.dateString) }} + +// From format +{{ DateTime.fromFormat($json.date, 'yyyy-MM-dd') }} + +// From timestamp +{{ DateTime.fromMillis($json.timestamp) }} + +// Specific date +{{ DateTime.fromObject({ year: 2024, month: 1, day: 15 }) }} +``` + +### Formatting Dates +```javascript +// ISO format +{{ DateTime.now().toISO() }} + +// Custom format +{{ DateTime.now().toFormat('yyyy-MM-dd') }} +{{ DateTime.now().toFormat('dd.MM.yyyy HH:mm:ss') }} +{{ DateTime.now().toFormat('MMMM dd, yyyy') }} + +// Locale-specific +{{ DateTime.now().toLocaleString(DateTime.DATE_FULL) }} +{{ DateTime.now().toLocaleString(DateTime.DATETIME_MED) }} + +// Relative time +{{ DateTime.now().toRelative() }} +``` + +### Date Arithmetic +```javascript +// Add time +{{ DateTime.now().plus({ days: 7 }) }} +{{ DateTime.now().plus({ hours: 2, minutes: 30 }) }} +{{ DateTime.now().plus({ months: 1 }) }} + +// Subtract time +{{ DateTime.now().minus({ days: 30 }) }} +{{ DateTime.now().minus({ years: 1 }) }} + +// Difference between dates +{{ DateTime.now().diff(DateTime.fromISO($json.startDate), 'days').days }} +{{ DateTime.now().diff(DateTime.fromISO($json.startDate), ['years', 'months', 'days']) }} +``` + +### Date Components +```javascript +// Get year +{{ DateTime.now().year }} + +// Get month (1-12) +{{ DateTime.now().month }} + +// Get day +{{ DateTime.now().day }} + +// Get hour +{{ DateTime.now().hour }} + +// Get minute +{{ DateTime.now().minute }} + +// Get day of week (1-7, Mon-Sun) +{{ DateTime.now().weekday }} + +// Get day name +{{ DateTime.now().weekdayLong }} + +// Get month name +{{ DateTime.now().monthLong }} +``` + +### Date Manipulation +```javascript +// Start of day +{{ DateTime.now().startOf('day') }} + +// End of month +{{ DateTime.now().endOf('month') }} + +// Start of week +{{ DateTime.now().startOf('week') }} + +// Set specific time +{{ DateTime.now().set({ hour: 9, minute: 0, second: 0 }) }} +``` + +## Array Operations + +### Basic Array Methods +```javascript +// Get array length +{{ $json.items.length }} + +// Access element by index +{{ $json.items[0] }} + +// Last element +{{ $json.items[$json.items.length - 1] }} + +// Join array +{{ $json.tags.join(', ') }} + +// Check if array includes value +{{ $json.items.includes('value') }} +``` + +### Transforming Arrays +```javascript +// Map: transform each element +{{ $json.users.map(user => user.email) }} +{{ $json.prices.map(p => parseFloat(p) * 1.2) }} + +// Filter: keep elements matching condition +{{ $json.users.filter(user => user.active === true) }} +{{ $json.numbers.filter(n => n > 10) }} + +// Find: get first matching element +{{ $json.users.find(user => user.id === 123) }} + +// Find index +{{ $json.items.findIndex(item => item.name === 'Product') }} + +// Some: check if any element matches +{{ $json.items.some(item => item.price > 100) }} + +// Every: check if all elements match +{{ $json.items.every(item => item.inStock === true) }} +``` + +### Array Aggregation +```javascript +// Reduce: calculate sum +{{ $json.prices.reduce((sum, price) => sum + parseFloat(price), 0) }} + +// Calculate average +{{ $json.numbers.reduce((sum, n) => sum + n, 0) / $json.numbers.length }} + +// Find maximum +{{ Math.max(...$json.numbers) }} + +// Find minimum +{{ Math.min(...$json.numbers) }} + +// Count occurrences +{{ $json.items.filter(item => item.status === 'active').length }} +``` + +### Sorting Arrays +```javascript +// Sort numbers ascending +{{ $json.numbers.sort((a, b) => a - b) }} + +// Sort numbers descending +{{ $json.numbers.sort((a, b) => b - a) }} + +// Sort strings alphabetically +{{ $json.names.sort() }} + +// Sort objects by property +{{ $json.users.sort((a, b) => a.name.localeCompare(b.name)) }} +``` + +### Array Manipulation +```javascript +// Slice: get portion of array +{{ $json.items.slice(0, 5) }} + +// Concat: combine arrays +{{ $json.array1.concat($json.array2) }} + +// Flat: flatten nested arrays +{{ $json.nestedArray.flat() }} + +// Unique values (using Set) +{{ [...new Set($json.items)] }} + +// Reverse array +{{ $json.items.reverse() }} +``` + +## Object Operations + +### Accessing Properties +```javascript +// Dot notation +{{ $json.user.name }} + +// Bracket notation +{{ $json["property-name"] }} + +// Nested access +{{ $json.data.user.addresses[0].city }} +``` + +### Object Methods +```javascript +// Get all keys +{{ Object.keys($json) }} + +// Get all values +{{ Object.values($json) }} + +// Get entries (key-value pairs) +{{ Object.entries($json) }} + +// Check if property exists +{{ $json.hasOwnProperty('email') }} + +// Check if object has any properties +{{ Object.keys($json).length > 0 }} +``` + +### Object Transformation +```javascript +// Merge objects +{{ Object.assign({}, $json.obj1, $json.obj2) }} + +// Spread operator +{{ {...$json.user, role: 'admin'} }} + +// Extract specific properties +{{ (({ name, email }) => ({ name, email }))($json.user) }} + +// Map object to array +{{ Object.entries($json).map(([key, value]) => ({ key, value })) }} +``` + +## Conditional Logic + +### Ternary Operator +```javascript +// Basic ternary +{{ $json.status === 'active' ? 'Active' : 'Inactive' }} + +// Nested ternary +{{ $json.score >= 90 ? 'A' : $json.score >= 80 ? 'B' : 'C' }} + +// With numbers +{{ $json.quantity > 0 ? $json.price * $json.quantity : 0 }} +``` + +### If Function +```javascript +// Basic if +{{ $if($json.premium === true, 'Premium User', 'Regular User') }} + +// With expressions +{{ $if($json.total > 100, $json.total * 0.9, $json.total) }} +``` + +### Logical Operators +```javascript +// AND +{{ $json.age >= 18 && $json.verified === true }} + +// OR +{{ $json.role === 'admin' || $json.role === 'moderator' }} + +// NOT +{{ !$json.deleted }} + +// Nullish coalescing +{{ $json.name ?? 'Unknown' }} + +// Default value +{{ $json.value || 'default' }} +``` + +### Comparison +```javascript +// Equality +{{ $json.status === 'completed' }} + +// Inequality +{{ $json.type !== 'test' }} + +// Greater than +{{ $json.score > 50 }} + +// Less than or equal +{{ $json.price <= 99.99 }} + +// Type checking +{{ typeof $json.value === 'string' }} +``` + +## JSON Operations + +### Parsing & Stringifying +```javascript +// Parse JSON string +{{ JSON.parse($json.jsonString) }} + +// Stringify object +{{ JSON.stringify($json.data) }} + +// Stringify with formatting +{{ JSON.stringify($json.data, null, 2) }} +``` + +### Handling JSON Errors +```javascript +// Safe JSON parse with try/catch in Code node +try { + const data = JSON.parse(items[0].json.rawData); + return [{json: data}]; +} catch (error) { + return [{json: {error: 'Invalid JSON', raw: items[0].json.rawData}}]; +} +``` + +## Common Patterns + +### Email Validation +```javascript +// Check if valid email format +{{ /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test($json.email) }} + +// Extract domain from email +{{ $json.email.split('@')[1] }} + +// Normalize email +{{ $json.email.toLowerCase().trim() }} +``` + +### URL Operations +```javascript +// Extract domain from URL +{{ new URL($json.url).hostname }} + +// Get URL parameters +{{ new URL($json.url).searchParams.get('id') }} + +// Build URL with parameters +{{ `https://api.example.com/users?id=${$json.userId}&format=json` }} +``` + +### Phone Number Formatting +```javascript +// Remove non-digits +{{ $json.phone.replace(/\D/g, '') }} + +// Format US phone +{{ $json.phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3') }} +``` + +### Price/Currency Formatting +```javascript +// Format as currency +{{ parseFloat($json.price).toFixed(2) }} + +// Add currency symbol +{{ `$${parseFloat($json.price).toFixed(2)}` }} + +// Calculate with tax +{{ (parseFloat($json.price) * 1.19).toFixed(2) }} +``` + +### Name Formatting +```javascript +// Capitalize first letter +{{ $json.name.charAt(0).toUpperCase() + $json.name.slice(1).toLowerCase() }} + +// Title case +{{ $json.name.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ') }} + +// Initials +{{ $json.firstName[0] + $json.lastName[0] }} +``` + +### Generating IDs +```javascript +// Random UUID-like string +{{ Math.random().toString(36).substring(2, 15) }} + +// Timestamp-based ID +{{ Date.now().toString(36) }} + +// Sequential ID with padding +{{ String($json.index).padStart(6, '0') }} +``` + +### Data Validation +```javascript +// Check if empty +{{ !$json.value || $json.value.trim() === '' }} + +// Check if valid number +{{ !isNaN(parseFloat($json.value)) }} + +// Check if array has items +{{ Array.isArray($json.items) && $json.items.length > 0 }} + +// Check if date is in future +{{ DateTime.fromISO($json.date) > DateTime.now() }} +``` + +### Data Cleaning +```javascript +// Remove duplicates from array +{{ [...new Set($json.items)] }} + +// Remove null/undefined values +{{ $json.items.filter(item => item != null) }} + +// Remove empty strings +{{ $json.values.filter(v => v !== '') }} + +// Trim all strings in array +{{ $json.items.map(item => item.trim()) }} +``` + +## Best Practices + +1. **Use meaningful variable names in Code nodes** +2. **Test expressions with sample data** before deploying +3. **Handle null/undefined values** with nullish coalescing (`??`) +4. **Use Set node for simple transformations**, Code node for complex logic +5. **Break complex expressions** into multiple steps for readability +6. **Check data structure** in debug panel before writing expressions +7. **Use DateTime for dates**, not JavaScript Date (better timezone support) +8. **Validate data** before processing to avoid runtime errors +9. **Document complex expressions** with comments in Code nodes +10. **Cache frequently used values** in variables to avoid repetition + +## Additional Resources + +- Official n8n Expression docs: https://docs.n8n.io/code-examples/expressions/ +- Luxon DateTime docs: https://moment.github.io/luxon/ +- JavaScript reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript diff --git a/skills/n8n-workflow-specialist/SKILL.md b/skills/n8n-workflow-specialist/SKILL.md new file mode 100644 index 0000000..eff137c --- /dev/null +++ b/skills/n8n-workflow-specialist/SKILL.md @@ -0,0 +1,371 @@ +--- +name: n8n-workflow-specialist +description: Design, configure, debug, and optimize n8n workflows. Write n8n expressions, create custom function nodes, set up webhooks, manage credentials, integrate APIs, troubleshoot workflow errors, and optimize performance. Use when building n8n automations, debugging workflow issues, writing expressions, configuring nodes, or improving workflow efficiency. +--- + +# n8n Workflow Specialist + +## Overview +This skill provides comprehensive support for n8n workflow automation, including workflow design, node configuration, expression writing, debugging, and performance optimization. + +## Instructions + +### When to Use This Skill +Activate this skill when the user needs help with: +- Designing new n8n workflows from scratch +- Configuring specific n8n nodes (HTTP Request, Webhook, Code, etc.) +- Writing n8n expressions for data transformation +- Debugging workflow errors and failed executions +- Optimizing workflow performance and costs +- Setting up API integrations and webhooks +- Managing credentials and authentication +- Creating custom Code nodes with JavaScript +- Understanding n8n workflow patterns + +### Core Capabilities + +#### 1. Workflow Design & Architecture +- **Start with the trigger**: Identify the right trigger type (Webhook, Schedule, Manual, Email, etc.) +- **Plan the flow**: Map out the sequence of operations logically +- **Data flow**: Understand how data passes between nodes +- **Error handling**: Add error workflows for robustness +- **Testing strategy**: Use sample data before production deployment + +Best practices: +- Keep workflows focused on a single purpose +- Use meaningful node names for clarity +- Group related operations together +- Add notes/sticky notes to document complex logic +- Use subworkflows for reusable components + +#### 2. Expression Writing +n8n uses a powerful expression language for data manipulation. Key syntax: + +**Accessing Data:** +- Current item: `{{ $json.fieldName }}` +- Previous node: `{{ $node["Node Name"].json.fieldName }}` +- Input data: `{{ $input.item.json.fieldName }}` +- All items: `{{ $items() }}` + +**JavaScript Methods:** +- String operations: `.toLowerCase()`, `.toUpperCase()`, `.trim()`, `.split()`, `.replace()` +- Array methods: `.map()`, `.filter()`, `.find()`, `.reduce()` +- Date operations: `new Date()`, `.toISOString()`, `DateTime.now()` + +**Conditional Logic:** +- Ternary: `{{ condition ? valueIfTrue : valueIfFalse }}` +- If function: `{{ $if(condition, trueValue, falseValue) }}` + +**Common Patterns:** +```javascript +// Format date +{{ DateTime.now().toFormat('yyyy-MM-dd') }} + +// Extract email domain +{{ $json.email.split('@')[1] }} + +// Conditional value +{{ $json.status === 'active' ? 'Active User' : 'Inactive User' }} + +// Transform array +{{ $json.users.map(user => user.email) }} + +// Parse JSON string +{{ JSON.parse($json.data) }} + +// Format currency +{{ parseFloat($json.amount).toFixed(2) }} +``` + +#### 3. Node Configuration + +**HTTP Request Node:** +1. Choose request method (GET, POST, PUT, DELETE) +2. Enter URL (can use expressions) +3. Set authentication (None, Basic, OAuth2, Header, etc.) +4. Add headers if needed +5. Configure body for POST/PUT requests +6. Test with sample data + +**Webhook Node:** +1. Choose "Webhook" trigger +2. Set HTTP method (GET, POST, etc.) +3. Configure path (optional) +4. Copy webhook URL for external services +5. Set up response (important for synchronous webhooks) +6. Test with curl or Postman + +**Code Node:** +1. Choose between "Run Once for All Items" or "Run Once for Each Item" +2. Write JavaScript code +3. Access items with `items` or `$input.item` +4. Return data: `return items;` or `return [{json: {result: 'value'}}];` +5. Use console.log() for debugging (visible in executions) + +**Set Node:** +- Transform data without code +- Add, remove, or modify fields +- Use expressions for dynamic values +- Simpler than Code node for basic transformations + +**IF Node:** +- Branch workflow based on conditions +- Configure multiple conditions with AND/OR logic +- Route data to different paths +- Essential for conditional workflows + +**Merge Node:** +- Combine data from multiple branches +- Modes: Append, Merge By Fields, Keep Key Matches +- Useful after IF nodes or parallel operations + +#### 4. Debugging Workflows + +**Common Error Types:** + +**401 Unauthorized:** +- Check credentials are configured correctly +- Verify API key hasn't expired +- Ensure correct authentication method selected +- Check required headers (Authorization, Content-Type) + +**404 Not Found:** +- Verify URL is correct +- Check if endpoint exists in API documentation +- Ensure dynamic URL expressions evaluate correctly + +**Expression Errors:** +- Verify node names match exactly (case-sensitive) +- Check JSON structure with debug panel +- Test expressions in the expression editor +- Ensure referenced fields exist in previous node output + +**Timeout Errors:** +- Increase timeout in node settings +- Check if external API is slow or down +- Consider pagination for large data sets +- Optimize queries to reduce response time + +**Debugging Tools:** +- Use the execution panel to inspect each node's output +- Enable "Continue on Fail" for error workflows +- Add "Stop and Error" nodes to catch issues +- Use console.log() in Code nodes +- Check execution logs in the workflow history + +#### 5. Performance Optimization + +**Reduce Execution Time:** +- Minimize HTTP requests (batch when possible) +- Use Set nodes instead of Code nodes for simple transformations +- Implement pagination for large datasets +- Run independent operations in parallel with Split In Batches + +**Reduce Costs:** +- Limit workflow executions with appropriate triggers +- Use static values instead of expressions when possible +- Implement caching for frequently accessed data +- Deactivate unused workflows + +**Improve Reliability:** +- Add retry logic with Error Trigger +- Implement exponential backoff for API calls +- Validate data before processing +- Use error workflows to handle failures gracefully + +#### 6. Common Integration Patterns + +**Webhook → Process → Respond:** +``` +Webhook (Wait for Webhook Call) + → Validate Data (IF node) + → Process (HTTP Request / Code) + → Respond to Webhook +``` + +**Schedule → Fetch → Transform → Store:** +``` +Schedule Trigger (Cron) + → Fetch Data (HTTP Request) + → Transform (Set / Code) + → Store (Database / Google Sheets) +``` + +**Email → Parse → Notify:** +``` +Email Trigger (IMAP) + → Extract Attachments + → Parse Content (Code) + → Send Notification (Slack / Discord) +``` + +**Event → Enrich → Multi-destination:** +``` +Webhook + → Fetch Additional Data (HTTP Request) + → IF (Route by condition) + → Path 1 (Slack notification) + → Path 2 (Database update) + → Path 3 (Email alert) +``` + +## Examples + +### Example 1: Creating an Email-to-Slack Automation + +**Use Case:** Send Slack notifications when receiving important emails + +**Workflow:** +1. **Gmail Trigger** - Monitor inbox for new emails +2. **IF Node** - Filter by subject contains "URGENT" +3. **Set Node** - Extract and format data: + ``` + Subject: {{ $json.subject }} + From: {{ $json.from.email }} + Preview: {{ $json.snippet }} + Link: {{ $json.webLink }} + ``` +4. **Slack Node** - Send formatted message to #alerts channel +5. **Gmail Node** - Mark email as read and add label + +**Key Expression:** +```javascript +// Format Slack message +{ + "text": "🚨 Urgent Email Received", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": `*From:* {{ $json.from.email }}\n*Subject:* {{ $json.subject }}\n\n{{ $json.snippet }}` + } + } + ] +} +``` + +### Example 2: Debugging a 401 Authentication Error + +**Scenario:** HTTP Request node returns 401 Unauthorized + +**Debugging Steps:** +1. Check credential configuration in node settings +2. Verify credential is correctly set up in Credentials panel +3. Test API key manually with curl: + ```bash + curl -H "Authorization: Bearer YOUR_API_KEY" https://api.example.com/endpoint + ``` +4. Check API documentation for required headers +5. Verify authentication method (Bearer, Basic, API Key, OAuth2) +6. Check if API key has required permissions +7. Ensure no typos in header names (e.g., "Authorization" not "Authorisation") + +**Common Fixes:** +- Re-create credential with correct API key +- Add missing headers (User-Agent, Content-Type) +- Switch authentication method +- Update expired OAuth2 token + +### Example 3: Writing Complex Data Transformation + +**Use Case:** Transform API response to match database schema + +**Input Data:** +```json +{ + "customer": { + "fullName": "John Doe", + "contactInfo": { + "email": "john@example.com", + "phone": "+1234567890" + }, + "orders": [ + {"id": 1, "total": "99.50"}, + {"id": 2, "total": "149.99"} + ] + } +} +``` + +**Code Node:** +```javascript +// For all items +const results = []; + +for (const item of items) { + const customer = item.json.customer; + + results.push({ + json: { + name: customer.fullName.trim(), + email: customer.contactInfo.email.toLowerCase(), + phone: customer.contactInfo.phone, + total_orders: customer.orders.length, + total_spent: customer.orders.reduce((sum, order) => + sum + parseFloat(order.total), 0 + ).toFixed(2), + last_order_id: customer.orders[customer.orders.length - 1].id + } + }); +} + +return results; +``` + +**Alternative with Set Node (simpler):** +- name: `{{ $json.customer.fullName.trim() }}` +- email: `{{ $json.customer.contactInfo.email.toLowerCase() }}` +- total_orders: `{{ $json.customer.orders.length }}` +- total_spent: `{{ $json.customer.orders.reduce((sum, o) => sum + parseFloat(o.total), 0).toFixed(2) }}` + +### Example 4: Implementing Error Handling + +**Pattern:** +``` +Main Workflow Node + → IF Node (Check for errors) + → Success Path + → Error Path + → Log to Database + → Send Alert (Slack/Email) + → Optional: Retry Logic +``` + +**Error Trigger Workflow:** +Create separate workflow: +1. **Error Trigger** - Activates when any workflow fails +2. **Set Node** - Format error details: + ``` + Workflow: {{ $json.workflow.name }} + Node: {{ $json.node.name }} + Error: {{ $json.error.message }} + Time: {{ $json.executionId }} + ``` +3. **Slack Node** - Alert team +4. **Google Sheets Node** - Log for analysis + +## Tips for Success + +- **Test incrementally**: Test each node as you build, don't wait until the end +- **Use meaningful names**: Rename nodes to describe what they do +- **Check data structure**: Use the debug panel to see actual data format +- **Start simple**: Begin with basic workflow, add complexity gradually +- **Document assumptions**: Add sticky notes to explain complex logic +- **Handle errors**: Always plan for failure scenarios +- **Monitor executions**: Regularly check execution logs for issues +- **Use subworkflows**: Break complex workflows into reusable components +- **Version control**: Export workflows regularly as backup +- **Consult documentation**: Refer to n8n docs for node-specific details at https://docs.n8n.io/ + +## Additional Resources + +For more detailed guidance, see: +- [EXPRESSIONS.md](EXPRESSIONS.md) - Comprehensive expression syntax reference +- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Common issues and solutions + +## n8n Documentation +- Official docs: https://docs.n8n.io/ +- Expression reference: https://docs.n8n.io/code-examples/expressions/ +- Node library: https://docs.n8n.io/integrations/builtin/ +- Community forum: https://community.n8n.io/ diff --git a/skills/n8n-workflow-specialist/TROUBLESHOOTING.md b/skills/n8n-workflow-specialist/TROUBLESHOOTING.md new file mode 100644 index 0000000..eb151b6 --- /dev/null +++ b/skills/n8n-workflow-specialist/TROUBLESHOOTING.md @@ -0,0 +1,609 @@ +# n8n Troubleshooting Guide + +Common issues and solutions for n8n workflow debugging. + +## Table of Contents +- [Authentication Errors](#authentication-errors) +- [Expression Errors](#expression-errors) +- [HTTP Request Errors](#http-request-errors) +- [Webhook Issues](#webhook-issues) +- [Data Transformation Problems](#data-transformation-problems) +- [Performance Issues](#performance-issues) +- [Execution Errors](#execution-errors) +- [Node-Specific Issues](#node-specific-issues) + +## Authentication Errors + +### 401 Unauthorized + +**Symptoms:** +- HTTP Request node returns 401 status code +- Error message: "Unauthorized" or "Invalid credentials" + +**Common Causes & Solutions:** + +1. **Incorrect Credentials** + - Check that the correct credential is selected in the node + - Verify the credential contains the right API key/token + - Re-create the credential if suspicious + +2. **Expired Token** + - OAuth2 tokens expire - reconnect the credential + - API keys may have expiration dates - check provider dashboard + - Refresh tokens if using OAuth2 + +3. **Wrong Authentication Method** + - Verify API requires Bearer token, Basic Auth, or API Key + - Check API documentation for correct header format + - Common headers: + - `Authorization: Bearer TOKEN` + - `Authorization: Basic BASE64_CREDENTIALS` + - `X-API-Key: YOUR_KEY` + +4. **Missing Headers** + ```javascript + // Common required headers + { + "Authorization": "Bearer YOUR_TOKEN", + "Content-Type": "application/json", + "User-Agent": "n8n-workflow" + } + ``` + +5. **Incorrect Scope/Permissions** + - OAuth2 credential may lack required scopes + - API key may not have permission for the endpoint + - Check provider's permission settings + +**Debugging Steps:** +```bash +# Test authentication manually with curl +curl -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + https://api.example.com/endpoint + +# Check if token is valid +curl -H "Authorization: Bearer YOUR_TOKEN" \ + https://api.example.com/me +``` + +### 403 Forbidden + +**Symptoms:** +- HTTP Request returns 403 status code +- "Access Denied" or "Forbidden" error + +**Solutions:** +1. **Insufficient Permissions** + - API key/token lacks permission for the resource + - Check if account has access to the endpoint + - Verify subscription/plan level + +2. **IP Whitelist** + - Some APIs require IP whitelisting + - Add n8n server IP to allowed list + - Check firewall rules + +3. **Rate Limiting** + - Too many requests in short time + - Implement delays between requests + - Use Split In Batches node with batch size limit + +## Expression Errors + +### "Cannot read property 'X' of undefined" + +**Cause:** Trying to access a property that doesn't exist + +**Solutions:** +```javascript +// Bad: Will fail if user is undefined +{{ $json.user.email }} + +// Good: Use optional chaining +{{ $json.user?.email }} + +// Good: Provide default value +{{ $json.user?.email ?? 'no-email@example.com' }} + +// Good: Check existence first +{{ $json.user && $json.user.email ? $json.user.email : 'N/A' }} +``` + +### "Node not found" + +**Cause:** Referenced node name doesn't match exactly + +**Solutions:** +```javascript +// Bad: Wrong node name +{{ $node["Http Request"].json }} + +// Good: Exact match (case-sensitive) +{{ $node["HTTP Request"].json }} + +// Best: Use debug panel to verify exact node name +``` + +**Debugging:** +1. Check node name exactly as it appears in workflow +2. Node names are case-sensitive +3. Spaces and special characters must match exactly +4. Renamed nodes? Update all expressions + +### Expression Syntax Errors + +**Common Mistakes:** +```javascript +// Bad: Missing quotes around property names with dashes +{{ $json.user-name }} + +// Good: Use bracket notation +{{ $json["user-name"] }} + +// Bad: Trying to use expressions in Code node +const value = {{ $json.value }}; // Won't work! + +// Good: In Code node, access directly +const value = items[0].json.value; + +// Bad: Wrong quotation marks in expression +{{ $json.text === "value" }} + +// Good: Use single quotes in expressions +{{ $json.text === 'value' }} +``` + +### DateTime Errors + +**"Invalid DateTime"** +```javascript +// Bad: Invalid date format +{{ DateTime.fromISO($json.badDate) }} + +// Good: Validate before parsing +{{ $json.date ? DateTime.fromISO($json.date).toISO() : '' }} + +// Good: Handle invalid dates +{{ DateTime.fromISO($json.date).isValid ? + DateTime.fromISO($json.date).toFormat('yyyy-MM-dd') : + 'Invalid Date' }} +``` + +## HTTP Request Errors + +### 404 Not Found + +**Solutions:** +1. **Verify URL** + ```javascript + // Check dynamic URLs evaluate correctly + {{ `https://api.example.com/users/${$json.userId}` }} + + // Debug: Use Set node to see final URL + // Set field "debug_url" to your URL expression + ``` + +2. **Check API Endpoint** + - Endpoint may have changed (check API docs) + - Verify API version in URL + - Check for typos in path + +3. **Resource Doesn't Exist** + - ID might be invalid + - Resource might have been deleted + - Test with known valid ID + +### Timeout Errors + +**"Request timed out"** + +**Solutions:** +1. **Increase Timeout** + - Go to node settings + - Increase "Timeout" value (default: 300000ms = 5 minutes) + - For slow APIs, increase to 600000ms (10 minutes) + +2. **Optimize Request** + - Add pagination for large datasets + - Request only needed fields + - Use filtering on API side + - Consider caching responses + +3. **Check API Health** + - API might be down or slow + - Test endpoint in browser/Postman + - Check API status page + +### SSL/TLS Errors + +**"unable to verify the first certificate"** + +**Solutions:** +1. **Ignore SSL Issues** (development only!) + - In HTTP Request node settings + - Enable "Ignore SSL Issues" + - ⚠️ Not recommended for production + +2. **Update Certificates** + - Update system certificates + - Contact API provider about SSL issues + +### CORS Errors + +**Note:** n8n workflows run server-side, so CORS shouldn't be an issue. If you see CORS errors: +- You might be testing in browser (test in n8n instead) +- The error might be from a different issue +- Check actual error message in execution logs + +## Webhook Issues + +### "Webhook waiting for response" + +**Cause:** Webhook is in "Respond to Webhook" mode but no response sent + +**Solution:** +``` +Webhook Node (Wait for Webhook Call) + → Process Data + → Respond to Webhook Node (REQUIRED) +``` + +**Always add "Respond to Webhook" node when using "Wait for Webhook Call"** + +### Webhook Not Triggering + +**Debugging Steps:** +1. **Verify Workflow is Active** + - Workflow must be activated (not just saved) + - Check toggle switch in top-right is ON + +2. **Check Webhook URL** + - Copy exact URL from Webhook node + - Verify no typos when configuring external service + - Test with curl: + ```bash + curl -X POST https://your-n8n.com/webhook/your-path \ + -H "Content-Type: application/json" \ + -d '{"test": "data"}' + ``` + +3. **Verify HTTP Method** + - Webhook expects POST but receiving GET (or vice versa) + - Match method in Webhook node config + +4. **Check Authentication** + - If "Webhook Authentication" is set, include credentials + - Verify Header Auth name and value match + +5. **Firewall/Network Issues** + - n8n instance must be accessible from internet + - Check firewall rules + - Verify DNS is resolving correctly + +### Webhook Returns Error to Caller + +**Customize response:** +``` +Webhook → IF Node + → Success Path → Respond (200 OK) + → Error Path → Respond to Webhook (400/500 with error message) +``` + +Use "Respond to Webhook" node to control: +- Status code +- Headers +- Response body +- Response format (JSON, text, etc.) + +## Data Transformation Problems + +### Empty Results After Transformation + +**Cause:** Filter or map returns empty array + +**Debugging:** +```javascript +// Check what data looks like before transformation +// Add a Set node to inspect: {{ $json }} + +// Ensure filter condition is correct +{{ $json.items.filter(item => item.status === 'active') }} + +// Check if property exists +{{ $json.items.filter(item => item.status && item.status === 'active') }} + +// Verify array is actually an array +{{ Array.isArray($json.items) ? $json.items.length : 0 }} +``` + +### Data Type Mismatches + +**String vs Number Issues:** +```javascript +// Problem: Comparing string with number +{{ $json.age > 18 }} // Fails if age is "25" (string) + +// Solution: Parse to number +{{ parseInt($json.age) > 18 }} +{{ parseFloat($json.price) > 99.99 }} + +// Problem: Math on strings +{{ $json.price * 1.2 }} // Fails if price is "$99.99" + +// Solution: Clean and parse +{{ parseFloat($json.price.replace('$', '')) * 1.2 }} +``` + +### JSON Parse Errors + +**"Unexpected token in JSON"** + +**Solutions:** +```javascript +// Problem: Invalid JSON string +JSON.parse($json.data) + +// Solution: Validate first (in Code node) +try { + const data = JSON.parse(items[0].json.rawData); + return [{ json: data }]; +} catch (error) { + return [{ json: { + error: 'Invalid JSON', + raw: items[0].json.rawData + }}]; +} + +// Solution: Check if already an object +if (typeof $json.data === 'string') { + JSON.parse($json.data) +} else { + $json.data +} +``` + +## Performance Issues + +### Workflow Execution Too Slow + +**Solutions:** + +1. **Reduce HTTP Requests** + ``` + // Bad: Loop making individual requests + Loop → HTTP Request (100x) + + // Good: Batch request + Batch Items → HTTP Request with array + ``` + +2. **Use Split In Batches** + ``` + Split In Batches (batch size: 10) + → Process Batch + → Loop Back + ``` + +3. **Optimize Expressions** + ```javascript + // Bad: Complex expression repeated + {{ $json.items.filter(...).map(...).reduce(...) }} + + // Good: Use Code node for complex logic + const result = items[0].json.items + .filter(item => item.active) + .map(item => item.value) + .reduce((sum, val) => sum + val, 0); + return [{ json: { total: result } }]; + ``` + +4. **Cache Frequently Accessed Data** + - Store results in Set node + - Reuse instead of re-fetching + - Consider using database for persistent cache + +### Memory Errors + +**"JavaScript heap out of memory"** + +**Solutions:** +1. **Process in Batches** + - Don't load all data at once + - Use Split In Batches node + - Process incrementally + +2. **Reduce Item Count** + - Add Limit node after triggers + - Use pagination in API requests + - Filter data at source + +3. **Optimize Data Size** + - Remove unnecessary fields with Set node + - Don't store large binary data in JSON + - Use binary data type for files + +## Execution Errors + +### "Workflow did not finish" + +**Causes:** +1. **Infinite Loop** + - Missing stop condition in loop + - Always include maximum iterations + +2. **Timeout** + - Workflow taking too long + - Default timeout might be exceeded + - Optimize or increase timeout settings + +### "Missing node parameter" + +**Cause:** Required field is empty or expression evaluates to empty + +**Solutions:** +```javascript +// Provide default values +{{ $json.email || 'noreply@example.com' }} + +// Validate before using +{{ $json.userId ? $json.userId : 'default-id' }} + +// Use IF node to branch based on data availability +IF node: {{ $json.email !== undefined && $json.email !== '' }} + → True: Continue with email + → False: Skip or use default +``` + +### Workflow Executes Multiple Times + +**Causes:** +1. **Trigger Firing Multiple Times** + - Webhook called multiple times by external service + - Schedule trigger overlapping with long execution + - Email trigger processing same email multiple times + +**Solutions:** +- Add deduplication logic +- Use IF node to check if already processed +- Store processed IDs in database +- Adjust trigger settings (e.g., mark emails as read) + +## Node-Specific Issues + +### Code Node Issues + +**"items is not defined"** +```javascript +// Wrong: Using old syntax +for (item of items) { } + +// Correct: Access items array +for (const item of items) { } + +// Or use functional approach +return items.map(item => ({ + json: { + // transformed data + } +})); +``` + +**"Must return array"** +```javascript +// Wrong: Returning object +return { json: { value: 123 } }; + +// Correct: Return array of items +return [{ json: { value: 123 } }]; + +// Multiple items +return [ + { json: { id: 1 } }, + { json: { id: 2 } } +]; +``` + +### Set Node Issues + +**"Expression error in field"** +- Check expression syntax +- Verify referenced node names +- Test expression in expression editor +- Use debug panel to see available data + +### IF Node Issues + +**"All items ending up in one branch"** +- Verify condition logic +- Check data types (string vs number) +- Use debug panel to see actual values +- Test condition in expression editor + +### Split In Batches Issues + +**"Loop not completing"** +- Ensure "Loop Over Items" is connected back to Split In Batches +- Verify batch size is appropriate +- Check for errors in loop that stop execution + +## General Debugging Tips + +1. **Use Debug Panel** + - Click on node to see input/output + - Inspect data structure before writing expressions + - Check for null/undefined values + +2. **Test Incrementally** + - Build workflow step by step + - Test each node before adding next + - Use "Execute Node" to test individual nodes + +3. **Add Logging** + ```javascript + // In Code node + console.log('Debug info:', items[0].json); + // View in execution logs + ``` + +4. **Use Set Nodes for Debugging** + ```javascript + // Add Set node with debug fields + debug_field: {{ $json }} + debug_type: {{ typeof $json.value }} + debug_length: {{ $json.items?.length }} + ``` + +5. **Check Execution Logs** + - View past executions + - Look for error messages + - Compare successful vs failed runs + +6. **Simplify** + - Remove complex expressions temporarily + - Use static values to isolate issues + - Test with minimal data first + +7. **Read Error Messages Carefully** + - Error often points to exact issue + - Note line numbers in Code nodes + - Google specific error messages + +## Getting Help + +If you're still stuck: + +1. **Check n8n Documentation** + - https://docs.n8n.io/ + - Node-specific docs + - Expression reference + +2. **Community Forum** + - https://community.n8n.io/ + - Search for similar issues + - Post workflow JSON for help + +3. **GitHub Issues** + - https://github.com/n8n-io/n8n + - Report bugs + - Check known issues + +4. **Export Workflow** + - Download as JSON for sharing + - Remove sensitive credentials + - Include error messages when asking for help + +## Quick Reference: Common Error Messages + +| Error | Common Cause | Solution | +|-------|-------------|----------| +| 401 Unauthorized | Wrong credentials | Check credential, verify API key | +| 403 Forbidden | Insufficient permissions | Check API permissions, IP whitelist | +| 404 Not Found | Wrong URL/endpoint | Verify URL, check API docs | +| 500 Internal Server Error | API issue or bad request | Check request body, verify API status | +| Timeout | Request too slow | Increase timeout, optimize request | +| Expression error | Syntax or missing data | Check syntax, verify node names | +| Cannot read property | Undefined value | Use optional chaining `?.` | +| Node not found | Wrong node name | Match exact node name (case-sensitive) | +| Invalid DateTime | Bad date format | Validate date before parsing | +| JavaScript heap out of memory | Too much data | Process in batches, reduce data size |