From e3d7ddf4bc52ec0ca41e0b18a211fc81467a06ba Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Apr 2026 13:21:32 +0000 Subject: [PATCH] feat(skills): Add n8n-automation collection (3 skill sets, 14 files) - expression-syntax: {{}} expressions, , variables - javascript-code: Code nodes, , , patterns - node-configuration: Operation-aware setup, dependencies - n8n runs on Command Center for Firefrost workflows - Updated SKILLS-INDEX.md Chronicler #73 --- docs/skills/SKILLS-INDEX.md | 40 + docs/skills/n8n-automation/README.md | 106 ++ .../expression-syntax/COMMON_MISTAKES.md | 393 ++++++ .../expression-syntax/EXAMPLES.md | 483 +++++++ .../expression-syntax/README.md | 93 ++ .../n8n-automation/expression-syntax/SKILL.md | 516 ++++++++ .../javascript-code/BUILTIN_FUNCTIONS.md | 764 ++++++++++++ .../javascript-code/COMMON_PATTERNS.md | 1110 +++++++++++++++++ .../javascript-code/DATA_ACCESS.md | 782 ++++++++++++ .../javascript-code/ERROR_PATTERNS.md | 763 +++++++++++ .../n8n-automation/javascript-code/README.md | 350 ++++++ .../n8n-automation/javascript-code/SKILL.md | 699 +++++++++++ .../node-configuration/DEPENDENCIES.md | 800 ++++++++++++ .../node-configuration/OPERATION_PATTERNS.md | 913 ++++++++++++++ .../node-configuration/README.md | 364 ++++++ .../node-configuration/SKILL.md | 774 ++++++++++++ 16 files changed, 8950 insertions(+) create mode 100644 docs/skills/n8n-automation/README.md create mode 100644 docs/skills/n8n-automation/expression-syntax/COMMON_MISTAKES.md create mode 100644 docs/skills/n8n-automation/expression-syntax/EXAMPLES.md create mode 100644 docs/skills/n8n-automation/expression-syntax/README.md create mode 100644 docs/skills/n8n-automation/expression-syntax/SKILL.md create mode 100644 docs/skills/n8n-automation/javascript-code/BUILTIN_FUNCTIONS.md create mode 100644 docs/skills/n8n-automation/javascript-code/COMMON_PATTERNS.md create mode 100644 docs/skills/n8n-automation/javascript-code/DATA_ACCESS.md create mode 100644 docs/skills/n8n-automation/javascript-code/ERROR_PATTERNS.md create mode 100644 docs/skills/n8n-automation/javascript-code/README.md create mode 100644 docs/skills/n8n-automation/javascript-code/SKILL.md create mode 100644 docs/skills/n8n-automation/node-configuration/DEPENDENCIES.md create mode 100644 docs/skills/n8n-automation/node-configuration/OPERATION_PATTERNS.md create mode 100644 docs/skills/n8n-automation/node-configuration/README.md create mode 100644 docs/skills/n8n-automation/node-configuration/SKILL.md diff --git a/docs/skills/SKILLS-INDEX.md b/docs/skills/SKILLS-INDEX.md index d233b92..8e4d8b0 100644 --- a/docs/skills/SKILLS-INDEX.md +++ b/docs/skills/SKILLS-INDEX.md @@ -264,6 +264,30 @@ --- +### n8n-automation (collection) +**Location:** `docs/skills/n8n-automation/` +**Source:** skill.fish +**Triggers:** n8n, workflow automation, expressions, Code node, JavaScript, node configuration + +**Purpose:** n8n workflow automation guidance + +**What It Covers:** +- **expression-syntax/** — {{}} expressions, $json, $node, $now variables +- **javascript-code/** — Code nodes, $input, $helpers, DateTime +- **node-configuration/** — Operation-aware node setup, property dependencies + +**Firefrost Context:** +- n8n runs on Command Center (63.143.34.217) +- Used for: Subscription sync, server monitoring, notifications + +**Read This When:** +- Writing n8n expressions +- Creating Code nodes with JavaScript +- Configuring complex nodes +- Troubleshooting n8n errors + +--- + ### postgres-patterns **Location:** `docs/skills/postgres-patterns/SKILL.md` **Source:** skill.fish (affaan-m/everything-claude-code) @@ -454,6 +478,22 @@ docs/skills/ │ └── SKILL.md ├── minecraft-mod-dev/ │ └── SKILL.md +├── n8n-automation/ +│ ├── README.md +│ ├── expression-syntax/ +│ │ ├── SKILL.md +│ │ ├── EXAMPLES.md +│ │ └── COMMON_MISTAKES.md +│ ├── javascript-code/ +│ │ ├── SKILL.md +│ │ ├── DATA_ACCESS.md +│ │ ├── BUILTIN_FUNCTIONS.md +│ │ ├── COMMON_PATTERNS.md +│ │ └── ERROR_PATTERNS.md +│ └── node-configuration/ +│ ├── SKILL.md +│ ├── OPERATION_PATTERNS.md +│ └── DEPENDENCIES.md ├── postgres-patterns/ │ └── SKILL.md ├── social-automation/ diff --git a/docs/skills/n8n-automation/README.md b/docs/skills/n8n-automation/README.md new file mode 100644 index 0000000..7575b64 --- /dev/null +++ b/docs/skills/n8n-automation/README.md @@ -0,0 +1,106 @@ +# n8n Automation Skills + +Comprehensive n8n workflow automation guidance for Firefrost. + +**n8n Location:** Command Center (63.143.34.217) +**Access:** https://n8n.firefrostgaming.com (or via Cloudflare tunnel) + +--- + +## Available Skills + +| Skill | Folder | Purpose | +|-------|--------|---------| +| Expression Syntax | `expression-syntax/` | {{}} expressions, $json, $node variables | +| JavaScript Code | `javascript-code/` | Code nodes, $input, $helpers | +| Node Configuration | `node-configuration/` | Operation-aware node setup | + +--- + +## Quick Reference + +### Expression Syntax (expression-syntax/) + +```javascript +// Access current node data +{{$json.fieldName}} +{{$json.body.email}} // Webhook data is under .body! + +// Access other nodes +{{$node["HTTP Request"].json.data}} + +// Dates +{{$now.toFormat('yyyy-MM-dd')}} +``` + +**Files:** +- `SKILL.md` — Core syntax guide +- `EXAMPLES.md` — Common expression examples +- `COMMON_MISTAKES.md` — Pitfalls to avoid + +--- + +### JavaScript Code (javascript-code/) + +```javascript +// Basic Code node template +const items = $input.all(); + +const processed = items.map(item => ({ + json: { + ...item.json, + processed: true + } +})); + +return processed; // MUST return [{json: {...}}] format +``` + +**Files:** +- `SKILL.md` — Core guidance +- `DATA_ACCESS.md` — $input, $json patterns +- `BUILTIN_FUNCTIONS.md` — $helpers.httpRequest(), DateTime +- `COMMON_PATTERNS.md` — Reusable code snippets +- `ERROR_PATTERNS.md` — Debugging help + +--- + +### Node Configuration (node-configuration/) + +**Philosophy:** Progressive disclosure — start minimal, add complexity + +1. **get_node_essentials** — Use first (91.7% success rate) +2. **get_property_dependencies** — For complex nodes +3. **get_node_info** — Full schema when needed + +**Files:** +- `SKILL.md` — Core concepts +- `OPERATION_PATTERNS.md` — Node-specific patterns +- `DEPENDENCIES.md` — Property dependency chains + +--- + +## Firefrost n8n Use Cases + +| Workflow | Description | +|----------|-------------| +| Subscription sync | Stripe → Discord role assignment | +| Server monitoring | Pterodactyl → Discord alerts | +| Backup notifications | Server backup status → Discord | +| Welcome automation | New subscriber → Welcome email/DM | + +--- + +## Common Pitfalls + +1. **Webhook data location:** Data is in `$json.body`, not `$json` directly +2. **Return format:** Code nodes MUST return `[{json: {...}}]` +3. **Node names:** Case-sensitive, must match exactly +4. **Expression braces:** Use `{{}}` not `{}` + +--- + +**Source:** skill.fish +**Added:** 2026-04-09 by Chronicler #73 + +**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️ diff --git a/docs/skills/n8n-automation/expression-syntax/COMMON_MISTAKES.md b/docs/skills/n8n-automation/expression-syntax/COMMON_MISTAKES.md new file mode 100644 index 0000000..dba879b --- /dev/null +++ b/docs/skills/n8n-automation/expression-syntax/COMMON_MISTAKES.md @@ -0,0 +1,393 @@ +# Common n8n Expression Mistakes + +Complete catalog of expression errors with explanations and fixes. + +--- + +## 1. Missing Curly Braces + +**Problem**: Expression not recognized, shows as literal text + +❌ **Wrong**: +``` +$json.email +``` + +✅ **Correct**: +``` +{{$json.email}} +``` + +**Why it fails**: n8n treats text without {{ }} as a literal string. Expressions must be wrapped to be evaluated. + +**How to identify**: Field shows exact text like "$json.email" instead of actual value. + +--- + +## 2. Webhook Body Access + +**Problem**: Undefined values when accessing webhook data + +❌ **Wrong**: +``` +{{$json.name}} +{{$json.email}} +{{$json.message}} +``` + +✅ **Correct**: +``` +{{$json.body.name}} +{{$json.body.email}} +{{$json.body.message}} +``` + +**Why it fails**: Webhook node wraps incoming data under `.body` property. The root `$json` contains headers, params, query, and body. + +**Webhook structure**: +```javascript +{ + "headers": {...}, + "params": {...}, + "query": {...}, + "body": { // User data is HERE! + "name": "John", + "email": "john@example.com" + } +} +``` + +**How to identify**: Webhook workflow shows "undefined" for fields that are definitely being sent. + +--- + +## 3. Spaces in Field Names + +**Problem**: Syntax error or undefined value + +❌ **Wrong**: +``` +{{$json.first name}} +{{$json.user data.email}} +``` + +✅ **Correct**: +``` +{{$json['first name']}} +{{$json['user data'].email}} +``` + +**Why it fails**: Spaces break dot notation. JavaScript interprets space as end of property name. + +**How to identify**: Error message about unexpected token, or undefined when field exists. + +--- + +## 4. Spaces in Node Names + +**Problem**: Cannot access other node's data + +❌ **Wrong**: +``` +{{$node.HTTP Request.json.data}} +{{$node.Respond to Webhook.json}} +``` + +✅ **Correct**: +``` +{{$node["HTTP Request"].json.data}} +{{$node["Respond to Webhook"].json}} +``` + +**Why it fails**: Node names are treated as object property names and need quotes when they contain spaces. + +**How to identify**: Error like "Cannot read property 'Request' of undefined" + +--- + +## 5. Incorrect Node Reference Case + +**Problem**: Undefined or wrong data returned + +❌ **Wrong**: +``` +{{$node["http request"].json.data}} // lowercase +{{$node["Http Request"].json.data}} // wrong capitalization +``` + +✅ **Correct**: +``` +{{$node["HTTP Request"].json.data}} // exact match +``` + +**Why it fails**: Node names are **case-sensitive**. Must match exactly as shown in workflow. + +**How to identify**: Undefined value even though node exists and has data. + +--- + +## 6. Double Wrapping + +**Problem**: Literal {{ }} appears in output + +❌ **Wrong**: +``` +{{{$json.field}}} +``` + +✅ **Correct**: +``` +{{$json.field}} +``` + +**Why it fails**: Only one set of {{ }} is needed. Extra braces are treated as literal characters. + +**How to identify**: Output shows "{{value}}" instead of just "value". + +--- + +## 7. Array Access with Dots + +**Problem**: Syntax error or undefined + +❌ **Wrong**: +``` +{{$json.items.0.name}} +{{$json.users.1.email}} +``` + +✅ **Correct**: +``` +{{$json.items[0].name}} +{{$json.users[1].email}} +``` + +**Why it fails**: Array indices require brackets, not dots. Number after dot is invalid JavaScript. + +**How to identify**: Syntax error or "Cannot read property '0' of undefined" + +--- + +## 8. Using Expressions in Code Nodes + +**Problem**: Literal string instead of value, or errors + +❌ **Wrong (in Code node)**: +```javascript +const email = '{{$json.email}}'; +const name = '={{$json.body.name}}'; +``` + +✅ **Correct (in Code node)**: +```javascript +const email = $json.email; +const name = $json.body.name; + +// Or using Code node API +const email = $input.item.json.email; +const allItems = $input.all(); +``` + +**Why it fails**: Code nodes have **direct access** to data. The {{ }} syntax is for expression fields in other nodes, not for JavaScript code. + +**How to identify**: Literal string "{{$json.email}}" appears in Code node output instead of actual value. + +--- + +## 9. Missing Quotes in $node Reference + +**Problem**: Syntax error + +❌ **Wrong**: +``` +{{$node[HTTP Request].json.data}} +``` + +✅ **Correct**: +``` +{{$node["HTTP Request"].json.data}} +``` + +**Why it fails**: Node names must be quoted strings inside brackets. + +**How to identify**: Syntax error "Unexpected identifier" + +--- + +## 10. Incorrect Property Path + +**Problem**: Undefined value + +❌ **Wrong**: +``` +{{$json.data.items.name}} // items is an array +{{$json.user.email}} // user doesn't exist, it's userData +``` + +✅ **Correct**: +``` +{{$json.data.items[0].name}} // access array element +{{$json.userData.email}} // correct property name +``` + +**Why it fails**: Wrong path to data. Arrays need index, property names must be exact. + +**How to identify**: Check actual data structure using expression editor preview. + +--- + +## 11. Using = Prefix Outside JSON + +**Problem**: Literal "=" appears in output + +❌ **Wrong (in text field)**: +``` +Email: ={{$json.email}} +``` + +✅ **Correct (in text field)**: +``` +Email: {{$json.email}} +``` + +**Note**: The `=` prefix is **only** needed in JSON mode or when you want to set entire field value to expression result: + +```javascript +// JSON mode (set property to expression) +{ + "email": "={{$json.body.email}}" +} + +// Text mode (no = needed) +Hello {{$json.body.name}}! +``` + +**Why it fails**: The `=` is parsed as literal text in non-JSON contexts. + +**How to identify**: Output shows "=john@example.com" instead of "john@example.com" + +--- + +## 12. Expressions in Webhook Path + +**Problem**: Path doesn't update, validation error + +❌ **Wrong**: +``` +path: "{{$json.user_id}}/webhook" +path: "users/={{$env.TENANT_ID}}" +``` + +✅ **Correct**: +``` +path: "my-webhook" // Static paths only +path: "user-webhook/:userId" // Use dynamic URL parameters instead +``` + +**Why it fails**: Webhook paths must be static. Use dynamic URL parameters (`:paramName`) instead of expressions. + +**How to identify**: Webhook path doesn't change or validation warns about invalid path. + +--- + +## 13. Forgetting .json in $node Reference + +**Problem**: Undefined or wrong data + +❌ **Wrong**: +``` +{{$node["HTTP Request"].data}} // Missing .json +{{$node["Webhook"].body.email}} // Missing .json +``` + +✅ **Correct**: +``` +{{$node["HTTP Request"].json.data}} +{{$node["Webhook"].json.body.email}} +``` + +**Why it fails**: Node data is always under `.json` property (or `.binary` for binary data). + +**How to identify**: Undefined value when you know the node has data. + +--- + +## 14. String Concatenation Confusion + +**Problem**: Attempting JavaScript template literals + +❌ **Wrong**: +``` +`Hello ${$json.name}!` // Template literal syntax +"Hello " + $json.name + "!" // String concatenation +``` + +✅ **Correct**: +``` +Hello {{$json.name}}! // n8n expressions auto-concatenate +``` + +**Why it fails**: n8n expressions don't use JavaScript template literal syntax. Adjacent text and expressions are automatically concatenated. + +**How to identify**: Literal backticks or + symbols appear in output. + +--- + +## 15. Empty Expression Brackets + +**Problem**: Literal {{}} in output + +❌ **Wrong**: +``` +{{}} +{{ }} +``` + +✅ **Correct**: +``` +{{$json.field}} // Include expression content +``` + +**Why it fails**: Empty expression brackets have nothing to evaluate. + +**How to identify**: Literal "{{ }}" text appears in output. + +--- + +## Quick Reference Table + +| Error | Symptom | Fix | +|-------|---------|-----| +| No {{ }} | Literal text | Add {{ }} | +| Webhook data | Undefined | Add `.body` | +| Space in field | Syntax error | Use `['field name']` | +| Space in node | Undefined | Use `["Node Name"]` | +| Wrong case | Undefined | Match exact case | +| Double {{ }} | Literal braces | Remove extra {{ }} | +| .0 array | Syntax error | Use [0] | +| {{ }} in Code | Literal string | Remove {{ }} | +| No quotes in $node | Syntax error | Add quotes | +| Wrong path | Undefined | Check data structure | +| = in text | Literal = | Remove = prefix | +| Dynamic path | Doesn't work | Use static path | +| Missing .json | Undefined | Add .json | +| Template literals | Literal text | Use {{ }} | +| Empty {{ }} | Literal braces | Add expression | + +--- + +## Debugging Process + +When expression doesn't work: + +1. **Check braces**: Is it wrapped in {{ }}? +2. **Check data source**: Is it webhook data? Add `.body` +3. **Check spaces**: Field or node name has spaces? Use brackets +4. **Check case**: Does node name match exactly? +5. **Check path**: Is the property path correct? +6. **Use expression editor**: Preview shows actual result +7. **Check context**: Is it a Code node? Remove {{ }} + +--- + +**Related**: See [EXAMPLES.md](EXAMPLES.md) for working examples of correct syntax. diff --git a/docs/skills/n8n-automation/expression-syntax/EXAMPLES.md b/docs/skills/n8n-automation/expression-syntax/EXAMPLES.md new file mode 100644 index 0000000..35a39c7 --- /dev/null +++ b/docs/skills/n8n-automation/expression-syntax/EXAMPLES.md @@ -0,0 +1,483 @@ +# n8n Expression Examples + +Real working examples from n8n workflows. + +--- + +## Example 1: Webhook Form Submission + +**Scenario**: Form submission webhook posts to Slack + +**Workflow**: Webhook → Slack + +**Webhook Input** (POST): +```json +{ + "name": "John Doe", + "email": "john@example.com", + "company": "Acme Corp", + "message": "Interested in your product" +} +``` + +**Webhook Node Output**: +```json +{ + "headers": {"content-type": "application/json"}, + "params": {}, + "query": {}, + "body": { + "name": "John Doe", + "email": "john@example.com", + "company": "Acme Corp", + "message": "Interested in your product" + } +} +``` + +**In Slack Node** (text field): +``` +New form submission! 📝 + +Name: {{$json.body.name}} +Email: {{$json.body.email}} +Company: {{$json.body.company}} +Message: {{$json.body.message}} +``` + +**Output**: +``` +New form submission! 📝 + +Name: John Doe +Email: john@example.com +Company: Acme Corp +Message: Interested in your product +``` + +--- + +## Example 2: HTTP API to Database + +**Scenario**: Fetch user data from API and insert into database + +**Workflow**: Schedule → HTTP Request → Postgres + +**HTTP Request Returns**: +```json +{ + "data": { + "users": [ + { + "id": 123, + "name": "Alice Smith", + "email": "alice@example.com", + "role": "admin" + } + ] + } +} +``` + +**In Postgres Node** (INSERT statement): +```sql +INSERT INTO users (user_id, name, email, role, synced_at) +VALUES ( + {{$json.data.users[0].id}}, + '{{$json.data.users[0].name}}', + '{{$json.data.users[0].email}}', + '{{$json.data.users[0].role}}', + '{{$now.toFormat('yyyy-MM-dd HH:mm:ss')}}' +) +``` + +**Result**: User inserted with current timestamp + +--- + +## Example 3: Multi-Node Data Flow + +**Scenario**: Webhook → HTTP Request → Email + +**Workflow Structure**: +1. Webhook receives order ID +2. HTTP Request fetches order details +3. Email sends confirmation + +### Node 1: Webhook + +**Receives**: +```json +{ + "body": { + "order_id": "ORD-12345" + } +} +``` + +### Node 2: HTTP Request + +**URL field**: +``` +https://api.example.com/orders/{{$json.body.order_id}} +``` + +**Returns**: +```json +{ + "order": { + "id": "ORD-12345", + "customer": "Bob Jones", + "total": 99.99, + "items": ["Widget", "Gadget"] + } +} +``` + +### Node 3: Email + +**Subject**: +``` +Order {{$node["Webhook"].json.body.order_id}} Confirmed +``` + +**Body**: +``` +Dear {{$node["HTTP Request"].json.order.customer}}, + +Your order {{$node["Webhook"].json.body.order_id}} has been confirmed! + +Total: ${{$node["HTTP Request"].json.order.total}} +Items: {{$node["HTTP Request"].json.order.items.join(', ')}} + +Thank you for your purchase! +``` + +**Email Result**: +``` +Subject: Order ORD-12345 Confirmed + +Dear Bob Jones, + +Your order ORD-12345 has been confirmed! + +Total: $99.99 +Items: Widget, Gadget + +Thank you for your purchase! +``` + +--- + +## Example 4: Date Formatting + +**Scenario**: Various date format outputs + +**Current Time**: 2025-10-20 14:30:45 + +### ISO Format +```javascript +{{$now.toISO()}} +``` +**Output**: `2025-10-20T14:30:45.000Z` + +### Custom Date Format +```javascript +{{$now.toFormat('yyyy-MM-dd')}} +``` +**Output**: `2025-10-20` + +### Time Only +```javascript +{{$now.toFormat('HH:mm:ss')}} +``` +**Output**: `14:30:45` + +### Full Readable Format +```javascript +{{$now.toFormat('MMMM dd, yyyy')}} +``` +**Output**: `October 20, 2025` + +### Date Math - Future +```javascript +{{$now.plus({days: 7}).toFormat('yyyy-MM-dd')}} +``` +**Output**: `2025-10-27` + +### Date Math - Past +```javascript +{{$now.minus({hours: 24}).toFormat('yyyy-MM-dd HH:mm')}} +``` +**Output**: `2025-10-19 14:30` + +--- + +## Example 5: Array Operations + +**Data**: +```json +{ + "users": [ + {"name": "Alice", "email": "alice@example.com"}, + {"name": "Bob", "email": "bob@example.com"}, + {"name": "Charlie", "email": "charlie@example.com"} + ] +} +``` + +### First User +```javascript +{{$json.users[0].name}} +``` +**Output**: `Alice` + +### Last User +```javascript +{{$json.users[$json.users.length - 1].name}} +``` +**Output**: `Charlie` + +### All Emails (Join) +```javascript +{{$json.users.map(u => u.email).join(', ')}} +``` +**Output**: `alice@example.com, bob@example.com, charlie@example.com` + +### Array Length +```javascript +{{$json.users.length}} +``` +**Output**: `3` + +--- + +## Example 6: Conditional Logic + +**Data**: +```json +{ + "order": { + "status": "completed", + "total": 150 + } +} +``` + +### Ternary Operator +```javascript +{{$json.order.status === 'completed' ? 'Order Complete ✓' : 'Pending...'}} +``` +**Output**: `Order Complete ✓` + +### Default Values +```javascript +{{$json.order.notes || 'No notes provided'}} +``` +**Output**: `No notes provided` (if notes field doesn't exist) + +### Multiple Conditions +```javascript +{{$json.order.total > 100 ? 'Premium Customer' : 'Standard Customer'}} +``` +**Output**: `Premium Customer` + +--- + +## Example 7: String Manipulation + +**Data**: +```json +{ + "user": { + "email": "JOHN@EXAMPLE.COM", + "message": " Hello World " + } +} +``` + +### Lowercase +```javascript +{{$json.user.email.toLowerCase()}} +``` +**Output**: `john@example.com` + +### Uppercase +```javascript +{{$json.user.message.toUpperCase()}} +``` +**Output**: ` HELLO WORLD ` + +### Trim +```javascript +{{$json.user.message.trim()}} +``` +**Output**: `Hello World` + +### Substring +```javascript +{{$json.user.email.substring(0, 4)}} +``` +**Output**: `JOHN` + +### Replace +```javascript +{{$json.user.message.replace('World', 'n8n')}} +``` +**Output**: ` Hello n8n ` + +--- + +## Example 8: Fields with Spaces + +**Data**: +```json +{ + "user data": { + "first name": "Jane", + "last name": "Doe", + "phone number": "+1234567890" + } +} +``` + +### Bracket Notation +```javascript +{{$json['user data']['first name']}} +``` +**Output**: `Jane` + +### Combined +```javascript +{{$json['user data']['first name']}} {{$json['user data']['last name']}} +``` +**Output**: `Jane Doe` + +### Nested Spaces +```javascript +Contact: {{$json['user data']['phone number']}} +``` +**Output**: `Contact: +1234567890` + +--- + +## Example 9: Code Node (Direct Access) + +**Code Node**: Transform webhook data + +**Input** (from Webhook node): +```json +{ + "body": { + "items": ["apple", "banana", "cherry"] + } +} +``` + +**Code** (JavaScript): +```javascript +// ✅ Direct access (no {{ }}) +const items = $json.body.items; + +// Transform to uppercase +const uppercased = items.map(item => item.toUpperCase()); + +// Return in n8n format +return [{ + json: { + original: items, + transformed: uppercased, + count: items.length + } +}]; +``` + +**Output**: +```json +{ + "original": ["apple", "banana", "cherry"], + "transformed": ["APPLE", "BANANA", "CHERRY"], + "count": 3 +} +``` + +--- + +## Example 10: Environment Variables + +**Setup**: Environment variable `API_KEY=secret123` + +### In HTTP Request (Headers) +```javascript +Authorization: Bearer {{$env.API_KEY}} +``` +**Result**: `Authorization: Bearer secret123` + +### In URL +```javascript +https://api.example.com/data?key={{$env.API_KEY}} +``` +**Result**: `https://api.example.com/data?key=secret123` + +--- + +## Template from Real Workflow + +**Based on n8n template #2947** (Weather to Slack) + +### Workflow Structure +Webhook → OpenStreetMap API → Weather API → Slack + +### Webhook Slash Command +**Input**: `/weather London` + +**Webhook receives**: +```json +{ + "body": { + "text": "London" + } +} +``` + +### OpenStreetMap API +**URL**: +``` +https://nominatim.openstreetmap.org/search?q={{$json.body.text}}&format=json +``` + +### Weather API (NWS) +**URL**: +``` +https://api.weather.gov/points/{{$node["OpenStreetMap"].json[0].lat}},{{$node["OpenStreetMap"].json[0].lon}} +``` + +### Slack Message +``` +Weather for {{$json.body.text}}: + +Temperature: {{$node["Weather API"].json.properties.temperature.value}}°C +Conditions: {{$node["Weather API"].json.properties.shortForecast}} +``` + +--- + +## Summary + +**Key Patterns**: +1. Webhook data is under `.body` +2. Use `{{}}` for expressions (except Code nodes) +3. Reference other nodes with `$node["Node Name"].json` +4. Use brackets for field names with spaces +5. Node names are case-sensitive + +**Most Common Uses**: +- `{{$json.body.field}}` - Webhook data +- `{{$node["Name"].json.field}}` - Other node data +- `{{$now.toFormat('yyyy-MM-dd')}}` - Timestamps +- `{{$json.array[0].field}}` - Array access +- `{{$json.field || 'default'}}` - Default values + +--- + +**Related**: See [COMMON_MISTAKES.md](COMMON_MISTAKES.md) for error examples and fixes. diff --git a/docs/skills/n8n-automation/expression-syntax/README.md b/docs/skills/n8n-automation/expression-syntax/README.md new file mode 100644 index 0000000..0fe0ded --- /dev/null +++ b/docs/skills/n8n-automation/expression-syntax/README.md @@ -0,0 +1,93 @@ +# n8n Expression Syntax + +Expert guide for writing correct n8n expressions in workflows. + +--- + +## Purpose + +Teaches correct n8n expression syntax ({{ }} patterns) and fixes common mistakes, especially the critical webhook data structure gotcha. + +## Activates On + +- expression +- {{}} syntax +- $json, $node, $now, $env +- webhook data +- troubleshoot expression error +- undefined in workflow + +## File Count + +4 files, ~450 lines total + +## Dependencies + +**n8n-mcp tools**: +- None directly (syntax knowledge skill) +- Works with n8n-mcp validation tools + +**Related skills**: +- n8n Workflow Patterns (uses expressions in examples) +- n8n MCP Tools Expert (validates expressions) +- n8n Node Configuration (when expressions are needed) + +## Coverage + +### Core Topics +- Expression format ({{ }}) +- Core variables ($json, $node, $now, $env) +- **Webhook data structure** ($json.body.*) +- When NOT to use expressions (Code nodes) + +### Common Patterns +- Accessing nested fields +- Referencing other nodes +- Array and object access +- Date/time formatting +- String manipulation + +### Error Prevention +- 15 common mistakes with fixes +- Quick reference table +- Debugging process + +## Evaluations + +4 scenarios (100% coverage expected): +1. **eval-001**: Missing curly braces +2. **eval-002**: Webhook body data access (critical!) +3. **eval-003**: Code node vs expression confusion +4. **eval-004**: Node reference syntax + +## Key Features + +✅ **Critical Gotcha Highlighted**: Webhook data under `.body` +✅ **Real Examples**: From MCP testing and real templates +✅ **Quick Fixes Table**: Fast reference for common errors +✅ **Code vs Expression**: Clear distinction +✅ **Comprehensive**: Covers 95% of expression use cases + +## Files + +- **SKILL.md** (285 lines) - Main content with all essential knowledge +- **COMMON_MISTAKES.md** (380 lines) - Complete error catalog with 15 common mistakes +- **EXAMPLES.md** (450 lines) - 10 real working examples +- **README.md** (this file) - Skill metadata + +## Success Metrics + +**Expected outcomes**: +- Users correctly wrap expressions in {{ }} +- Zero webhook `.body` access errors +- No expressions used in Code nodes +- Correct $node reference syntax + +## Last Updated + +2025-10-20 + +--- + +**Part of**: n8n-skills repository +**Conceived by**: Romuald Członkowski - [www.aiadvisors.pl/en](https://www.aiadvisors.pl/en) diff --git a/docs/skills/n8n-automation/expression-syntax/SKILL.md b/docs/skills/n8n-automation/expression-syntax/SKILL.md new file mode 100644 index 0000000..2276cdb --- /dev/null +++ b/docs/skills/n8n-automation/expression-syntax/SKILL.md @@ -0,0 +1,516 @@ +--- +name: n8n-expression-syntax +description: Validate n8n expression syntax and fix common errors. Use when writing n8n expressions, using {{}} syntax, accessing $json/$node variables, troubleshooting expression errors, or working with webhook data in workflows. +--- + +# n8n Expression Syntax + +Expert guide for writing correct n8n expressions in workflows. + +--- + +## Expression Format + +All dynamic content in n8n uses **double curly braces**: + +``` +{{expression}} +``` + +**Examples**: +``` +✅ {{$json.email}} +✅ {{$json.body.name}} +✅ {{$node["HTTP Request"].json.data}} +❌ $json.email (no braces - treated as literal text) +❌ {$json.email} (single braces - invalid) +``` + +--- + +## Core Variables + +### $json - Current Node Output + +Access data from the current node: + +```javascript +{{$json.fieldName}} +{{$json['field with spaces']}} +{{$json.nested.property}} +{{$json.items[0].name}} +``` + +### $node - Reference Other Nodes + +Access data from any previous node: + +```javascript +{{$node["Node Name"].json.fieldName}} +{{$node["HTTP Request"].json.data}} +{{$node["Webhook"].json.body.email}} +``` + +**Important**: +- Node names **must** be in quotes +- Node names are **case-sensitive** +- Must match exact node name from workflow + +### $now - Current Timestamp + +Access current date/time: + +```javascript +{{$now}} +{{$now.toFormat('yyyy-MM-dd')}} +{{$now.toFormat('HH:mm:ss')}} +{{$now.plus({days: 7})}} +``` + +### $env - Environment Variables + +Access environment variables: + +```javascript +{{$env.API_KEY}} +{{$env.DATABASE_URL}} +``` + +--- + +## 🚨 CRITICAL: Webhook Data Structure + +**Most Common Mistake**: Webhook data is **NOT** at the root! + +### Webhook Node Output Structure + +```javascript +{ + "headers": {...}, + "params": {...}, + "query": {...}, + "body": { // ⚠️ USER DATA IS HERE! + "name": "John", + "email": "john@example.com", + "message": "Hello" + } +} +``` + +### Correct Webhook Data Access + +```javascript +❌ WRONG: {{$json.name}} +❌ WRONG: {{$json.email}} + +✅ CORRECT: {{$json.body.name}} +✅ CORRECT: {{$json.body.email}} +✅ CORRECT: {{$json.body.message}} +``` + +**Why**: Webhook node wraps incoming data under `.body` property to preserve headers, params, and query parameters. + +--- + +## Common Patterns + +### Access Nested Fields + +```javascript +// Simple nesting +{{$json.user.email}} + +// Array access +{{$json.data[0].name}} +{{$json.items[0].id}} + +// Bracket notation for spaces +{{$json['field name']}} +{{$json['user data']['first name']}} +``` + +### Reference Other Nodes + +```javascript +// Node without spaces +{{$node["Set"].json.value}} + +// Node with spaces (common!) +{{$node["HTTP Request"].json.data}} +{{$node["Respond to Webhook"].json.message}} + +// Webhook node +{{$node["Webhook"].json.body.email}} +``` + +### Combine Variables + +```javascript +// Concatenation (automatic) +Hello {{$json.body.name}}! + +// In URLs +https://api.example.com/users/{{$json.body.user_id}} + +// In object properties +{ + "name": "={{$json.body.name}}", + "email": "={{$json.body.email}}" +} +``` + +--- + +## When NOT to Use Expressions + +### ❌ Code Nodes + +Code nodes use **direct JavaScript access**, NOT expressions! + +```javascript +// ❌ WRONG in Code node +const email = '={{$json.email}}'; +const name = '{{$json.body.name}}'; + +// ✅ CORRECT in Code node +const email = $json.email; +const name = $json.body.name; + +// Or using Code node API +const email = $input.item.json.email; +const allItems = $input.all(); +``` + +### ❌ Webhook Paths + +```javascript +// ❌ WRONG +path: "{{$json.user_id}}/webhook" + +// ✅ CORRECT +path: "user-webhook" // Static paths only +``` + +### ❌ Credential Fields + +```javascript +// ❌ WRONG +apiKey: "={{$env.API_KEY}}" + +// ✅ CORRECT +Use n8n credential system, not expressions +``` + +--- + +## Validation Rules + +### 1. Always Use {{}} + +Expressions **must** be wrapped in double curly braces. + +```javascript +❌ $json.field +✅ {{$json.field}} +``` + +### 2. Use Quotes for Spaces + +Field or node names with spaces require **bracket notation**: + +```javascript +❌ {{$json.field name}} +✅ {{$json['field name']}} + +❌ {{$node.HTTP Request.json}} +✅ {{$node["HTTP Request"].json}} +``` + +### 3. Match Exact Node Names + +Node references are **case-sensitive**: + +```javascript +❌ {{$node["http request"].json}} // lowercase +❌ {{$node["Http Request"].json}} // wrong case +✅ {{$node["HTTP Request"].json}} // exact match +``` + +### 4. No Nested {{}} + +Don't double-wrap expressions: + +```javascript +❌ {{{$json.field}}} +✅ {{$json.field}} +``` + +--- + +## Common Mistakes + +For complete error catalog with fixes, see [COMMON_MISTAKES.md](COMMON_MISTAKES.md) + +### Quick Fixes + +| Mistake | Fix | +|---------|-----| +| `$json.field` | `{{$json.field}}` | +| `{{$json.field name}}` | `{{$json['field name']}}` | +| `{{$node.HTTP Request}}` | `{{$node["HTTP Request"]}}` | +| `{{{$json.field}}}` | `{{$json.field}}` | +| `{{$json.name}}` (webhook) | `{{$json.body.name}}` | +| `'={{$json.email}}'` (Code node) | `$json.email` | + +--- + +## Working Examples + +For real workflow examples, see [EXAMPLES.md](EXAMPLES.md) + +### Example 1: Webhook to Slack + +**Webhook receives**: +```json +{ + "body": { + "name": "John Doe", + "email": "john@example.com", + "message": "Hello!" + } +} +``` + +**In Slack node text field**: +``` +New form submission! + +Name: {{$json.body.name}} +Email: {{$json.body.email}} +Message: {{$json.body.message}} +``` + +### Example 2: HTTP Request to Email + +**HTTP Request returns**: +```json +{ + "data": { + "items": [ + {"name": "Product 1", "price": 29.99} + ] + } +} +``` + +**In Email node** (reference HTTP Request): +``` +Product: {{$node["HTTP Request"].json.data.items[0].name}} +Price: ${{$node["HTTP Request"].json.data.items[0].price}} +``` + +### Example 3: Format Timestamp + +```javascript +// Current date +{{$now.toFormat('yyyy-MM-dd')}} +// Result: 2025-10-20 + +// Time +{{$now.toFormat('HH:mm:ss')}} +// Result: 14:30:45 + +// Full datetime +{{$now.toFormat('yyyy-MM-dd HH:mm')}} +// Result: 2025-10-20 14:30 +``` + +--- + +## Data Type Handling + +### Arrays + +```javascript +// First item +{{$json.users[0].email}} + +// Array length +{{$json.users.length}} + +// Last item +{{$json.users[$json.users.length - 1].name}} +``` + +### Objects + +```javascript +// Dot notation (no spaces) +{{$json.user.email}} + +// Bracket notation (with spaces or dynamic) +{{$json['user data'].email}} +``` + +### Strings + +```javascript +// Concatenation (automatic) +Hello {{$json.name}}! + +// String methods +{{$json.email.toLowerCase()}} +{{$json.name.toUpperCase()}} +``` + +### Numbers + +```javascript +// Direct use +{{$json.price}} + +// Math operations +{{$json.price * 1.1}} // Add 10% +{{$json.quantity + 5}} +``` + +--- + +## Advanced Patterns + +### Conditional Content + +```javascript +// Ternary operator +{{$json.status === 'active' ? 'Active User' : 'Inactive User'}} + +// Default values +{{$json.email || 'no-email@example.com'}} +``` + +### Date Manipulation + +```javascript +// Add days +{{$now.plus({days: 7}).toFormat('yyyy-MM-dd')}} + +// Subtract hours +{{$now.minus({hours: 24}).toISO()}} + +// Set specific date +{{DateTime.fromISO('2025-12-25').toFormat('MMMM dd, yyyy')}} +``` + +### String Manipulation + +```javascript +// Substring +{{$json.email.substring(0, 5)}} + +// Replace +{{$json.message.replace('old', 'new')}} + +// Split and join +{{$json.tags.split(',').join(', ')}} +``` + +--- + +## Debugging Expressions + +### Test in Expression Editor + +1. Click field with expression +2. Open expression editor (click "fx" icon) +3. See live preview of result +4. Check for errors highlighted in red + +### Common Error Messages + +**"Cannot read property 'X' of undefined"** +→ Parent object doesn't exist +→ Check your data path + +**"X is not a function"** +→ Trying to call method on non-function +→ Check variable type + +**Expression shows as literal text** +→ Missing {{ }} +→ Add curly braces + +--- + +## Expression Helpers + +### Available Methods + +**String**: +- `.toLowerCase()`, `.toUpperCase()` +- `.trim()`, `.replace()`, `.substring()` +- `.split()`, `.includes()` + +**Array**: +- `.length`, `.map()`, `.filter()` +- `.find()`, `.join()`, `.slice()` + +**DateTime** (Luxon): +- `.toFormat()`, `.toISO()`, `.toLocal()` +- `.plus()`, `.minus()`, `.set()` + +**Number**: +- `.toFixed()`, `.toString()` +- Math operations: `+`, `-`, `*`, `/`, `%` + +--- + +## Best Practices + +### ✅ Do + +- Always use {{ }} for dynamic content +- Use bracket notation for field names with spaces +- Reference webhook data from `.body` +- Use $node for data from other nodes +- Test expressions in expression editor + +### ❌ Don't + +- Don't use expressions in Code nodes +- Don't forget quotes around node names with spaces +- Don't double-wrap with extra {{ }} +- Don't assume webhook data is at root (it's under .body!) +- Don't use expressions in webhook paths or credentials + +--- + +## Related Skills + +- **n8n MCP Tools Expert**: Learn how to validate expressions using MCP tools +- **n8n Workflow Patterns**: See expressions in real workflow examples +- **n8n Node Configuration**: Understand when expressions are needed + +--- + +## Summary + +**Essential Rules**: +1. Wrap expressions in {{ }} +2. Webhook data is under `.body` +3. No {{ }} in Code nodes +4. Quote node names with spaces +5. Node names are case-sensitive + +**Most Common Mistakes**: +- Missing {{ }} → Add braces +- `{{$json.name}}` in webhooks → Use `{{$json.body.name}}` +- `{{$json.email}}` in Code → Use `$json.email` +- `{{$node.HTTP Request}}` → Use `{{$node["HTTP Request"]}}` + +For more details, see: +- [COMMON_MISTAKES.md](COMMON_MISTAKES.md) - Complete error catalog +- [EXAMPLES.md](EXAMPLES.md) - Real workflow examples + +--- + +**Need Help?** Reference the n8n expression documentation or use n8n-mcp validation tools to check your expressions. diff --git a/docs/skills/n8n-automation/javascript-code/BUILTIN_FUNCTIONS.md b/docs/skills/n8n-automation/javascript-code/BUILTIN_FUNCTIONS.md new file mode 100644 index 0000000..71c32b7 --- /dev/null +++ b/docs/skills/n8n-automation/javascript-code/BUILTIN_FUNCTIONS.md @@ -0,0 +1,764 @@ +# Built-in Functions - JavaScript Code Node + +Complete reference for n8n's built-in JavaScript functions and helpers. + +--- + +## Overview + +n8n Code nodes provide powerful built-in functions beyond standard JavaScript. This guide covers: + +1. **$helpers.httpRequest()** - Make HTTP requests +2. **DateTime (Luxon)** - Advanced date/time operations +3. **$jmespath()** - Query JSON structures +4. **$getWorkflowStaticData()** - Persistent storage +5. **Standard JavaScript Globals** - Math, JSON, console, etc. +6. **Available Node.js Modules** - crypto, Buffer, URL + +--- + +## 1. $helpers.httpRequest() - HTTP Requests + +Make HTTP requests directly from Code nodes without using HTTP Request node. + +### Basic Usage + +```javascript +const response = await $helpers.httpRequest({ + method: 'GET', + url: 'https://api.example.com/users' +}); + +return [{json: {data: response}}]; +``` + +### Complete Options + +```javascript +const response = await $helpers.httpRequest({ + method: 'POST', // GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS + url: 'https://api.example.com/users', + headers: { + 'Authorization': 'Bearer token123', + 'Content-Type': 'application/json', + 'User-Agent': 'n8n-workflow' + }, + body: { + name: 'John Doe', + email: 'john@example.com' + }, + qs: { // Query string parameters + page: 1, + limit: 10 + }, + timeout: 10000, // Milliseconds (default: no timeout) + json: true, // Auto-parse JSON response (default: true) + simple: false, // Don't throw on HTTP errors (default: true) + resolveWithFullResponse: false // Return only body (default: false) +}); +``` + +### GET Request + +```javascript +// Simple GET +const users = await $helpers.httpRequest({ + method: 'GET', + url: 'https://api.example.com/users' +}); + +return [{json: {users}}]; +``` + +```javascript +// GET with query parameters +const results = await $helpers.httpRequest({ + method: 'GET', + url: 'https://api.example.com/search', + qs: { + q: 'javascript', + page: 1, + per_page: 50 + } +}); + +return [{json: results}]; +``` + +### POST Request + +```javascript +// POST with JSON body +const newUser = await $helpers.httpRequest({ + method: 'POST', + url: 'https://api.example.com/users', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + $env.API_KEY + }, + body: { + name: $json.body.name, + email: $json.body.email, + role: 'user' + } +}); + +return [{json: newUser}]; +``` + +### PUT/PATCH Request + +```javascript +// Update resource +const updated = await $helpers.httpRequest({ + method: 'PATCH', + url: `https://api.example.com/users/${userId}`, + body: { + name: 'Updated Name', + status: 'active' + } +}); + +return [{json: updated}]; +``` + +### DELETE Request + +```javascript +// Delete resource +await $helpers.httpRequest({ + method: 'DELETE', + url: `https://api.example.com/users/${userId}`, + headers: { + 'Authorization': 'Bearer ' + $env.API_KEY + } +}); + +return [{json: {deleted: true, userId}}]; +``` + +### Authentication Patterns + +```javascript +// Bearer Token +const response = await $helpers.httpRequest({ + url: 'https://api.example.com/data', + headers: { + 'Authorization': `Bearer ${$env.API_TOKEN}` + } +}); +``` + +```javascript +// API Key in Header +const response = await $helpers.httpRequest({ + url: 'https://api.example.com/data', + headers: { + 'X-API-Key': $env.API_KEY + } +}); +``` + +```javascript +// Basic Auth (manual) +const credentials = Buffer.from(`${username}:${password}`).toString('base64'); + +const response = await $helpers.httpRequest({ + url: 'https://api.example.com/data', + headers: { + 'Authorization': `Basic ${credentials}` + } +}); +``` + +### Error Handling + +```javascript +// Handle HTTP errors gracefully +try { + const response = await $helpers.httpRequest({ + method: 'GET', + url: 'https://api.example.com/users', + simple: false // Don't throw on 4xx/5xx + }); + + if (response.statusCode >= 200 && response.statusCode < 300) { + return [{json: {success: true, data: response.body}}]; + } else { + return [{ + json: { + success: false, + status: response.statusCode, + error: response.body + } + }]; + } +} catch (error) { + return [{ + json: { + success: false, + error: error.message + } + }]; +} +``` + +### Full Response Access + +```javascript +// Get full response including headers and status +const response = await $helpers.httpRequest({ + url: 'https://api.example.com/data', + resolveWithFullResponse: true +}); + +return [{ + json: { + statusCode: response.statusCode, + headers: response.headers, + body: response.body, + rateLimit: response.headers['x-ratelimit-remaining'] + } +}]; +``` + +--- + +## 2. DateTime (Luxon) - Date & Time Operations + +n8n includes Luxon for powerful date/time handling. Access via `DateTime` global. + +### Current Date/Time + +```javascript +// Current time +const now = DateTime.now(); + +// Current time in specific timezone +const nowTokyo = DateTime.now().setZone('Asia/Tokyo'); + +// Today at midnight +const today = DateTime.now().startOf('day'); + +return [{ + json: { + iso: now.toISO(), // "2025-01-20T15:30:00.000Z" + formatted: now.toFormat('yyyy-MM-dd HH:mm:ss'), // "2025-01-20 15:30:00" + unix: now.toSeconds(), // Unix timestamp + millis: now.toMillis() // Milliseconds since epoch + } +}]; +``` + +### Formatting Dates + +```javascript +const now = DateTime.now(); + +return [{ + json: { + isoFormat: now.toISO(), // ISO 8601: "2025-01-20T15:30:00.000Z" + sqlFormat: now.toSQL(), // SQL: "2025-01-20 15:30:00.000" + httpFormat: now.toHTTP(), // HTTP: "Mon, 20 Jan 2025 15:30:00 GMT" + + // Custom formats + dateOnly: now.toFormat('yyyy-MM-dd'), // "2025-01-20" + timeOnly: now.toFormat('HH:mm:ss'), // "15:30:00" + readable: now.toFormat('MMMM dd, yyyy'), // "January 20, 2025" + compact: now.toFormat('yyyyMMdd'), // "20250120" + withDay: now.toFormat('EEEE, MMMM dd, yyyy'), // "Monday, January 20, 2025" + custom: now.toFormat('dd/MM/yy HH:mm') // "20/01/25 15:30" + } +}]; +``` + +### Parsing Dates + +```javascript +// From ISO string +const dt1 = DateTime.fromISO('2025-01-20T15:30:00'); + +// From specific format +const dt2 = DateTime.fromFormat('01/20/2025', 'MM/dd/yyyy'); + +// From SQL +const dt3 = DateTime.fromSQL('2025-01-20 15:30:00'); + +// From Unix timestamp +const dt4 = DateTime.fromSeconds(1737384600); + +// From milliseconds +const dt5 = DateTime.fromMillis(1737384600000); + +return [{json: {parsed: dt1.toISO()}}]; +``` + +### Date Arithmetic + +```javascript +const now = DateTime.now(); + +return [{ + json: { + // Adding time + tomorrow: now.plus({days: 1}).toISO(), + nextWeek: now.plus({weeks: 1}).toISO(), + nextMonth: now.plus({months: 1}).toISO(), + inTwoHours: now.plus({hours: 2}).toISO(), + + // Subtracting time + yesterday: now.minus({days: 1}).toISO(), + lastWeek: now.minus({weeks: 1}).toISO(), + lastMonth: now.minus({months: 1}).toISO(), + twoHoursAgo: now.minus({hours: 2}).toISO(), + + // Complex operations + in90Days: now.plus({days: 90}).toFormat('yyyy-MM-dd'), + in6Months: now.plus({months: 6}).toFormat('yyyy-MM-dd') + } +}]; +``` + +### Time Comparisons + +```javascript +const now = DateTime.now(); +const targetDate = DateTime.fromISO('2025-12-31'); + +return [{ + json: { + // Comparisons + isFuture: targetDate > now, + isPast: targetDate < now, + isEqual: targetDate.equals(now), + + // Differences + daysUntil: targetDate.diff(now, 'days').days, + hoursUntil: targetDate.diff(now, 'hours').hours, + monthsUntil: targetDate.diff(now, 'months').months, + + // Detailed difference + detailedDiff: targetDate.diff(now, ['months', 'days', 'hours']).toObject() + } +}]; +``` + +### Timezone Operations + +```javascript +const now = DateTime.now(); + +return [{ + json: { + // Current timezone + local: now.toISO(), + + // Convert to different timezone + tokyo: now.setZone('Asia/Tokyo').toISO(), + newYork: now.setZone('America/New_York').toISO(), + london: now.setZone('Europe/London').toISO(), + utc: now.toUTC().toISO(), + + // Get timezone info + timezone: now.zoneName, // "America/Los_Angeles" + offset: now.offset, // Offset in minutes + offsetFormatted: now.toFormat('ZZ') // "+08:00" + } +}]; +``` + +### Start/End of Period + +```javascript +const now = DateTime.now(); + +return [{ + json: { + startOfDay: now.startOf('day').toISO(), + endOfDay: now.endOf('day').toISO(), + startOfWeek: now.startOf('week').toISO(), + endOfWeek: now.endOf('week').toISO(), + startOfMonth: now.startOf('month').toISO(), + endOfMonth: now.endOf('month').toISO(), + startOfYear: now.startOf('year').toISO(), + endOfYear: now.endOf('year').toISO() + } +}]; +``` + +### Weekday & Month Info + +```javascript +const now = DateTime.now(); + +return [{ + json: { + // Day info + weekday: now.weekday, // 1 = Monday, 7 = Sunday + weekdayShort: now.weekdayShort, // "Mon" + weekdayLong: now.weekdayLong, // "Monday" + isWeekend: now.weekday > 5, // Saturday or Sunday + + // Month info + month: now.month, // 1-12 + monthShort: now.monthShort, // "Jan" + monthLong: now.monthLong, // "January" + + // Year info + year: now.year, // 2025 + quarter: now.quarter, // 1-4 + daysInMonth: now.daysInMonth // 28-31 + } +}]; +``` + +--- + +## 3. $jmespath() - JSON Querying + +Query and transform JSON structures using JMESPath syntax. + +### Basic Queries + +```javascript +const data = $input.first().json; + +// Extract specific field +const names = $jmespath(data, 'users[*].name'); + +// Filter array +const adults = $jmespath(data, 'users[?age >= `18`]'); + +// Get specific index +const firstUser = $jmespath(data, 'users[0]'); + +return [{json: {names, adults, firstUser}}]; +``` + +### Advanced Queries + +```javascript +const data = $input.first().json; + +// Sort and slice +const top5 = $jmespath(data, 'users | sort_by(@, &score) | reverse(@) | [0:5]'); + +// Extract nested fields +const emails = $jmespath(data, 'users[*].contact.email'); + +// Multi-field extraction +const simplified = $jmespath(data, 'users[*].{name: name, email: contact.email}'); + +// Conditional filtering +const premium = $jmespath(data, 'users[?subscription.tier == `premium`]'); + +return [{json: {top5, emails, simplified, premium}}]; +``` + +### Common Patterns + +```javascript +// Pattern 1: Filter and project +const query1 = $jmespath(data, 'products[?price > `100`].{name: name, price: price}'); + +// Pattern 2: Aggregate functions +const query2 = $jmespath(data, 'sum(products[*].price)'); +const query3 = $jmespath(data, 'max(products[*].price)'); +const query4 = $jmespath(data, 'length(products)'); + +// Pattern 3: Nested filtering +const query5 = $jmespath(data, 'categories[*].products[?inStock == `true`]'); + +return [{json: {query1, query2, query3, query4, query5}}]; +``` + +--- + +## 4. $getWorkflowStaticData() - Persistent Storage + +Store data that persists across workflow executions. + +### Basic Usage + +```javascript +// Get static data storage +const staticData = $getWorkflowStaticData(); + +// Initialize counter if doesn't exist +if (!staticData.counter) { + staticData.counter = 0; +} + +// Increment counter +staticData.counter++; + +return [{ + json: { + executionCount: staticData.counter + } +}]; +``` + +### Use Cases + +```javascript +// Use Case 1: Rate limiting +const staticData = $getWorkflowStaticData(); +const now = Date.now(); + +if (!staticData.lastRun) { + staticData.lastRun = now; + staticData.runCount = 1; +} else { + const timeSinceLastRun = now - staticData.lastRun; + + if (timeSinceLastRun < 60000) { // Less than 1 minute + return [{json: {error: 'Rate limit: wait 1 minute between runs'}}]; + } + + staticData.lastRun = now; + staticData.runCount++; +} + +return [{json: {allowed: true, totalRuns: staticData.runCount}}]; +``` + +```javascript +// Use Case 2: Tracking last processed ID +const staticData = $getWorkflowStaticData(); +const currentItems = $input.all(); + +// Get last processed ID +const lastId = staticData.lastProcessedId || 0; + +// Filter only new items +const newItems = currentItems.filter(item => item.json.id > lastId); + +// Update last processed ID +if (newItems.length > 0) { + staticData.lastProcessedId = Math.max(...newItems.map(item => item.json.id)); +} + +return newItems; +``` + +```javascript +// Use Case 3: Accumulating results +const staticData = $getWorkflowStaticData(); + +if (!staticData.accumulated) { + staticData.accumulated = []; +} + +// Add current items to accumulated list +const currentData = $input.all().map(item => item.json); +staticData.accumulated.push(...currentData); + +return [{ + json: { + currentBatch: currentData.length, + totalAccumulated: staticData.accumulated.length, + allData: staticData.accumulated + } +}]; +``` + +--- + +## 5. Standard JavaScript Globals + +### Math Object + +```javascript +return [{ + json: { + // Rounding + rounded: Math.round(3.7), // 4 + floor: Math.floor(3.7), // 3 + ceil: Math.ceil(3.2), // 4 + + // Min/Max + max: Math.max(1, 5, 3, 9, 2), // 9 + min: Math.min(1, 5, 3, 9, 2), // 1 + + // Random + random: Math.random(), // 0-1 + randomInt: Math.floor(Math.random() * 100), // 0-99 + + // Other + abs: Math.abs(-5), // 5 + sqrt: Math.sqrt(16), // 4 + pow: Math.pow(2, 3) // 8 + } +}]; +``` + +### JSON Object + +```javascript +// Parse JSON string +const jsonString = '{"name": "John", "age": 30}'; +const parsed = JSON.parse(jsonString); + +// Stringify object +const obj = {name: "John", age: 30}; +const stringified = JSON.stringify(obj); + +// Pretty print +const pretty = JSON.stringify(obj, null, 2); + +return [{json: {parsed, stringified, pretty}}]; +``` + +### console Object + +```javascript +// Debug logging (appears in browser console, press F12) +console.log('Processing items:', $input.all().length); +console.log('First item:', $input.first().json); + +// Other console methods +console.error('Error message'); +console.warn('Warning message'); +console.info('Info message'); + +// Continues to return data +return [{json: {processed: true}}]; +``` + +### Object Methods + +```javascript +const obj = {name: "John", age: 30, city: "NYC"}; + +return [{ + json: { + keys: Object.keys(obj), // ["name", "age", "city"] + values: Object.values(obj), // ["John", 30, "NYC"] + entries: Object.entries(obj), // [["name", "John"], ...] + + // Check property + hasName: 'name' in obj, // true + + // Merge objects + merged: Object.assign({}, obj, {country: "USA"}) + } +}]; +``` + +### Array Methods + +```javascript +const arr = [1, 2, 3, 4, 5]; + +return [{ + json: { + mapped: arr.map(x => x * 2), // [2, 4, 6, 8, 10] + filtered: arr.filter(x => x > 2), // [3, 4, 5] + reduced: arr.reduce((sum, x) => sum + x, 0), // 15 + some: arr.some(x => x > 3), // true + every: arr.every(x => x > 0), // true + find: arr.find(x => x > 3), // 4 + includes: arr.includes(3), // true + joined: arr.join(', ') // "1, 2, 3, 4, 5" + } +}]; +``` + +--- + +## 6. Available Node.js Modules + +### crypto Module + +```javascript +const crypto = require('crypto'); + +// Hash functions +const hash = crypto.createHash('sha256') + .update('my secret text') + .digest('hex'); + +// MD5 hash +const md5 = crypto.createHash('md5') + .update('my text') + .digest('hex'); + +// Random values +const randomBytes = crypto.randomBytes(16).toString('hex'); + +return [{json: {hash, md5, randomBytes}}]; +``` + +### Buffer (built-in) + +```javascript +// Base64 encoding +const encoded = Buffer.from('Hello World').toString('base64'); + +// Base64 decoding +const decoded = Buffer.from(encoded, 'base64').toString(); + +// Hex encoding +const hex = Buffer.from('Hello').toString('hex'); + +return [{json: {encoded, decoded, hex}}]; +``` + +### URL / URLSearchParams + +```javascript +// Parse URL +const url = new URL('https://example.com/path?param1=value1¶m2=value2'); + +// Build query string +const params = new URLSearchParams({ + search: 'query', + page: 1, + limit: 10 +}); + +return [{ + json: { + host: url.host, + pathname: url.pathname, + search: url.search, + queryString: params.toString() // "search=query&page=1&limit=10" + } +}]; +``` + +--- + +## What's NOT Available + +**External npm packages are NOT available:** +- ❌ axios +- ❌ lodash +- ❌ moment (use DateTime/Luxon instead) +- ❌ request +- ❌ Any other npm package + +**Workaround**: Use $helpers.httpRequest() for HTTP, or add data to workflow via HTTP Request node. + +--- + +## Summary + +**Most Useful Built-ins**: +1. **$helpers.httpRequest()** - API calls without HTTP Request node +2. **DateTime** - Professional date/time handling +3. **$jmespath()** - Complex JSON queries +4. **Math, JSON, Object, Array** - Standard JavaScript utilities + +**Common Patterns**: +- API calls: Use $helpers.httpRequest() +- Date operations: Use DateTime (Luxon) +- Data filtering: Use $jmespath() or JavaScript .filter() +- Persistent data: Use $getWorkflowStaticData() +- Hashing: Use crypto module + +**See Also**: +- [SKILL.md](SKILL.md) - Overview +- [COMMON_PATTERNS.md](COMMON_PATTERNS.md) - Real usage examples +- [ERROR_PATTERNS.md](ERROR_PATTERNS.md) - Error prevention diff --git a/docs/skills/n8n-automation/javascript-code/COMMON_PATTERNS.md b/docs/skills/n8n-automation/javascript-code/COMMON_PATTERNS.md new file mode 100644 index 0000000..15aa06d --- /dev/null +++ b/docs/skills/n8n-automation/javascript-code/COMMON_PATTERNS.md @@ -0,0 +1,1110 @@ +# Common Patterns - JavaScript Code Node + +Production-tested patterns for n8n Code nodes. These patterns are proven in real workflows. + +--- + +## Overview + +This guide covers the 10 most useful Code node patterns for n8n workflows. Each pattern includes: +- **Use Case**: When to use this pattern +- **Key Techniques**: Important coding techniques demonstrated +- **Complete Example**: Working code you can adapt +- **Variations**: Common modifications + +**Pattern Categories:** +- Data Aggregation (Patterns 1, 5, 10) +- Content Processing (Patterns 2, 3) +- Data Validation & Comparison (Patterns 4) +- Data Transformation (Patterns 5, 6, 7) +- Output Formatting (Pattern 8) +- Filtering & Ranking (Pattern 9) + +--- + +## Pattern 1: Multi-Source Data Aggregation + +**Use Case**: Combining data from multiple APIs, RSS feeds, webhooks, or databases + +**When to use:** +- Collecting data from multiple services +- Normalizing different API response formats +- Merging data sources into unified structure +- Building aggregated reports + +**Key Techniques**: Loop iteration, conditional parsing, data normalization + +### Complete Example + +```javascript +// Process and structure data collected from multiple sources +const allItems = $input.all(); +let processedArticles = []; + +// Handle different source formats +for (const item of allItems) { + const sourceName = item.json.name || 'Unknown'; + const sourceData = item.json; + + // Parse source-specific structure - Hacker News format + if (sourceName === 'Hacker News' && sourceData.hits) { + for (const hit of sourceData.hits) { + processedArticles.push({ + title: hit.title, + url: hit.url, + summary: hit.story_text || 'No summary', + source: 'Hacker News', + score: hit.points || 0, + fetchedAt: new Date().toISOString() + }); + } + } + + // Parse source-specific structure - Reddit format + else if (sourceName === 'Reddit' && sourceData.data?.children) { + for (const post of sourceData.data.children) { + processedArticles.push({ + title: post.data.title, + url: post.data.url, + summary: post.data.selftext || 'No summary', + source: 'Reddit', + score: post.data.score || 0, + fetchedAt: new Date().toISOString() + }); + } + } + + // Parse source-specific structure - RSS feed format + else if (sourceName === 'RSS' && sourceData.items) { + for (const rssItem of sourceData.items) { + processedArticles.push({ + title: rssItem.title, + url: rssItem.link, + summary: rssItem.description || 'No summary', + source: 'RSS Feed', + score: 0, + fetchedAt: new Date().toISOString() + }); + } + } +} + +// Sort by score (highest first) +processedArticles.sort((a, b) => b.score - a.score); + +return processedArticles.map(article => ({json: article})); +``` + +### Variations + +```javascript +// Variation 1: Add source weighting +for (const article of processedArticles) { + const weights = { + 'Hacker News': 1.5, + 'Reddit': 1.0, + 'RSS Feed': 0.8 + }; + + article.weightedScore = article.score * (weights[article.source] || 1.0); +} + +// Variation 2: Filter by minimum score +processedArticles = processedArticles.filter(article => article.score >= 10); + +// Variation 3: Deduplicate by URL +const seen = new Set(); +processedArticles = processedArticles.filter(article => { + if (seen.has(article.url)) { + return false; + } + seen.add(article.url); + return true; +}); +``` + +--- + +## Pattern 2: Regex Filtering & Pattern Matching + +**Use Case**: Content analysis, keyword extraction, mention tracking, text parsing + +**When to use:** +- Extracting mentions or tags from text +- Finding patterns in unstructured data +- Counting keyword occurrences +- Validating formats (emails, phone numbers) + +**Key Techniques**: Regex matching, object aggregation, sorting/ranking + +### Complete Example + +```javascript +// Extract and track mentions using regex patterns +const etfPattern = /\b([A-Z]{2,5})\b/g; +const knownETFs = ['VOO', 'VTI', 'VT', 'SCHD', 'QYLD', 'VXUS', 'SPY', 'QQQ']; + +const etfMentions = {}; + +for (const item of $input.all()) { + const data = item.json.data; + + // Skip if no data or children + if (!data?.children) continue; + + for (const post of data.children) { + // Combine title and body text + const title = post.data.title || ''; + const body = post.data.selftext || ''; + const combinedText = (title + ' ' + body).toUpperCase(); + + // Find all matches + const matches = combinedText.match(etfPattern); + + if (matches) { + for (const match of matches) { + // Only count known ETFs + if (knownETFs.includes(match)) { + if (!etfMentions[match]) { + etfMentions[match] = { + count: 0, + totalScore: 0, + posts: [] + }; + } + + etfMentions[match].count++; + etfMentions[match].totalScore += post.data.score || 0; + etfMentions[match].posts.push({ + title: post.data.title, + url: post.data.url, + score: post.data.score + }); + } + } + } + } +} + +// Convert to array and sort by mention count +return Object.entries(etfMentions) + .map(([etf, data]) => ({ + json: { + etf, + mentions: data.count, + totalScore: data.totalScore, + averageScore: data.totalScore / data.count, + topPosts: data.posts + .sort((a, b) => b.score - a.score) + .slice(0, 3) + } + })) + .sort((a, b) => b.json.mentions - a.json.mentions); +``` + +### Variations + +```javascript +// Variation 1: Email extraction +const emailPattern = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g; +const emails = text.match(emailPattern) || []; + +// Variation 2: Phone number extraction +const phonePattern = /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g; +const phones = text.match(phonePattern) || []; + +// Variation 3: Hashtag extraction +const hashtagPattern = /#(\w+)/g; +const hashtags = []; +let match; +while ((match = hashtagPattern.exec(text)) !== null) { + hashtags.push(match[1]); +} + +// Variation 4: URL extraction +const urlPattern = /https?:\/\/[^\s]+/g; +const urls = text.match(urlPattern) || []; +``` + +--- + +## Pattern 3: Markdown Parsing & Structured Data Extraction + +**Use Case**: Parsing formatted text, extracting structured fields, content transformation + +**When to use:** +- Parsing markdown or HTML +- Extracting data from structured text +- Converting formatted content to JSON +- Processing documentation or articles + +**Key Techniques**: Regex grouping, helper functions, data normalization, while loops for iteration + +### Complete Example + +```javascript +// Parse markdown and extract structured information +const markdown = $input.first().json.data.markdown; +const adRegex = /##\s*(.*?)\n(.*?)(?=\n##|\n---|$)/gs; + +const ads = []; +let match; + +// Helper function to parse time strings to minutes +function parseTimeToMinutes(timeStr) { + if (!timeStr) return 999999; // Sort unparseable times last + + const hourMatch = timeStr.match(/(\d+)\s*hour/); + const dayMatch = timeStr.match(/(\d+)\s*day/); + const minMatch = timeStr.match(/(\d+)\s*min/); + + let totalMinutes = 0; + if (dayMatch) totalMinutes += parseInt(dayMatch[1]) * 1440; // 24 * 60 + if (hourMatch) totalMinutes += parseInt(hourMatch[1]) * 60; + if (minMatch) totalMinutes += parseInt(minMatch[1]); + + return totalMinutes; +} + +// Extract all job postings from markdown +while ((match = adRegex.exec(markdown)) !== null) { + const title = match[1]?.trim() || 'No title'; + const content = match[2]?.trim() || ''; + + // Extract structured fields from content + const districtMatch = content.match(/\*\*District:\*\*\s*(.*?)(?:\n|$)/); + const salaryMatch = content.match(/\*\*Salary:\*\*\s*(.*?)(?:\n|$)/); + const timeMatch = content.match(/Posted:\s*(.*?)\*/); + + ads.push({ + title: title, + district: districtMatch?.[1].trim() || 'Unknown', + salary: salaryMatch?.[1].trim() || 'Not specified', + postedTimeAgo: timeMatch?.[1] || 'Unknown', + timeInMinutes: parseTimeToMinutes(timeMatch?.[1]), + fullContent: content, + extractedAt: new Date().toISOString() + }); +} + +// Sort by recency (posted time) +ads.sort((a, b) => a.timeInMinutes - b.timeInMinutes); + +return ads.map(ad => ({json: ad})); +``` + +### Variations + +```javascript +// Variation 1: Parse HTML table to JSON +const tableRegex = /(.*?)<\/tr>/gs; +const cellRegex = /(.*?)<\/td>/g; + +const rows = []; +let tableMatch; + +while ((tableMatch = tableRegex.exec(htmlTable)) !== null) { + const cells = []; + let cellMatch; + + while ((cellMatch = cellRegex.exec(tableMatch[1])) !== null) { + cells.push(cellMatch[1].trim()); + } + + if (cells.length > 0) { + rows.push(cells); + } +} + +// Variation 2: Extract code blocks from markdown +const codeBlockRegex = /```(\w+)?\n(.*?)```/gs; +const codeBlocks = []; + +while ((match = codeBlockRegex.exec(markdown)) !== null) { + codeBlocks.push({ + language: match[1] || 'plain', + code: match[2].trim() + }); +} + +// Variation 3: Parse YAML frontmatter +const frontmatterRegex = /^---\n(.*?)\n---/s; +const frontmatterMatch = content.match(frontmatterRegex); + +if (frontmatterMatch) { + const yamlLines = frontmatterMatch[1].split('\n'); + const metadata = {}; + + for (const line of yamlLines) { + const [key, ...valueParts] = line.split(':'); + if (key && valueParts.length > 0) { + metadata[key.trim()] = valueParts.join(':').trim(); + } + } +} +``` + +--- + +## Pattern 4: JSON Comparison & Validation + +**Use Case**: Workflow versioning, configuration validation, change detection, data integrity + +**When to use:** +- Comparing two versions of data +- Detecting changes in configurations +- Validating data consistency +- Checking for differences + +**Key Techniques**: JSON ordering, base64 decoding, deep comparison, object manipulation + +### Complete Example + +```javascript +// Compare and validate JSON objects from different sources +const orderJsonKeys = (jsonObj) => { + const ordered = {}; + Object.keys(jsonObj).sort().forEach(key => { + ordered[key] = jsonObj[key]; + }); + return ordered; +}; + +const allItems = $input.all(); + +// Assume first item is base64-encoded original, second is current +const origWorkflow = JSON.parse( + Buffer.from(allItems[0].json.content, 'base64').toString() +); +const currentWorkflow = allItems[1].json; + +// Order keys for consistent comparison +const orderedOriginal = orderJsonKeys(origWorkflow); +const orderedCurrent = orderJsonKeys(currentWorkflow); + +// Deep comparison +const isSame = JSON.stringify(orderedOriginal) === JSON.stringify(orderedCurrent); + +// Find differences +const differences = []; +for (const key of Object.keys(orderedOriginal)) { + if (JSON.stringify(orderedOriginal[key]) !== JSON.stringify(orderedCurrent[key])) { + differences.push({ + field: key, + original: orderedOriginal[key], + current: orderedCurrent[key] + }); + } +} + +// Check for new keys +for (const key of Object.keys(orderedCurrent)) { + if (!(key in orderedOriginal)) { + differences.push({ + field: key, + original: null, + current: orderedCurrent[key], + status: 'new' + }); + } +} + +return [{ + json: { + identical: isSame, + differenceCount: differences.length, + differences: differences, + original: orderedOriginal, + current: orderedCurrent, + comparedAt: new Date().toISOString() + } +}]; +``` + +### Variations + +```javascript +// Variation 1: Simple equality check +const isEqual = JSON.stringify(obj1) === JSON.stringify(obj2); + +// Variation 2: Deep diff with detailed changes +function deepDiff(obj1, obj2, path = '') { + const changes = []; + + for (const key in obj1) { + const currentPath = path ? `${path}.${key}` : key; + + if (!(key in obj2)) { + changes.push({type: 'removed', path: currentPath, value: obj1[key]}); + } else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') { + changes.push(...deepDiff(obj1[key], obj2[key], currentPath)); + } else if (obj1[key] !== obj2[key]) { + changes.push({ + type: 'modified', + path: currentPath, + from: obj1[key], + to: obj2[key] + }); + } + } + + for (const key in obj2) { + if (!(key in obj1)) { + const currentPath = path ? `${path}.${key}` : key; + changes.push({type: 'added', path: currentPath, value: obj2[key]}); + } + } + + return changes; +} + +// Variation 3: Schema validation +function validateSchema(data, schema) { + const errors = []; + + for (const field of schema.required || []) { + if (!(field in data)) { + errors.push(`Missing required field: ${field}`); + } + } + + for (const [field, type] of Object.entries(schema.types || {})) { + if (field in data && typeof data[field] !== type) { + errors.push(`Field ${field} should be ${type}, got ${typeof data[field]}`); + } + } + + return { + valid: errors.length === 0, + errors + }; +} +``` + +--- + +## Pattern 5: CRM Data Transformation + +**Use Case**: Lead enrichment, data normalization, API preparation, form data processing + +**When to use:** +- Processing form submissions +- Preparing data for CRM APIs +- Normalizing contact information +- Enriching lead data + +**Key Techniques**: Object destructuring, data mapping, format conversion, field splitting + +### Complete Example + +```javascript +// Transform form data into CRM-compatible format +const item = $input.all()[0]; +const { + name, + email, + phone, + company, + course_interest, + message, + timestamp +} = item.json; + +// Split name into first and last +const nameParts = name.split(' '); +const firstName = nameParts[0] || ''; +const lastName = nameParts.slice(1).join(' ') || 'Unknown'; + +// Format phone number +const cleanPhone = phone.replace(/[^\d]/g, ''); // Remove non-digits + +// Build CRM data structure +const crmData = { + data: { + type: 'Contact', + attributes: { + first_name: firstName, + last_name: lastName, + email1: email, + phone_work: cleanPhone, + account_name: company, + description: `Course Interest: ${course_interest}\n\nMessage: ${message}\n\nSubmitted: ${timestamp}`, + lead_source: 'Website Form', + status: 'New' + } + }, + metadata: { + original_submission: timestamp, + processed_at: new Date().toISOString() + } +}; + +return [{ + json: { + ...item.json, + crmData, + processed: true + } +}]; +``` + +### Variations + +```javascript +// Variation 1: Multiple contact processing +const contacts = $input.all(); + +return contacts.map(item => { + const data = item.json; + const [firstName, ...lastNameParts] = data.name.split(' '); + + return { + json: { + firstName, + lastName: lastNameParts.join(' ') || 'Unknown', + email: data.email.toLowerCase(), + phone: data.phone.replace(/[^\d]/g, ''), + tags: [data.source, data.interest_level].filter(Boolean) + } + }; +}); + +// Variation 2: Field validation and normalization +function normalizePContact(raw) { + return { + first_name: raw.firstName?.trim() || '', + last_name: raw.lastName?.trim() || 'Unknown', + email: raw.email?.toLowerCase().trim() || '', + phone: raw.phone?.replace(/[^\d]/g, '') || '', + company: raw.company?.trim() || 'Unknown', + title: raw.title?.trim() || '', + valid: Boolean(raw.email && raw.firstName) + }; +} + +// Variation 3: Lead scoring +function calculateLeadScore(data) { + let score = 0; + + if (data.email) score += 10; + if (data.phone) score += 10; + if (data.company) score += 15; + if (data.title?.toLowerCase().includes('director')) score += 20; + if (data.title?.toLowerCase().includes('manager')) score += 15; + if (data.message?.length > 100) score += 10; + + return score; +} +``` + +--- + +## Pattern 6: Release Information Processing + +**Use Case**: Version management, changelog parsing, release notes generation, GitHub API processing + +**When to use:** +- Processing GitHub releases +- Filtering stable versions +- Generating changelog summaries +- Extracting version information + +**Key Techniques**: Array filtering, conditional field extraction, date formatting, string manipulation + +### Complete Example + +```javascript +// Extract and filter stable releases from GitHub API +const allReleases = $input.first().json; + +const stableReleases = allReleases + .filter(release => !release.prerelease && !release.draft) + .slice(0, 10) + .map(release => { + // Extract highlights section from changelog + const body = release.body || ''; + let highlights = 'No highlights available'; + + if (body.includes('## Highlights:')) { + highlights = body.split('## Highlights:')[1]?.split('##')[0]?.trim(); + } else { + // Fallback to first 500 chars + highlights = body.substring(0, 500) + '...'; + } + + return { + tag: release.tag_name, + name: release.name, + published: release.published_at, + publishedDate: new Date(release.published_at).toLocaleDateString(), + author: release.author.login, + url: release.html_url, + changelog: body, + highlights: highlights, + assetCount: release.assets.length, + assets: release.assets.map(asset => ({ + name: asset.name, + size: asset.size, + downloadCount: asset.download_count, + downloadUrl: asset.browser_download_url + })) + }; + }); + +return stableReleases.map(release => ({json: release})); +``` + +### Variations + +```javascript +// Variation 1: Version comparison +function compareVersions(v1, v2) { + const parts1 = v1.replace('v', '').split('.').map(Number); + const parts2 = v2.replace('v', '').split('.').map(Number); + + for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) { + const num1 = parts1[i] || 0; + const num2 = parts2[i] || 0; + + if (num1 > num2) return 1; + if (num1 < num2) return -1; + } + + return 0; +} + +// Variation 2: Breaking change detection +function hasBreakingChanges(changelog) { + const breakingKeywords = [ + 'BREAKING CHANGE', + 'breaking change', + 'BC:', + '💥' + ]; + + return breakingKeywords.some(keyword => changelog.includes(keyword)); +} + +// Variation 3: Extract version numbers +const versionPattern = /v?(\d+)\.(\d+)\.(\d+)/; +const match = tagName.match(versionPattern); + +if (match) { + const [_, major, minor, patch] = match; + const version = {major: parseInt(major), minor: parseInt(minor), patch: parseInt(patch)}; +} +``` + +--- + +## Pattern 7: Array Transformation with Context + +**Use Case**: Quick data transformation, field mapping, adding computed fields + +**When to use:** +- Transforming arrays with additional context +- Adding calculated fields +- Simplifying complex objects +- Pluralization logic + +**Key Techniques**: Array methods chaining, ternary operators, computed properties + +### Complete Example + +```javascript +// Transform releases with contextual information +const releases = $input.first().json + .filter(release => !release.prerelease && !release.draft) + .slice(0, 10) + .map(release => ({ + version: release.tag_name, + assetCount: release.assets.length, + assetsCountText: release.assets.length === 1 ? 'file' : 'files', + downloadUrl: release.html_url, + isRecent: new Date(release.published_at) > new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), + age: Math.floor((Date.now() - new Date(release.published_at)) / (24 * 60 * 60 * 1000)), + ageText: `${Math.floor((Date.now() - new Date(release.published_at)) / (24 * 60 * 60 * 1000))} days ago` + })); + +return releases.map(release => ({json: release})); +``` + +### Variations + +```javascript +// Variation 1: Add ranking +const items = $input.all() + .sort((a, b) => b.json.score - a.json.score) + .map((item, index) => ({ + json: { + ...item.json, + rank: index + 1, + medal: index < 3 ? ['🥇', '🥈', '🥉'][index] : '' + } + })); + +// Variation 2: Add percentage calculations +const total = $input.all().reduce((sum, item) => sum + item.json.value, 0); + +const itemsWithPercentage = $input.all().map(item => ({ + json: { + ...item.json, + percentage: ((item.json.value / total) * 100).toFixed(2) + '%' + } +})); + +// Variation 3: Add category labels +const categorize = (value) => { + if (value > 100) return 'High'; + if (value > 50) return 'Medium'; + return 'Low'; +}; + +const categorized = $input.all().map(item => ({ + json: { + ...item.json, + category: categorize(item.json.value) + } +})); +``` + +--- + +## Pattern 8: Slack Block Kit Formatting + +**Use Case**: Chat notifications, rich message formatting, interactive messages + +**When to use:** +- Sending formatted Slack messages +- Creating interactive notifications +- Building rich content for chat platforms +- Status reports and alerts + +**Key Techniques**: Template literals, nested objects, Block Kit syntax, date formatting + +### Complete Example + +```javascript +// Create Slack-formatted message with structured blocks +const date = new Date().toISOString().split('T')[0]; +const data = $input.first().json; + +return [{ + json: { + text: `Daily Report - ${date}`, // Fallback text + blocks: [ + { + type: "header", + text: { + type: "plain_text", + text: `📊 Daily Security Report - ${date}` + } + }, + { + type: "section", + text: { + type: "mrkdwn", + text: `*Status:* ${data.status === 'ok' ? '✅ All Clear' : '⚠️ Issues Detected'}\n*Alerts:* ${data.alertCount || 0}\n*Updated:* ${new Date().toLocaleString()}` + } + }, + { + type: "divider" + }, + { + type: "section", + fields: [ + { + type: "mrkdwn", + text: `*Failed Logins:*\n${data.failedLogins || 0}` + }, + { + type: "mrkdwn", + text: `*API Errors:*\n${data.apiErrors || 0}` + }, + { + type: "mrkdwn", + text: `*Uptime:*\n${data.uptime || '100%'}` + }, + { + type: "mrkdwn", + text: `*Response Time:*\n${data.avgResponseTime || 'N/A'}ms` + } + ] + }, + { + type: "context", + elements: [{ + type: "mrkdwn", + text: `Report generated automatically by n8n workflow` + }] + } + ] + } +}]; +``` + +### Variations + +```javascript +// Variation 1: Interactive buttons +const blocksWithButtons = [ + { + type: "section", + text: { + type: "mrkdwn", + text: "Would you like to approve this request?" + }, + accessory: { + type: "button", + text: { + type: "plain_text", + text: "Approve" + }, + style: "primary", + value: "approve", + action_id: "approve_button" + } + } +]; + +// Variation 2: List formatting +const items = ['Item 1', 'Item 2', 'Item 3']; +const formattedList = items.map((item, i) => `${i + 1}. ${item}`).join('\n'); + +// Variation 3: Status indicators +function getStatusEmoji(status) { + const statusMap = { + 'success': '✅', + 'warning': '⚠️', + 'error': '❌', + 'info': 'ℹ️' + }; + + return statusMap[status] || '•'; +} + +// Variation 4: Truncate long messages +function truncate(text, maxLength = 3000) { + if (text.length <= maxLength) return text; + return text.substring(0, maxLength - 3) + '...'; +} +``` + +--- + +## Pattern 9: Top N Filtering & Ranking + +**Use Case**: RAG pipelines, ranking algorithms, result filtering, leaderboards + +**When to use:** +- Getting top results by score +- Filtering best/worst performers +- Building leaderboards +- Relevance ranking + +**Key Techniques**: Sorting, slicing, null coalescing, score calculations + +### Complete Example + +```javascript +// Filter and rank by similarity score, return top results +const ragResponse = $input.item.json; +const chunks = ragResponse.chunks || []; + +// Sort by similarity (highest first) +const topChunks = chunks + .sort((a, b) => (b.similarity || 0) - (a.similarity || 0)) + .slice(0, 6); + +return [{ + json: { + query: ragResponse.query, + topChunks: topChunks, + count: topChunks.length, + maxSimilarity: topChunks[0]?.similarity || 0, + minSimilarity: topChunks[topChunks.length - 1]?.similarity || 0, + averageSimilarity: topChunks.reduce((sum, chunk) => sum + (chunk.similarity || 0), 0) / topChunks.length + } +}]; +``` + +### Variations + +```javascript +// Variation 1: Top N with minimum threshold +const threshold = 0.7; +const topItems = $input.all() + .filter(item => item.json.score >= threshold) + .sort((a, b) => b.json.score - a.json.score) + .slice(0, 10); + +// Variation 2: Bottom N (worst performers) +const bottomItems = $input.all() + .sort((a, b) => a.json.score - b.json.score) // Ascending + .slice(0, 5); + +// Variation 3: Top N by multiple criteria +const ranked = $input.all() + .map(item => ({ + ...item, + compositeScore: (item.json.relevance * 0.6) + (item.json.recency * 0.4) + })) + .sort((a, b) => b.compositeScore - a.compositeScore) + .slice(0, 10); + +// Variation 4: Percentile filtering +const allScores = $input.all().map(item => item.json.score).sort((a, b) => b - a); +const percentile95 = allScores[Math.floor(allScores.length * 0.05)]; + +const topPercentile = $input.all().filter(item => item.json.score >= percentile95); +``` + +--- + +## Pattern 10: String Aggregation & Reporting + +**Use Case**: Report generation, log aggregation, content concatenation, summary creation + +**When to use:** +- Combining multiple text outputs +- Generating reports from data +- Aggregating logs or messages +- Creating formatted summaries + +**Key Techniques**: Array joining, string concatenation, template literals, timestamp handling + +### Complete Example + +```javascript +// Aggregate multiple text inputs into formatted report +const allItems = $input.all(); + +// Collect all messages +const messages = allItems.map(item => item.json.message); + +// Build report +const header = `🎯 **Daily Summary Report**\n📅 ${new Date().toLocaleString()}\n📊 Total Items: ${messages.length}\n\n`; +const divider = '\n\n---\n\n'; +const footer = `\n\n---\n\n✅ Report generated at ${new Date().toISOString()}`; + +const finalReport = header + messages.join(divider) + footer; + +return [{ + json: { + report: finalReport, + messageCount: messages.length, + generatedAt: new Date().toISOString(), + reportLength: finalReport.length + } +}]; +``` + +### Variations + +```javascript +// Variation 1: Numbered list +const numberedReport = allItems + .map((item, index) => `${index + 1}. ${item.json.title}\n ${item.json.description}`) + .join('\n\n'); + +// Variation 2: Markdown table +const headers = '| Name | Status | Score |\n|------|--------|-------|\n'; +const rows = allItems + .map(item => `| ${item.json.name} | ${item.json.status} | ${item.json.score} |`) + .join('\n'); + +const table = headers + rows; + +// Variation 3: HTML report +const htmlReport = ` + + +Report + +

Report - ${new Date().toLocaleDateString()}

+ + + +`; + +// Variation 4: JSON summary +const summary = { + generated: new Date().toISOString(), + totalItems: allItems.length, + items: allItems.map(item => item.json), + statistics: { + total: allItems.reduce((sum, item) => sum + (item.json.value || 0), 0), + average: allItems.reduce((sum, item) => sum + (item.json.value || 0), 0) / allItems.length, + max: Math.max(...allItems.map(item => item.json.value || 0)), + min: Math.min(...allItems.map(item => item.json.value || 0)) + } +}; +``` + +--- + +## Choosing the Right Pattern + +### Pattern Selection Guide + +| Your Goal | Use Pattern | +|-----------|-------------| +| Combine multiple API responses | Pattern 1 (Multi-source Aggregation) | +| Extract mentions or keywords | Pattern 2 (Regex Filtering) | +| Parse formatted text | Pattern 3 (Markdown Parsing) | +| Detect changes in data | Pattern 4 (JSON Comparison) | +| Prepare form data for CRM | Pattern 5 (CRM Transformation) | +| Process GitHub releases | Pattern 6 (Release Processing) | +| Add computed fields | Pattern 7 (Array Transformation) | +| Format Slack messages | Pattern 8 (Block Kit Formatting) | +| Get top results | Pattern 9 (Top N Filtering) | +| Create text reports | Pattern 10 (String Aggregation) | + +### Combining Patterns + +Many real workflows combine multiple patterns: + +```javascript +// Example: Multi-source aggregation + Top N filtering +const allItems = $input.all(); +const aggregated = []; + +// Pattern 1: Aggregate from different sources +for (const item of allItems) { + // ... aggregation logic + aggregated.push(normalizedItem); +} + +// Pattern 9: Get top 10 by score +const top10 = aggregated + .sort((a, b) => b.score - a.score) + .slice(0, 10); + +// Pattern 10: Generate report +const report = `Top 10 Items:\n\n${top10.map((item, i) => `${i + 1}. ${item.title} (${item.score})`).join('\n')}`; + +return [{json: {report, items: top10}}]; +``` + +--- + +## Summary + +**Most Useful Patterns**: +1. Multi-source Aggregation - Combining data from APIs, databases +2. Top N Filtering - Rankings, leaderboards, best results +3. Data Transformation - CRM data, field mapping, enrichment + +**Key Techniques Across Patterns**: +- Array methods (map, filter, reduce, sort, slice) +- Regex for pattern matching +- Object manipulation and destructuring +- Error handling with optional chaining +- Template literals for formatting + +**See Also**: +- [DATA_ACCESS.md](DATA_ACCESS.md) - Data access methods +- [ERROR_PATTERNS.md](ERROR_PATTERNS.md) - Avoid common mistakes +- [BUILTIN_FUNCTIONS.md](BUILTIN_FUNCTIONS.md) - Built-in helpers diff --git a/docs/skills/n8n-automation/javascript-code/DATA_ACCESS.md b/docs/skills/n8n-automation/javascript-code/DATA_ACCESS.md new file mode 100644 index 0000000..50a8d20 --- /dev/null +++ b/docs/skills/n8n-automation/javascript-code/DATA_ACCESS.md @@ -0,0 +1,782 @@ +# Data Access Patterns - JavaScript Code Node + +Comprehensive guide to accessing data in n8n Code nodes using JavaScript. + +--- + +## Overview + +In n8n Code nodes, you access data from previous nodes using built-in variables and methods. Understanding which method to use is critical for correct workflow execution. + +**Data Access Priority** (by common usage): +1. **`$input.all()`** - Most common - Batch operations, aggregations +2. **`$input.first()`** - Very common - Single item operations +3. **`$input.item`** - Common - Each Item mode only +4. **`$node["NodeName"].json`** - Specific node references +5. **`$json`** - Direct current item (legacy, use `$input` instead) + +--- + +## Pattern 1: $input.all() - Process All Items + +**Usage**: Most common pattern for batch processing + +**When to use:** +- Processing multiple records +- Aggregating data (sum, count, average) +- Filtering arrays +- Transforming datasets +- Comparing items +- Sorting or ranking + +### Basic Usage + +```javascript +// Get all items from previous node +const allItems = $input.all(); + +// allItems is an array of objects like: +// [ +// {json: {id: 1, name: "Alice"}}, +// {json: {id: 2, name: "Bob"}} +// ] + +console.log(`Received ${allItems.length} items`); + +return allItems; +``` + +### Example 1: Filter Active Items + +```javascript +const allItems = $input.all(); + +// Filter only active items +const activeItems = allItems.filter(item => item.json.status === 'active'); + +return activeItems; +``` + +### Example 2: Transform All Items + +```javascript +const allItems = $input.all(); + +// Map to new structure +const transformed = allItems.map(item => ({ + json: { + id: item.json.id, + fullName: `${item.json.firstName} ${item.json.lastName}`, + email: item.json.email, + processedAt: new Date().toISOString() + } +})); + +return transformed; +``` + +### Example 3: Aggregate Data + +```javascript +const allItems = $input.all(); + +// Calculate total +const total = allItems.reduce((sum, item) => { + return sum + (item.json.amount || 0); +}, 0); + +return [{ + json: { + total, + count: allItems.length, + average: total / allItems.length + } +}]; +``` + +### Example 4: Sort and Limit + +```javascript +const allItems = $input.all(); + +// Get top 5 by score +const topFive = allItems + .sort((a, b) => (b.json.score || 0) - (a.json.score || 0)) + .slice(0, 5); + +return topFive.map(item => ({json: item.json})); +``` + +### Example 5: Group By Category + +```javascript +const allItems = $input.all(); + +// Group items by category +const grouped = {}; + +for (const item of allItems) { + const category = item.json.category || 'Uncategorized'; + + if (!grouped[category]) { + grouped[category] = []; + } + + grouped[category].push(item.json); +} + +// Convert to array format +return Object.entries(grouped).map(([category, items]) => ({ + json: { + category, + items, + count: items.length + } +})); +``` + +### Example 6: Deduplicate by ID + +```javascript +const allItems = $input.all(); + +// Remove duplicates by ID +const seen = new Set(); +const unique = []; + +for (const item of allItems) { + const id = item.json.id; + + if (!seen.has(id)) { + seen.add(id); + unique.push(item); + } +} + +return unique; +``` + +--- + +## Pattern 2: $input.first() - Get First Item + +**Usage**: Very common for single-item operations + +**When to use:** +- Previous node returns single object +- Working with API responses +- Getting initial/first data point +- Configuration or metadata access + +### Basic Usage + +```javascript +// Get first item from previous node +const firstItem = $input.first(); + +// Access the JSON data +const data = firstItem.json; + +console.log('First item:', data); + +return [{json: data}]; +``` + +### Example 1: Process Single API Response + +```javascript +// Get API response (typically single object) +const response = $input.first().json; + +// Extract what you need +return [{ + json: { + userId: response.data.user.id, + userName: response.data.user.name, + status: response.status, + fetchedAt: new Date().toISOString() + } +}]; +``` + +### Example 2: Transform Single Object + +```javascript +const data = $input.first().json; + +// Transform structure +return [{ + json: { + id: data.id, + contact: { + email: data.email, + phone: data.phone + }, + address: { + street: data.street, + city: data.city, + zip: data.zip + } + } +}]; +``` + +### Example 3: Validate Single Item + +```javascript +const item = $input.first().json; + +// Validation logic +const isValid = item.email && item.email.includes('@'); + +return [{ + json: { + ...item, + valid: isValid, + validatedAt: new Date().toISOString() + } +}]; +``` + +### Example 4: Extract Nested Data + +```javascript +const response = $input.first().json; + +// Navigate nested structure +const users = response.data?.users || []; + +return users.map(user => ({ + json: { + id: user.id, + name: user.profile?.name || 'Unknown', + email: user.contact?.email || 'no-email' + } +})); +``` + +### Example 5: Combine with Other Methods + +```javascript +// Get first item's data +const firstData = $input.first().json; + +// Use it to filter all items +const allItems = $input.all(); +const matching = allItems.filter(item => + item.json.category === firstData.targetCategory +); + +return matching; +``` + +--- + +## Pattern 3: $input.item - Current Item (Each Item Mode) + +**Usage**: Common in "Run Once for Each Item" mode + +**When to use:** +- Mode is set to "Run Once for Each Item" +- Need to process items independently +- Per-item API calls or validations +- Item-specific error handling + +**IMPORTANT**: Only use in "Each Item" mode. Will be undefined in "All Items" mode. + +### Basic Usage + +```javascript +// In "Run Once for Each Item" mode +const currentItem = $input.item; +const data = currentItem.json; + +console.log('Processing item:', data.id); + +return [{ + json: { + ...data, + processed: true + } +}]; +``` + +### Example 1: Add Processing Metadata + +```javascript +const item = $input.item; + +return [{ + json: { + ...item.json, + processed: true, + processedAt: new Date().toISOString(), + processingDuration: Math.random() * 1000 // Simulated duration + } +}]; +``` + +### Example 2: Per-Item Validation + +```javascript +const item = $input.item; +const data = item.json; + +// Validate this specific item +const errors = []; + +if (!data.email) errors.push('Email required'); +if (!data.name) errors.push('Name required'); +if (data.age && data.age < 18) errors.push('Must be 18+'); + +return [{ + json: { + ...data, + valid: errors.length === 0, + errors: errors.length > 0 ? errors : undefined + } +}]; +``` + +### Example 3: Item-Specific API Call + +```javascript +const item = $input.item; +const userId = item.json.userId; + +// Make API call specific to this item +const response = await $helpers.httpRequest({ + method: 'GET', + url: `https://api.example.com/users/${userId}/details` +}); + +return [{ + json: { + ...item.json, + details: response + } +}]; +``` + +### Example 4: Conditional Processing + +```javascript +const item = $input.item; +const data = item.json; + +// Process based on item type +if (data.type === 'premium') { + return [{ + json: { + ...data, + discount: 0.20, + tier: 'premium' + } + }]; +} else { + return [{ + json: { + ...data, + discount: 0.05, + tier: 'standard' + } + }]; +} +``` + +--- + +## Pattern 4: $node - Reference Other Nodes + +**Usage**: Less common, but powerful for specific scenarios + +**When to use:** +- Need data from specific named node +- Combining data from multiple nodes +- Accessing metadata about workflow execution + +### Basic Usage + +```javascript +// Get output from specific node +const webhookData = $node["Webhook"].json; +const apiData = $node["HTTP Request"].json; + +return [{ + json: { + fromWebhook: webhookData, + fromAPI: apiData + } +}]; +``` + +### Example 1: Combine Multiple Sources + +```javascript +// Reference multiple nodes +const webhook = $node["Webhook"].json; +const database = $node["Postgres"].json; +const api = $node["HTTP Request"].json; + +return [{ + json: { + combined: { + webhook: webhook.body, + dbRecords: database.length, + apiResponse: api.status + }, + processedAt: new Date().toISOString() + } +}]; +``` + +### Example 2: Compare Across Nodes + +```javascript +const oldData = $node["Get Old Data"].json; +const newData = $node["Get New Data"].json; + +// Compare +const changes = { + added: newData.filter(n => !oldData.find(o => o.id === n.id)), + removed: oldData.filter(o => !newData.find(n => n.id === o.id)), + modified: newData.filter(n => { + const old = oldData.find(o => o.id === n.id); + return old && JSON.stringify(old) !== JSON.stringify(n); + }) +}; + +return [{ + json: { + changes, + summary: { + added: changes.added.length, + removed: changes.removed.length, + modified: changes.modified.length + } + } +}]; +``` + +### Example 3: Access Node Metadata + +```javascript +// Get data from specific execution path +const ifTrueBranch = $node["IF True"].json; +const ifFalseBranch = $node["IF False"].json; + +// Use whichever branch executed +const result = ifTrueBranch || ifFalseBranch || {}; + +return [{json: result}]; +``` + +--- + +## Critical: Webhook Data Structure + +**MOST COMMON MISTAKE**: Forgetting webhook data is nested under `.body` + +### The Problem + +Webhook node wraps all incoming data under a `body` property. This catches many developers by surprise. + +### Structure + +```javascript +// Webhook node output structure: +{ + "headers": { + "content-type": "application/json", + "user-agent": "...", + // ... other headers + }, + "params": {}, + "query": {}, + "body": { + // ← YOUR DATA IS HERE + "name": "Alice", + "email": "alice@example.com", + "message": "Hello!" + } +} +``` + +### Wrong vs Right + +```javascript +// ❌ WRONG: Trying to access directly +const name = $json.name; // undefined +const email = $json.email; // undefined + +// ✅ CORRECT: Access via .body +const name = $json.body.name; // "Alice" +const email = $json.body.email; // "alice@example.com" + +// ✅ CORRECT: Extract body first +const webhookData = $json.body; +const name = webhookData.name; // "Alice" +const email = webhookData.email; // "alice@example.com" +``` + +### Example: Full Webhook Processing + +```javascript +// Get webhook data from previous node +const webhookOutput = $input.first().json; + +// Access the actual payload +const payload = webhookOutput.body; + +// Access headers if needed +const contentType = webhookOutput.headers['content-type']; + +// Access query parameters if needed +const apiKey = webhookOutput.query.api_key; + +// Process the actual data +return [{ + json: { + // Data from webhook body + userName: payload.name, + userEmail: payload.email, + message: payload.message, + + // Metadata + receivedAt: new Date().toISOString(), + contentType: contentType, + authenticated: !!apiKey + } +}]; +``` + +### POST Data, Query Params, and Headers + +```javascript +const webhook = $input.first().json; + +return [{ + json: { + // POST body data + formData: webhook.body, + + // Query parameters (?key=value) + queryParams: webhook.query, + + // HTTP headers + userAgent: webhook.headers['user-agent'], + contentType: webhook.headers['content-type'], + + // Request metadata + method: webhook.method, // POST, GET, etc. + url: webhook.url + } +}]; +``` + +### Common Webhook Scenarios + +```javascript +// Scenario 1: Form submission +const formData = $json.body; +const name = formData.name; +const email = formData.email; + +// Scenario 2: JSON API webhook +const apiPayload = $json.body; +const eventType = apiPayload.event; +const data = apiPayload.data; + +// Scenario 3: Query parameters +const apiKey = $json.query.api_key; +const userId = $json.query.user_id; + +// Scenario 4: Headers +const authorization = $json.headers['authorization']; +const signature = $json.headers['x-signature']; +``` + +--- + +## Choosing the Right Pattern + +### Decision Tree + +``` +Do you need ALL items from previous node? +├─ YES → Use $input.all() +│ +└─ NO → Do you need just the FIRST item? + ├─ YES → Use $input.first() + │ + └─ NO → Are you in "Each Item" mode? + ├─ YES → Use $input.item + │ + └─ NO → Do you need specific node data? + ├─ YES → Use $node["NodeName"] + └─ NO → Use $input.first() (default) +``` + +### Quick Reference Table + +| Scenario | Use This | Example | +|----------|----------|---------| +| Sum all amounts | `$input.all()` | `allItems.reduce((sum, i) => sum + i.json.amount, 0)` | +| Get API response | `$input.first()` | `$input.first().json.data` | +| Process each independently | `$input.item` | `$input.item.json` (Each Item mode) | +| Combine two nodes | `$node["Name"]` | `$node["API"].json` | +| Filter array | `$input.all()` | `allItems.filter(i => i.json.active)` | +| Transform single object | `$input.first()` | `{...input.first().json, new: true}` | +| Webhook data | `$input.first()` | `$input.first().json.body` | + +--- + +## Common Mistakes + +### Mistake 1: Using $json Without Context + +```javascript +// ❌ WRONG: $json is ambiguous +const value = $json.field; + +// ✅ CORRECT: Be explicit +const value = $input.first().json.field; +``` + +### Mistake 2: Forgetting .json Property + +```javascript +// ❌ WRONG: Trying to access fields on item object +const items = $input.all(); +const names = items.map(item => item.name); // undefined + +// ✅ CORRECT: Access via .json +const names = items.map(item => item.json.name); +``` + +### Mistake 3: Using $input.item in All Items Mode + +```javascript +// ❌ WRONG: $input.item is undefined in "All Items" mode +const data = $input.item.json; // Error! + +// ✅ CORRECT: Use appropriate method +const data = $input.first().json; // Or $input.all() +``` + +### Mistake 4: Not Handling Empty Arrays + +```javascript +// ❌ WRONG: Crashes if no items +const first = $input.all()[0].json; + +// ✅ CORRECT: Check length first +const items = $input.all(); +if (items.length === 0) { + return []; +} +const first = items[0].json; + +// ✅ ALSO CORRECT: Use $input.first() +const first = $input.first().json; // Built-in safety +``` + +### Mistake 5: Modifying Original Data + +```javascript +// ❌ RISKY: Mutating original +const items = $input.all(); +items[0].json.modified = true; // Modifies original +return items; + +// ✅ SAFE: Create new objects +const items = $input.all(); +return items.map(item => ({ + json: { + ...item.json, + modified: true + } +})); +``` + +--- + +## Advanced Patterns + +### Pattern: Pagination Handling + +```javascript +const currentPage = $input.all(); +const pageNumber = $node["Set Page"].json.page || 1; + +// Combine with previous pages +const allPreviousPages = $node["Accumulator"]?.json.accumulated || []; + +return [{ + json: { + accumulated: [...allPreviousPages, ...currentPage], + currentPage: pageNumber, + totalItems: allPreviousPages.length + currentPage.length + } +}]; +``` + +### Pattern: Conditional Node Reference + +```javascript +// Access different nodes based on condition +const condition = $input.first().json.type; + +let data; +if (condition === 'api') { + data = $node["API Response"].json; +} else if (condition === 'database') { + data = $node["Database"].json; +} else { + data = $node["Default"].json; +} + +return [{json: data}]; +``` + +### Pattern: Multi-Node Aggregation + +```javascript +// Collect data from multiple named nodes +const sources = ['Source1', 'Source2', 'Source3']; +const allData = []; + +for (const source of sources) { + const nodeData = $node[source]?.json; + if (nodeData) { + allData.push({ + source, + data: nodeData + }); + } +} + +return allData.map(item => ({json: item})); +``` + +--- + +## Summary + +**Most Common Patterns**: +1. `$input.all()` - Process multiple items, batch operations +2. `$input.first()` - Single item, API responses +3. `$input.item` - Each Item mode processing + +**Critical Rule**: +- Webhook data is under `.body` property + +**Best Practice**: +- Be explicit: Use `$input.first().json.field` instead of `$json.field` +- Always check for null/undefined +- Use appropriate method for your mode (All Items vs Each Item) + +**See Also**: +- [SKILL.md](SKILL.md) - Overview and quick start +- [COMMON_PATTERNS.md](COMMON_PATTERNS.md) - Production patterns +- [ERROR_PATTERNS.md](ERROR_PATTERNS.md) - Avoid common mistakes diff --git a/docs/skills/n8n-automation/javascript-code/ERROR_PATTERNS.md b/docs/skills/n8n-automation/javascript-code/ERROR_PATTERNS.md new file mode 100644 index 0000000..d2ae482 --- /dev/null +++ b/docs/skills/n8n-automation/javascript-code/ERROR_PATTERNS.md @@ -0,0 +1,763 @@ +# Error Patterns - JavaScript Code Node + +Complete guide to avoiding the most common Code node errors. + +--- + +## Overview + +This guide covers the **top 5 error patterns** encountered in n8n Code nodes. Understanding and avoiding these errors will save you significant debugging time. + +**Error Frequency**: +1. Empty Code / Missing Return - **38% of failures** +2. Expression Syntax Confusion - **8% of failures** +3. Incorrect Return Wrapper - **5% of failures** +4. Unmatched Expression Brackets - **6% of failures** +5. Missing Null Checks - **Common runtime error** + +--- + +## Error #1: Empty Code or Missing Return Statement + +**Frequency**: Most common error (38% of all validation failures) + +**What Happens**: +- Workflow execution fails +- Next nodes receive no data +- Error: "Code cannot be empty" or "Code must return data" + +### The Problem + +```javascript +// ❌ ERROR: No code at all +// (Empty code field) +``` + +```javascript +// ❌ ERROR: Code executes but doesn't return anything +const items = $input.all(); + +// Process items +for (const item of items) { + console.log(item.json.name); +} + +// Forgot to return! +``` + +```javascript +// ❌ ERROR: Early return path exists, but not all paths return +const items = $input.all(); + +if (items.length === 0) { + return []; // ✅ This path returns +} + +// Process items +const processed = items.map(item => ({json: item.json})); + +// ❌ Forgot to return processed! +``` + +### The Solution + +```javascript +// ✅ CORRECT: Always return data +const items = $input.all(); + +// Process items +const processed = items.map(item => ({ + json: { + ...item.json, + processed: true + } +})); + +return processed; // ✅ Return statement present +``` + +```javascript +// ✅ CORRECT: Return empty array if no items +const items = $input.all(); + +if (items.length === 0) { + return []; // Valid: empty array when no data +} + +// Process and return +return items.map(item => ({json: item.json})); +``` + +```javascript +// ✅ CORRECT: All code paths return +const items = $input.all(); + +if (items.length === 0) { + return []; +} else if (items.length === 1) { + return [{json: {single: true, data: items[0].json}}]; +} else { + return items.map(item => ({json: item.json})); +} + +// All paths covered +``` + +### Checklist + +- [ ] Code field is not empty +- [ ] Return statement exists +- [ ] ALL code paths return data (if/else branches) +- [ ] Return format is correct (`[{json: {...}}]`) +- [ ] Return happens even on errors (use try-catch) + +--- + +## Error #2: Expression Syntax Confusion + +**Frequency**: 8% of validation failures + +**What Happens**: +- Syntax error in code execution +- Error: "Unexpected token" or "Expression syntax is not valid in Code nodes" +- Template variables not evaluated + +### The Problem + +n8n has TWO distinct syntaxes: +1. **Expression syntax** `{{ }}` - Used in OTHER nodes (Set, IF, HTTP Request) +2. **JavaScript** - Used in CODE nodes (no `{{ }}`) + +Many developers mistakenly use expression syntax inside Code nodes. + +```javascript +// ❌ WRONG: Using n8n expression syntax in Code node +const userName = "{{ $json.name }}"; +const userEmail = "{{ $json.body.email }}"; + +return [{ + json: { + name: userName, + email: userEmail + } +}]; + +// Result: Literal string "{{ $json.name }}", NOT the value! +``` + +```javascript +// ❌ WRONG: Trying to evaluate expressions +const value = "{{ $now.toFormat('yyyy-MM-dd') }}"; +``` + +### The Solution + +```javascript +// ✅ CORRECT: Use JavaScript directly (no {{ }}) +const userName = $json.name; +const userEmail = $json.body.email; + +return [{ + json: { + name: userName, + email: userEmail + } +}]; +``` + +```javascript +// ✅ CORRECT: JavaScript template literals (use backticks) +const message = `Hello, ${$json.name}! Your email is ${$json.email}`; + +return [{ + json: { + greeting: message + } +}]; +``` + +```javascript +// ✅ CORRECT: Direct variable access +const item = $input.first().json; + +return [{ + json: { + name: item.name, + email: item.email, + timestamp: new Date().toISOString() // JavaScript Date, not {{ }} + } +}]; +``` + +### Comparison Table + +| Context | Syntax | Example | +|---------|--------|---------| +| Set node | `{{ }}` expressions | `{{ $json.name }}` | +| IF node | `{{ }}` expressions | `{{ $json.age > 18 }}` | +| HTTP Request URL | `{{ }}` expressions | `{{ $json.userId }}` | +| **Code node** | **JavaScript** | `$json.name` | +| **Code node strings** | **Template literals** | `` `Hello ${$json.name}` `` | + +### Quick Fix Guide + +```javascript +// WRONG → RIGHT conversions + +// ❌ "{{ $json.field }}" +// ✅ $json.field + +// ❌ "{{ $now }}" +// ✅ new Date().toISOString() + +// ❌ "{{ $node['HTTP Request'].json.data }}" +// ✅ $node["HTTP Request"].json.data + +// ❌ `{{ $json.firstName }} {{ $json.lastName }}` +// ✅ `${$json.firstName} ${$json.lastName}` +``` + +--- + +## Error #3: Incorrect Return Wrapper Format + +**Frequency**: 5% of validation failures + +**What Happens**: +- Error: "Return value must be an array of objects" +- Error: "Each item must have a json property" +- Next nodes receive malformed data + +### The Problem + +Code nodes MUST return: +- **Array** of objects +- Each object MUST have a **`json` property** + +```javascript +// ❌ WRONG: Returning object instead of array +return { + json: { + result: 'success' + } +}; +// Missing array wrapper [] +``` + +```javascript +// ❌ WRONG: Returning array without json wrapper +return [ + {id: 1, name: 'Alice'}, + {id: 2, name: 'Bob'} +]; +// Missing json property +``` + +```javascript +// ❌ WRONG: Returning plain value +return "processed"; +``` + +```javascript +// ❌ WRONG: Returning items without mapping +return $input.all(); +// Works if items already have json property, but not guaranteed +``` + +```javascript +// ❌ WRONG: Incomplete structure +return [{data: {result: 'success'}}]; +// Should be {json: {...}}, not {data: {...}} +``` + +### The Solution + +```javascript +// ✅ CORRECT: Single result +return [{ + json: { + result: 'success', + timestamp: new Date().toISOString() + } +}]; +``` + +```javascript +// ✅ CORRECT: Multiple results +return [ + {json: {id: 1, name: 'Alice'}}, + {json: {id: 2, name: 'Bob'}}, + {json: {id: 3, name: 'Carol'}} +]; +``` + +```javascript +// ✅ CORRECT: Transforming array +const items = $input.all(); + +return items.map(item => ({ + json: { + id: item.json.id, + name: item.json.name, + processed: true + } +})); +``` + +```javascript +// ✅ CORRECT: Empty result +return []; +// Valid when no data to return +``` + +```javascript +// ✅ CORRECT: Conditional returns +if (shouldProcess) { + return [{json: {result: 'processed'}}]; +} else { + return []; +} +``` + +### Return Format Checklist + +- [ ] Return value is an **array** `[...]` +- [ ] Each array element has **`json` property** +- [ ] Structure is `[{json: {...}}]` or `[{json: {...}}, {json: {...}}]` +- [ ] NOT `{json: {...}}` (missing array wrapper) +- [ ] NOT `[{...}]` (missing json property) + +### Common Scenarios + +```javascript +// Scenario 1: Single object from API +const response = $input.first().json; + +// ✅ CORRECT +return [{json: response}]; + +// ❌ WRONG +return {json: response}; + + +// Scenario 2: Array of objects +const users = $input.all(); + +// ✅ CORRECT +return users.map(user => ({json: user.json})); + +// ❌ WRONG +return users; // Risky - depends on existing structure + + +// Scenario 3: Computed result +const total = $input.all().reduce((sum, item) => sum + item.json.amount, 0); + +// ✅ CORRECT +return [{json: {total}}]; + +// ❌ WRONG +return {total}; + + +// Scenario 4: No results +// ✅ CORRECT +return []; + +// ❌ WRONG +return null; +``` + +--- + +## Error #4: Unmatched Expression Brackets + +**Frequency**: 6% of validation failures + +**What Happens**: +- Parsing error during save +- Error: "Unmatched expression brackets" +- Code appears correct but fails validation + +### The Problem + +This error typically occurs when: +1. Strings contain unbalanced quotes +2. Multi-line strings with special characters +3. Template literals with nested brackets + +```javascript +// ❌ WRONG: Unescaped quote in string +const message = "It's a nice day"; +// Single quote breaks string +``` + +```javascript +// ❌ WRONG: Unbalanced brackets in regex +const pattern = /\{(\w+)\}/; // JSON storage issue +``` + +```javascript +// ❌ WRONG: Multi-line string with quotes +const html = " +
+

Hello

+
+"; +// Quote balance issues +``` + +### The Solution + +```javascript +// ✅ CORRECT: Escape quotes +const message = "It\\'s a nice day"; +// Or use different quotes +const message = "It's a nice day"; // Double quotes work +``` + +```javascript +// ✅ CORRECT: Escape regex properly +const pattern = /\\{(\\w+)\\}/; +``` + +```javascript +// ✅ CORRECT: Template literals for multi-line +const html = ` +
+

Hello

+
+`; +// Backticks handle multi-line and quotes +``` + +```javascript +// ✅ CORRECT: Escape backslashes +const path = "C:\\\\Users\\\\Documents\\\\file.txt"; +``` + +### Escaping Guide + +| Character | Escape As | Example | +|-----------|-----------|---------| +| Single quote in single-quoted string | `\\'` | `'It\\'s working'` | +| Double quote in double-quoted string | `\\"` | `"She said \\"hello\\""` | +| Backslash | `\\\\` | `"C:\\\\path"` | +| Newline | `\\n` | `"Line 1\\nLine 2"` | +| Tab | `\\t` | `"Column1\\tColumn2"` | + +### Best Practices + +```javascript +// ✅ BEST: Use template literals for complex strings +const message = `User ${name} said: "Hello!"`; + +// ✅ BEST: Use template literals for HTML +const html = ` +
+

${title}

+

${content}

+
+`; + +// ✅ BEST: Use template literals for JSON +const jsonString = `{ + "name": "${name}", + "email": "${email}" +}`; +``` + +--- + +## Error #5: Missing Null Checks / Undefined Access + +**Frequency**: Very common runtime error + +**What Happens**: +- Workflow execution stops +- Error: "Cannot read property 'X' of undefined" +- Error: "Cannot read property 'X' of null" +- Crashes on missing data + +### The Problem + +```javascript +// ❌ WRONG: No null check - crashes if user doesn't exist +const email = item.json.user.email; +``` + +```javascript +// ❌ WRONG: Assumes array has items +const firstItem = $input.all()[0].json; +``` + +```javascript +// ❌ WRONG: Assumes nested property exists +const city = $json.address.city; +``` + +```javascript +// ❌ WRONG: No validation before array operations +const names = $json.users.map(user => user.name); +``` + +### The Solution + +```javascript +// ✅ CORRECT: Optional chaining +const email = item.json?.user?.email || 'no-email@example.com'; +``` + +```javascript +// ✅ CORRECT: Check array length +const items = $input.all(); + +if (items.length === 0) { + return []; +} + +const firstItem = items[0].json; +``` + +```javascript +// ✅ CORRECT: Guard clauses +const data = $input.first().json; + +if (!data.address) { + return [{json: {error: 'No address provided'}}]; +} + +const city = data.address.city; +``` + +```javascript +// ✅ CORRECT: Default values +const users = $json.users || []; +const names = users.map(user => user.name || 'Unknown'); +``` + +```javascript +// ✅ CORRECT: Try-catch for risky operations +try { + const email = item.json.user.email.toLowerCase(); + return [{json: {email}}]; +} catch (error) { + return [{ + json: { + error: 'Invalid user data', + details: error.message + } + }]; +} +``` + +### Safe Access Patterns + +```javascript +// Pattern 1: Optional chaining (modern, recommended) +const value = data?.nested?.property?.value; + +// Pattern 2: Logical OR with default +const value = data.property || 'default'; + +// Pattern 3: Ternary check +const value = data.property ? data.property : 'default'; + +// Pattern 4: Guard clause +if (!data.property) { + return []; +} +const value = data.property; + +// Pattern 5: Try-catch +try { + const value = data.nested.property.value; +} catch (error) { + const value = 'default'; +} +``` + +### Webhook Data Safety + +```javascript +// Webhook data requires extra safety + +// ❌ RISKY: Assumes all fields exist +const name = $json.body.user.name; +const email = $json.body.user.email; + +// ✅ SAFE: Check each level +const body = $json.body || {}; +const user = body.user || {}; +const name = user.name || 'Unknown'; +const email = user.email || 'no-email'; + +// ✅ BETTER: Optional chaining +const name = $json.body?.user?.name || 'Unknown'; +const email = $json.body?.user?.email || 'no-email'; +``` + +### Array Safety + +```javascript +// ❌ RISKY: No length check +const items = $input.all(); +const firstId = items[0].json.id; + +// ✅ SAFE: Check length +const items = $input.all(); + +if (items.length > 0) { + const firstId = items[0].json.id; +} else { + // Handle empty case + return []; +} + +// ✅ BETTER: Use $input.first() +const firstItem = $input.first(); +const firstId = firstItem.json.id; // Built-in safety +``` + +### Object Property Safety + +```javascript +// ❌ RISKY: Direct access +const config = $json.settings.advanced.timeout; + +// ✅ SAFE: Step by step with defaults +const settings = $json.settings || {}; +const advanced = settings.advanced || {}; +const timeout = advanced.timeout || 30000; + +// ✅ BETTER: Optional chaining +const timeout = $json.settings?.advanced?.timeout ?? 30000; +// Note: ?? (nullish coalescing) vs || (logical OR) +``` + +--- + +## Error Prevention Checklist + +Use this checklist before deploying Code nodes: + +### Code Structure +- [ ] Code field is not empty +- [ ] Return statement exists +- [ ] All code paths return data + +### Return Format +- [ ] Returns array: `[...]` +- [ ] Each item has `json` property: `{json: {...}}` +- [ ] Format is `[{json: {...}}]` + +### Syntax +- [ ] No `{{ }}` expression syntax (use JavaScript) +- [ ] Template literals use backticks: `` `${variable}` `` +- [ ] All quotes and brackets balanced +- [ ] Strings properly escaped + +### Data Safety +- [ ] Null checks for optional properties +- [ ] Array length checks before access +- [ ] Webhook data accessed via `.body` +- [ ] Try-catch for risky operations +- [ ] Default values for missing data + +### Testing +- [ ] Test with empty input +- [ ] Test with missing fields +- [ ] Test with unexpected data types +- [ ] Check browser console for errors + +--- + +## Quick Error Reference + +| Error Message | Likely Cause | Fix | +|---------------|--------------|-----| +| "Code cannot be empty" | Empty code field | Add meaningful code | +| "Code must return data" | Missing return statement | Add `return [...]` | +| "Return value must be an array" | Returning object instead of array | Wrap in `[...]` | +| "Each item must have json property" | Missing `json` wrapper | Use `{json: {...}}` | +| "Unexpected token" | Expression syntax `{{ }}` in code | Remove `{{ }}`, use JavaScript | +| "Cannot read property X of undefined" | Missing null check | Use optional chaining `?.` | +| "Cannot read property X of null" | Null value access | Add guard clause or default | +| "Unmatched expression brackets" | Quote/bracket imbalance | Check string escaping | + +--- + +## Debugging Tips + +### 1. Use console.log() + +```javascript +const items = $input.all(); +console.log('Items count:', items.length); +console.log('First item:', items[0]); + +// Check browser console (F12) for output +``` + +### 2. Return Intermediate Results + +```javascript +// Debug by returning current state +const items = $input.all(); +const processed = items.map(item => ({json: item.json})); + +// Return to see what you have +return processed; +``` + +### 3. Try-Catch for Troubleshooting + +```javascript +try { + // Your code here + const result = riskyOperation(); + return [{json: {result}}]; +} catch (error) { + // See what failed + return [{ + json: { + error: error.message, + stack: error.stack + } + }]; +} +``` + +### 4. Validate Input Structure + +```javascript +const items = $input.all(); + +// Check what you received +console.log('Input structure:', JSON.stringify(items[0], null, 2)); + +// Then process +``` + +--- + +## Summary + +**Top 5 Errors to Avoid**: +1. **Empty code / missing return** (38%) - Always return data +2. **Expression syntax `{{ }}`** (8%) - Use JavaScript, not expressions +3. **Wrong return format** (5%) - Always `[{json: {...}}]` +4. **Unmatched brackets** (6%) - Escape strings properly +5. **Missing null checks** - Use optional chaining `?.` + +**Quick Prevention**: +- Return `[{json: {...}}]` format +- Use JavaScript, NOT `{{ }}` expressions +- Check for null/undefined before accessing +- Test with empty and invalid data +- Use browser console for debugging + +**See Also**: +- [SKILL.md](SKILL.md) - Overview and best practices +- [DATA_ACCESS.md](DATA_ACCESS.md) - Safe data access patterns +- [COMMON_PATTERNS.md](COMMON_PATTERNS.md) - Working examples diff --git a/docs/skills/n8n-automation/javascript-code/README.md b/docs/skills/n8n-automation/javascript-code/README.md new file mode 100644 index 0000000..df3464f --- /dev/null +++ b/docs/skills/n8n-automation/javascript-code/README.md @@ -0,0 +1,350 @@ +# n8n Code JavaScript + +Expert guidance for writing JavaScript code in n8n Code nodes. + +--- + +## Purpose + +Teaches how to write effective JavaScript in n8n Code nodes, avoid common errors, and use built-in functions effectively. + +--- + +## Activates On + +**Trigger keywords**: +- "javascript code node" +- "write javascript in n8n" +- "code node javascript" +- "$input syntax" +- "$json syntax" +- "$helpers.httpRequest" +- "DateTime luxon" +- "code node error" +- "webhook data code" +- "return format code node" + +**Common scenarios**: +- Writing JavaScript code in Code nodes +- Troubleshooting Code node errors +- Making HTTP requests from code +- Working with dates and times +- Accessing webhook data +- Choosing between All Items and Each Item mode + +--- + +## What You'll Learn + +### Quick Start +- Mode selection (All Items vs Each Item) +- Data access patterns ($input.all(), $input.first(), $input.item) +- Correct return format: `[{json: {...}}]` +- Webhook data structure (.body nesting) +- Built-in functions overview + +### Data Access Mastery +- $input.all() - Batch operations (most common) +- $input.first() - Single item operations +- $input.item - Each Item mode processing +- $node - Reference other workflow nodes +- **Critical gotcha**: Webhook data under `.body` + +### Common Patterns (Production-Tested) +1. Multi-source Data Aggregation +2. Regex Filtering & Pattern Matching +3. Markdown Parsing & Structured Extraction +4. JSON Comparison & Validation +5. CRM Data Transformation +6. Release Information Processing +7. Array Transformation with Context +8. Slack Block Kit Formatting +9. Top N Filtering & Ranking +10. String Aggregation & Reporting + +### Error Prevention +Top 5 errors to avoid: +1. **Empty code / missing return** (38% of failures) +2. **Expression syntax confusion** (using `{{}}` in code) +3. **Incorrect return format** (missing array wrapper or json property) +4. **Unmatched brackets** (string escaping issues) +5. **Missing null checks** (crashes on undefined) + +### Built-in Functions +- **$helpers.httpRequest()** - Make HTTP requests +- **DateTime (Luxon)** - Advanced date/time operations +- **$jmespath()** - Query JSON structures +- **$getWorkflowStaticData()** - Persistent storage +- Standard JavaScript globals (Math, JSON, console) +- Available Node.js modules (crypto, Buffer, URL) + +--- + +## File Structure + +``` +n8n-code-javascript/ +├── SKILL.md (500 lines) +│ Overview, quick start, mode selection, best practices +│ - Mode selection guide (All Items vs Each Item) +│ - Data access patterns overview +│ - Return format requirements +│ - Critical webhook gotcha +│ - Error prevention overview +│ - Quick reference checklist +│ +├── DATA_ACCESS.md (400 lines) +│ Complete data access patterns +│ - $input.all() - Most common (26% usage) +│ - $input.first() - Very common (25% usage) +│ - $input.item - Each Item mode (19% usage) +│ - $node - Reference other nodes +│ - Webhook data structure (.body nesting) +│ - Choosing the right pattern +│ - Common mistakes to avoid +│ +├── COMMON_PATTERNS.md (600 lines) +│ 10 production-tested patterns +│ - Pattern 1: Multi-source Aggregation +│ - Pattern 2: Regex Filtering +│ - Pattern 3: Markdown Parsing +│ - Pattern 4: JSON Comparison +│ - Pattern 5: CRM Transformation +│ - Pattern 6: Release Processing +│ - Pattern 7: Array Transformation +│ - Pattern 8: Slack Block Kit +│ - Pattern 9: Top N Filtering +│ - Pattern 10: String Aggregation +│ - Pattern selection guide +│ +├── ERROR_PATTERNS.md (450 lines) +│ Top 5 errors with solutions +│ - Error #1: Empty Code / Missing Return (38%) +│ - Error #2: Expression Syntax Confusion (8%) +│ - Error #3: Incorrect Return Wrapper (5%) +│ - Error #4: Unmatched Brackets (6%) +│ - Error #5: Missing Null Checks +│ - Error prevention checklist +│ - Quick error reference +│ - Debugging tips +│ +├── BUILTIN_FUNCTIONS.md (450 lines) +│ Complete built-in function reference +│ - $helpers.httpRequest() API reference +│ - DateTime (Luxon) complete guide +│ - $jmespath() JSON querying +│ - $getWorkflowStaticData() persistent storage +│ - Standard JavaScript globals +│ - Available Node.js modules +│ - What's NOT available +│ +└── README.md (this file) + Skill metadata and overview +``` + +**Total**: ~2,400 lines across 6 files + +--- + +## Coverage + +### Mode Selection +- **Run Once for All Items** - Recommended for 95% of use cases +- **Run Once for Each Item** - Specialized cases only +- Decision guide and performance implications + +### Data Access +- Most common patterns with usage statistics +- Webhook data structure (critical .body gotcha) +- Safe access patterns with null checks +- When to use which pattern + +### Error Prevention +- Top 5 errors covering 62%+ of all failures +- Clear wrong vs right examples +- Error prevention checklist +- Debugging tips and console.log usage + +### Production Patterns +- 10 patterns from real workflows +- Complete working examples +- Use cases and key techniques +- Pattern selection guide + +### Built-in Functions +- Complete $helpers.httpRequest() reference +- DateTime/Luxon operations (formatting, parsing, arithmetic) +- $jmespath() for JSON queries +- Persistent storage with $getWorkflowStaticData() +- Standard JavaScript and Node.js modules + +--- + +## Critical Gotchas Highlighted + +### #1: Webhook Data Structure +**MOST COMMON MISTAKE**: Webhook data is under `.body` + +```javascript +// ❌ WRONG +const name = $json.name; + +// ✅ CORRECT +const name = $json.body.name; +``` + +### #2: Return Format +**CRITICAL**: Must return array with json property + +```javascript +// ❌ WRONG +return {json: {result: 'success'}}; + +// ✅ CORRECT +return [{json: {result: 'success'}}]; +``` + +### #3: Expression Syntax +**Don't use `{{}}` in Code nodes** + +```javascript +// ❌ WRONG +const value = "{{ $json.field }}"; + +// ✅ CORRECT +const value = $json.field; +``` + +--- + +## Integration with Other Skills + +### n8n Expression Syntax +- **Distinction**: Expressions use `{{}}` in OTHER nodes +- **Code nodes**: Use JavaScript directly (no `{{}}`) +- **When to use each**: Code vs expressions decision guide + +### n8n MCP Tools Expert +- Find Code node: `search_nodes({query: "code"})` +- Get configuration: `get_node_essentials("nodes-base.code")` +- Validate code: `validate_node_operation()` + +### n8n Node Configuration +- Mode selection (All Items vs Each Item) +- Language selection (JavaScript vs Python) +- Understanding property dependencies + +### n8n Workflow Patterns +- Code nodes in transformation step +- Webhook → Code → API pattern +- Error handling in workflows + +### n8n Validation Expert +- Validate Code node configuration +- Handle validation errors +- Auto-fix common issues + +--- + +## When to Use Code Node + +**Use Code node when:** +- ✅ Complex transformations requiring multiple steps +- ✅ Custom calculations or business logic +- ✅ Recursive operations +- ✅ API response parsing with complex structure +- ✅ Multi-step conditionals +- ✅ Data aggregation across items + +**Consider other nodes when:** +- ❌ Simple field mapping → Use **Set** node +- ❌ Basic filtering → Use **Filter** node +- ❌ Simple conditionals → Use **IF** or **Switch** node +- ❌ HTTP requests only → Use **HTTP Request** node + +**Code node excels at**: Complex logic that would require chaining many simple nodes + +--- + +## Success Metrics + +**Before this skill**: +- Users confused by mode selection +- Frequent return format errors +- Expression syntax mistakes +- Webhook data access failures +- Missing null check crashes + +**After this skill**: +- Clear mode selection guidance +- Understanding of return format +- JavaScript vs expression distinction +- Correct webhook data access +- Safe null-handling patterns +- Production-ready code patterns + +--- + +## Quick Reference + +### Essential Rules +1. Choose "All Items" mode (recommended) +2. Access data: `$input.all()`, `$input.first()`, `$input.item` +3. **MUST return**: `[{json: {...}}]` format +4. **Webhook data**: Under `.body` property +5. **No `{{}}` syntax**: Use JavaScript directly + +### Most Common Patterns +- Batch processing → $input.all() + map/filter +- Single item → $input.first() +- Aggregation → reduce() +- HTTP requests → $helpers.httpRequest() +- Date handling → DateTime (Luxon) + +### Error Prevention +- Always return data +- Check for null/undefined +- Use try-catch for risky operations +- Test with empty input +- Use console.log() for debugging + +--- + +## Related Documentation + +- **n8n Code Node Guide**: https://docs.n8n.io/code/code-node/ +- **Built-in Methods Reference**: https://docs.n8n.io/code-examples/methods-variables-reference/ +- **Luxon Documentation**: https://moment.github.io/luxon/ + +--- + +## Evaluations + +**5 test scenarios** covering: +1. Webhook body gotcha (most common mistake) +2. Return format error (missing array wrapper) +3. HTTP request with $helpers.httpRequest() +4. Aggregation pattern with $input.all() +5. Expression syntax confusion (using `{{}}`) + +Each evaluation tests skill activation, correct guidance, and reference to appropriate documentation files. + +--- + +## Version History + +- **v1.0** (2025-01-20): Initial implementation + - SKILL.md with comprehensive overview + - DATA_ACCESS.md covering all access patterns + - COMMON_PATTERNS.md with 10 production patterns + - ERROR_PATTERNS.md covering top 5 errors + - BUILTIN_FUNCTIONS.md complete reference + - 5 evaluation scenarios + +--- + +## Author + +Conceived by Romuald Członkowski - [www.aiadvisors.pl/en](https://www.aiadvisors.pl/en) + +Part of the n8n-skills collection. diff --git a/docs/skills/n8n-automation/javascript-code/SKILL.md b/docs/skills/n8n-automation/javascript-code/SKILL.md new file mode 100644 index 0000000..449a7b6 --- /dev/null +++ b/docs/skills/n8n-automation/javascript-code/SKILL.md @@ -0,0 +1,699 @@ +--- +name: n8n-code-javascript +description: Write JavaScript code in n8n Code nodes. Use when writing JavaScript in n8n, using $input/$json/$node syntax, making HTTP requests with $helpers, working with dates using DateTime, troubleshooting Code node errors, or choosing between Code node modes. +--- + +# JavaScript Code Node + +Expert guidance for writing JavaScript code in n8n Code nodes. + +--- + +## Quick Start + +```javascript +// Basic template for Code nodes +const items = $input.all(); + +// Process data +const processed = items.map(item => ({ + json: { + ...item.json, + processed: true, + timestamp: new Date().toISOString() + } +})); + +return processed; +``` + +### Essential Rules + +1. **Choose "Run Once for All Items" mode** (recommended for most use cases) +2. **Access data**: `$input.all()`, `$input.first()`, or `$input.item` +3. **CRITICAL**: Must return `[{json: {...}}]` format +4. **CRITICAL**: Webhook data is under `$json.body` (not `$json` directly) +5. **Built-ins available**: $helpers.httpRequest(), DateTime (Luxon), $jmespath() + +--- + +## Mode Selection Guide + +The Code node offers two execution modes. Choose based on your use case: + +### Run Once for All Items (Recommended - Default) + +**Use this mode for:** 95% of use cases + +- **How it works**: Code executes **once** regardless of input count +- **Data access**: `$input.all()` or `items` array +- **Best for**: Aggregation, filtering, batch processing, transformations, API calls with all data +- **Performance**: Faster for multiple items (single execution) + +```javascript +// Example: Calculate total from all items +const allItems = $input.all(); +const total = allItems.reduce((sum, item) => sum + (item.json.amount || 0), 0); + +return [{ + json: { + total, + count: allItems.length, + average: total / allItems.length + } +}]; +``` + +**When to use:** +- ✅ Comparing items across the dataset +- ✅ Calculating totals, averages, or statistics +- ✅ Sorting or ranking items +- ✅ Deduplication +- ✅ Building aggregated reports +- ✅ Combining data from multiple items + +### Run Once for Each Item + +**Use this mode for:** Specialized cases only + +- **How it works**: Code executes **separately** for each input item +- **Data access**: `$input.item` or `$item` +- **Best for**: Item-specific logic, independent operations, per-item validation +- **Performance**: Slower for large datasets (multiple executions) + +```javascript +// Example: Add processing timestamp to each item +const item = $input.item; + +return [{ + json: { + ...item.json, + processed: true, + processedAt: new Date().toISOString() + } +}]; +``` + +**When to use:** +- ✅ Each item needs independent API call +- ✅ Per-item validation with different error handling +- ✅ Item-specific transformations based on item properties +- ✅ When items must be processed separately for business logic + +**Decision Shortcut:** +- **Need to look at multiple items?** → Use "All Items" mode +- **Each item completely independent?** → Use "Each Item" mode +- **Not sure?** → Use "All Items" mode (you can always loop inside) + +--- + +## Data Access Patterns + +### Pattern 1: $input.all() - Most Common + +**Use when**: Processing arrays, batch operations, aggregations + +```javascript +// Get all items from previous node +const allItems = $input.all(); + +// Filter, map, reduce as needed +const valid = allItems.filter(item => item.json.status === 'active'); +const mapped = valid.map(item => ({ + json: { + id: item.json.id, + name: item.json.name + } +})); + +return mapped; +``` + +### Pattern 2: $input.first() - Very Common + +**Use when**: Working with single objects, API responses, first-in-first-out + +```javascript +// Get first item only +const firstItem = $input.first(); +const data = firstItem.json; + +return [{ + json: { + result: processData(data), + processedAt: new Date().toISOString() + } +}]; +``` + +### Pattern 3: $input.item - Each Item Mode Only + +**Use when**: In "Run Once for Each Item" mode + +```javascript +// Current item in loop (Each Item mode only) +const currentItem = $input.item; + +return [{ + json: { + ...currentItem.json, + itemProcessed: true + } +}]; +``` + +### Pattern 4: $node - Reference Other Nodes + +**Use when**: Need data from specific nodes in workflow + +```javascript +// Get output from specific node +const webhookData = $node["Webhook"].json; +const httpData = $node["HTTP Request"].json; + +return [{ + json: { + combined: { + webhook: webhookData, + api: httpData + } + } +}]; +``` + +**See**: [DATA_ACCESS.md](DATA_ACCESS.md) for comprehensive guide + +--- + +## Critical: Webhook Data Structure + +**MOST COMMON MISTAKE**: Webhook data is nested under `.body` + +```javascript +// ❌ WRONG - Will return undefined +const name = $json.name; +const email = $json.email; + +// ✅ CORRECT - Webhook data is under .body +const name = $json.body.name; +const email = $json.body.email; + +// Or with $input +const webhookData = $input.first().json.body; +const name = webhookData.name; +``` + +**Why**: Webhook node wraps all request data under `body` property. This includes POST data, query parameters, and JSON payloads. + +**See**: [DATA_ACCESS.md](DATA_ACCESS.md) for full webhook structure details + +--- + +## Return Format Requirements + +**CRITICAL RULE**: Always return array of objects with `json` property + +### Correct Return Formats + +```javascript +// ✅ Single result +return [{ + json: { + field1: value1, + field2: value2 + } +}]; + +// ✅ Multiple results +return [ + {json: {id: 1, data: 'first'}}, + {json: {id: 2, data: 'second'}} +]; + +// ✅ Transformed array +const transformed = $input.all() + .filter(item => item.json.valid) + .map(item => ({ + json: { + id: item.json.id, + processed: true + } + })); +return transformed; + +// ✅ Empty result (when no data to return) +return []; + +// ✅ Conditional return +if (shouldProcess) { + return [{json: processedData}]; +} else { + return []; +} +``` + +### Incorrect Return Formats + +```javascript +// ❌ WRONG: Object without array wrapper +return { + json: {field: value} +}; + +// ❌ WRONG: Array without json wrapper +return [{field: value}]; + +// ❌ WRONG: Plain string +return "processed"; + +// ❌ WRONG: Raw data without mapping +return $input.all(); // Missing .map() + +// ❌ WRONG: Incomplete structure +return [{data: value}]; // Should be {json: value} +``` + +**Why it matters**: Next nodes expect array format. Incorrect format causes workflow execution to fail. + +**See**: [ERROR_PATTERNS.md](ERROR_PATTERNS.md) #3 for detailed error solutions + +--- + +## Common Patterns Overview + +Based on production workflows, here are the most useful patterns: + +### 1. Multi-Source Data Aggregation +Combine data from multiple APIs, webhooks, or nodes + +```javascript +const allItems = $input.all(); +const results = []; + +for (const item of allItems) { + const sourceName = item.json.name || 'Unknown'; + // Parse source-specific structure + if (sourceName === 'API1' && item.json.data) { + results.push({ + json: { + title: item.json.data.title, + source: 'API1' + } + }); + } +} + +return results; +``` + +### 2. Filtering with Regex +Extract patterns, mentions, or keywords from text + +```javascript +const pattern = /\b([A-Z]{2,5})\b/g; +const matches = {}; + +for (const item of $input.all()) { + const text = item.json.text; + const found = text.match(pattern); + + if (found) { + found.forEach(match => { + matches[match] = (matches[match] || 0) + 1; + }); + } +} + +return [{json: {matches}}]; +``` + +### 3. Data Transformation & Enrichment +Map fields, normalize formats, add computed fields + +```javascript +const items = $input.all(); + +return items.map(item => { + const data = item.json; + const nameParts = data.name.split(' '); + + return { + json: { + first_name: nameParts[0], + last_name: nameParts.slice(1).join(' '), + email: data.email, + created_at: new Date().toISOString() + } + }; +}); +``` + +### 4. Top N Filtering & Ranking +Sort and limit results + +```javascript +const items = $input.all(); + +const topItems = items + .sort((a, b) => (b.json.score || 0) - (a.json.score || 0)) + .slice(0, 10); + +return topItems.map(item => ({json: item.json})); +``` + +### 5. Aggregation & Reporting +Sum, count, group data + +```javascript +const items = $input.all(); +const total = items.reduce((sum, item) => sum + (item.json.amount || 0), 0); + +return [{ + json: { + total, + count: items.length, + average: total / items.length, + timestamp: new Date().toISOString() + } +}]; +``` + +**See**: [COMMON_PATTERNS.md](COMMON_PATTERNS.md) for 10 detailed production patterns + +--- + +## Error Prevention - Top 5 Mistakes + +### #1: Empty Code or Missing Return (Most Common) + +```javascript +// ❌ WRONG: No return statement +const items = $input.all(); +// ... processing code ... +// Forgot to return! + +// ✅ CORRECT: Always return data +const items = $input.all(); +// ... processing ... +return items.map(item => ({json: item.json})); +``` + +### #2: Expression Syntax Confusion + +```javascript +// ❌ WRONG: Using n8n expression syntax in code +const value = "{{ $json.field }}"; + +// ✅ CORRECT: Use JavaScript template literals +const value = `${$json.field}`; + +// ✅ CORRECT: Direct access +const value = $input.first().json.field; +``` + +### #3: Incorrect Return Wrapper + +```javascript +// ❌ WRONG: Returning object instead of array +return {json: {result: 'success'}}; + +// ✅ CORRECT: Array wrapper required +return [{json: {result: 'success'}}]; +``` + +### #4: Missing Null Checks + +```javascript +// ❌ WRONG: Crashes if field doesn't exist +const value = item.json.user.email; + +// ✅ CORRECT: Safe access with optional chaining +const value = item.json?.user?.email || 'no-email@example.com'; + +// ✅ CORRECT: Guard clause +if (!item.json.user) { + return []; +} +const value = item.json.user.email; +``` + +### #5: Webhook Body Nesting + +```javascript +// ❌ WRONG: Direct access to webhook data +const email = $json.email; + +// ✅ CORRECT: Webhook data under .body +const email = $json.body.email; +``` + +**See**: [ERROR_PATTERNS.md](ERROR_PATTERNS.md) for comprehensive error guide + +--- + +## Built-in Functions & Helpers + +### $helpers.httpRequest() + +Make HTTP requests from within code: + +```javascript +const response = await $helpers.httpRequest({ + method: 'GET', + url: 'https://api.example.com/data', + headers: { + 'Authorization': 'Bearer token', + 'Content-Type': 'application/json' + } +}); + +return [{json: {data: response}}]; +``` + +### DateTime (Luxon) + +Date and time operations: + +```javascript +// Current time +const now = DateTime.now(); + +// Format dates +const formatted = now.toFormat('yyyy-MM-dd'); +const iso = now.toISO(); + +// Date arithmetic +const tomorrow = now.plus({days: 1}); +const lastWeek = now.minus({weeks: 1}); + +return [{ + json: { + today: formatted, + tomorrow: tomorrow.toFormat('yyyy-MM-dd') + } +}]; +``` + +### $jmespath() + +Query JSON structures: + +```javascript +const data = $input.first().json; + +// Filter array +const adults = $jmespath(data, 'users[?age >= `18`]'); + +// Extract fields +const names = $jmespath(data, 'users[*].name'); + +return [{json: {adults, names}}]; +``` + +**See**: [BUILTIN_FUNCTIONS.md](BUILTIN_FUNCTIONS.md) for complete reference + +--- + +## Best Practices + +### 1. Always Validate Input Data + +```javascript +const items = $input.all(); + +// Check if data exists +if (!items || items.length === 0) { + return []; +} + +// Validate structure +if (!items[0].json) { + return [{json: {error: 'Invalid input format'}}]; +} + +// Continue processing... +``` + +### 2. Use Try-Catch for Error Handling + +```javascript +try { + const response = await $helpers.httpRequest({ + url: 'https://api.example.com/data' + }); + + return [{json: {success: true, data: response}}]; +} catch (error) { + return [{ + json: { + success: false, + error: error.message + } + }]; +} +``` + +### 3. Prefer Array Methods Over Loops + +```javascript +// ✅ GOOD: Functional approach +const processed = $input.all() + .filter(item => item.json.valid) + .map(item => ({json: {id: item.json.id}})); + +// ❌ SLOWER: Manual loop +const processed = []; +for (const item of $input.all()) { + if (item.json.valid) { + processed.push({json: {id: item.json.id}}); + } +} +``` + +### 4. Filter Early, Process Late + +```javascript +// ✅ GOOD: Filter first to reduce processing +const processed = $input.all() + .filter(item => item.json.status === 'active') // Reduce dataset first + .map(item => expensiveTransformation(item)); // Then transform + +// ❌ WASTEFUL: Transform everything, then filter +const processed = $input.all() + .map(item => expensiveTransformation(item)) // Wastes CPU + .filter(item => item.json.status === 'active'); +``` + +### 5. Use Descriptive Variable Names + +```javascript +// ✅ GOOD: Clear intent +const activeUsers = $input.all().filter(item => item.json.active); +const totalRevenue = activeUsers.reduce((sum, user) => sum + user.json.revenue, 0); + +// ❌ BAD: Unclear purpose +const a = $input.all().filter(item => item.json.active); +const t = a.reduce((s, u) => s + u.json.revenue, 0); +``` + +### 6. Debug with console.log() + +```javascript +// Debug statements appear in browser console +const items = $input.all(); +console.log(`Processing ${items.length} items`); + +for (const item of items) { + console.log('Item data:', item.json); + // Process... +} + +return result; +``` + +--- + +## When to Use Code Node + +Use Code node when: +- ✅ Complex transformations requiring multiple steps +- ✅ Custom calculations or business logic +- ✅ Recursive operations +- ✅ API response parsing with complex structure +- ✅ Multi-step conditionals +- ✅ Data aggregation across items + +Consider other nodes when: +- ❌ Simple field mapping → Use **Set** node +- ❌ Basic filtering → Use **Filter** node +- ❌ Simple conditionals → Use **IF** or **Switch** node +- ❌ HTTP requests only → Use **HTTP Request** node + +**Code node excels at**: Complex logic that would require chaining many simple nodes + +--- + +## Integration with Other Skills + +### Works With: + +**n8n Expression Syntax**: +- Expressions use `{{ }}` syntax in other nodes +- Code nodes use JavaScript directly (no `{{ }}`) +- When to use expressions vs code + +**n8n MCP Tools Expert**: +- How to find Code node: `search_nodes({query: "code"})` +- Get configuration help: `get_node_essentials("nodes-base.code")` +- Validate code: `validate_node_operation()` + +**n8n Node Configuration**: +- Mode selection (All Items vs Each Item) +- Language selection (JavaScript vs Python) +- Understanding property dependencies + +**n8n Workflow Patterns**: +- Code nodes in transformation step +- Webhook → Code → API pattern +- Error handling in workflows + +**n8n Validation Expert**: +- Validate Code node configuration +- Handle validation errors +- Auto-fix common issues + +--- + +## Quick Reference Checklist + +Before deploying Code nodes, verify: + +- [ ] **Code is not empty** - Must have meaningful logic +- [ ] **Return statement exists** - Must return array of objects +- [ ] **Proper return format** - Each item: `{json: {...}}` +- [ ] **Data access correct** - Using `$input.all()`, `$input.first()`, or `$input.item` +- [ ] **No n8n expressions** - Use JavaScript template literals: `` `${value}` `` +- [ ] **Error handling** - Guard clauses for null/undefined inputs +- [ ] **Webhook data** - Access via `.body` if from webhook +- [ ] **Mode selection** - "All Items" for most cases +- [ ] **Performance** - Prefer map/filter over manual loops +- [ ] **Output consistent** - All code paths return same structure + +--- + +## Additional Resources + +### Related Files +- [DATA_ACCESS.md](DATA_ACCESS.md) - Comprehensive data access patterns +- [COMMON_PATTERNS.md](COMMON_PATTERNS.md) - 10 production-tested patterns +- [ERROR_PATTERNS.md](ERROR_PATTERNS.md) - Top 5 errors and solutions +- [BUILTIN_FUNCTIONS.md](BUILTIN_FUNCTIONS.md) - Complete built-in reference + +### n8n Documentation +- Code Node Guide: https://docs.n8n.io/code/code-node/ +- Built-in Methods: https://docs.n8n.io/code-examples/methods-variables-reference/ +- Luxon Documentation: https://moment.github.io/luxon/ + +--- + +**Ready to write JavaScript in n8n Code nodes!** Start with simple transformations, use the error patterns guide to avoid common mistakes, and reference the pattern library for production-ready examples. diff --git a/docs/skills/n8n-automation/node-configuration/DEPENDENCIES.md b/docs/skills/n8n-automation/node-configuration/DEPENDENCIES.md new file mode 100644 index 0000000..ad48183 --- /dev/null +++ b/docs/skills/n8n-automation/node-configuration/DEPENDENCIES.md @@ -0,0 +1,800 @@ +# Property Dependencies Guide + +Deep dive into n8n property dependencies and displayOptions mechanism. + +--- + +## What Are Property Dependencies? + +**Definition**: Rules that control when fields are visible or required based on other field values. + +**Mechanism**: `displayOptions` in node schema + +**Purpose**: +- Show relevant fields only +- Hide irrelevant fields +- Simplify configuration UX +- Prevent invalid configurations + +--- + +## displayOptions Structure + +### Basic Format + +```javascript +{ + "name": "fieldName", + "type": "string", + "displayOptions": { + "show": { + "otherField": ["value1", "value2"] + } + } +} +``` + +**Translation**: Show `fieldName` when `otherField` equals "value1" OR "value2" + +### Show vs Hide + +#### show (Most Common) + +**Show field when condition matches**: +```javascript +{ + "name": "body", + "displayOptions": { + "show": { + "sendBody": [true] + } + } +} +``` + +**Meaning**: Show `body` when `sendBody = true` + +#### hide (Less Common) + +**Hide field when condition matches**: +```javascript +{ + "name": "advanced", + "displayOptions": { + "hide": { + "simpleMode": [true] + } + } +} +``` + +**Meaning**: Hide `advanced` when `simpleMode = true` + +### Multiple Conditions (AND Logic) + +```javascript +{ + "name": "body", + "displayOptions": { + "show": { + "sendBody": [true], + "method": ["POST", "PUT", "PATCH"] + } + } +} +``` + +**Meaning**: Show `body` when: +- `sendBody = true` AND +- `method IN (POST, PUT, PATCH)` + +**All conditions must match** (AND logic) + +### Multiple Values (OR Logic) + +```javascript +{ + "name": "someField", + "displayOptions": { + "show": { + "method": ["POST", "PUT", "PATCH"] + } + } +} +``` + +**Meaning**: Show `someField` when: +- `method = POST` OR +- `method = PUT` OR +- `method = PATCH` + +**Any value matches** (OR logic) + +--- + +## Common Dependency Patterns + +### Pattern 1: Boolean Toggle + +**Use case**: Optional feature flag + +**Example**: HTTP Request sendBody +```javascript +// Field: sendBody (boolean) +{ + "name": "sendBody", + "type": "boolean", + "default": false +} + +// Field: body (depends on sendBody) +{ + "name": "body", + "displayOptions": { + "show": { + "sendBody": [true] + } + } +} +``` + +**Flow**: +1. User sees sendBody checkbox +2. When checked → body field appears +3. When unchecked → body field hides + +### Pattern 2: Resource/Operation Cascade + +**Use case**: Different operations show different fields + +**Example**: Slack message operations +```javascript +// Operation: post +{ + "name": "channel", + "displayOptions": { + "show": { + "resource": ["message"], + "operation": ["post"] + } + } +} + +// Operation: update +{ + "name": "messageId", + "displayOptions": { + "show": { + "resource": ["message"], + "operation": ["update"] + } + } +} +``` + +**Flow**: +1. User selects resource="message" +2. User selects operation="post" → sees channel +3. User changes to operation="update" → channel hides, messageId shows + +### Pattern 3: Type-Specific Configuration + +**Use case**: Different types need different fields + +**Example**: IF node conditions +```javascript +// String operations +{ + "name": "value2", + "displayOptions": { + "show": { + "conditions.string.0.operation": ["equals", "notEquals", "contains"] + } + } +} + +// Unary operations (isEmpty) don't show value2 +{ + "displayOptions": { + "hide": { + "conditions.string.0.operation": ["isEmpty", "isNotEmpty"] + } + } +} +``` + +### Pattern 4: Method-Specific Fields + +**Use case**: HTTP methods have different options + +**Example**: HTTP Request +```javascript +// Query parameters (all methods can have) +{ + "name": "queryParameters", + "displayOptions": { + "show": { + "sendQuery": [true] + } + } +} + +// Body (only certain methods) +{ + "name": "body", + "displayOptions": { + "show": { + "sendBody": [true], + "method": ["POST", "PUT", "PATCH", "DELETE"] + } + } +} +``` + +--- + +## Using get_property_dependencies + +### Basic Usage + +```javascript +const deps = get_property_dependencies({ + nodeType: "nodes-base.httpRequest" +}); +``` + +### Example Response + +```javascript +{ + "nodeType": "n8n-nodes-base.httpRequest", + "dependencies": { + "body": { + "shows_when": { + "sendBody": [true], + "method": ["POST", "PUT", "PATCH", "DELETE"] + }, + "required_when_shown": true + }, + "queryParameters": { + "shows_when": { + "sendQuery": [true] + }, + "required_when_shown": false + }, + "headerParameters": { + "shows_when": { + "sendHeaders": [true] + }, + "required_when_shown": false + } + } +} +``` + +### When to Use + +**✅ Use when**: +- Validation fails with "missing field" but you don't see that field +- A field appears/disappears unexpectedly +- You need to understand what controls field visibility +- Building dynamic configuration tools + +**❌ Don't use when**: +- Simple configuration (use get_node_essentials) +- Just starting configuration +- Field requirements are obvious + +--- + +## Complex Dependency Examples + +### Example 1: HTTP Request Complete Flow + +**Scenario**: Configuring POST with JSON body + +**Step 1**: Set method +```javascript +{ + "method": "POST" + // → sendBody becomes visible +} +``` + +**Step 2**: Enable body +```javascript +{ + "method": "POST", + "sendBody": true + // → body field becomes visible AND required +} +``` + +**Step 3**: Configure body +```javascript +{ + "method": "POST", + "sendBody": true, + "body": { + "contentType": "json" + // → content field becomes visible AND required + } +} +``` + +**Step 4**: Add content +```javascript +{ + "method": "POST", + "sendBody": true, + "body": { + "contentType": "json", + "content": { + "name": "John", + "email": "john@example.com" + } + } +} +// ✅ Valid! +``` + +**Dependency chain**: +``` +method=POST + → sendBody visible + → sendBody=true + → body visible + required + → body.contentType=json + → body.content visible + required +``` + +### Example 2: IF Node Operator Dependencies + +**Scenario**: String comparison with different operators + +**Binary operator** (equals): +```javascript +{ + "conditions": { + "string": [ + { + "operation": "equals" + // → value1 required + // → value2 required + // → singleValue should NOT be set + } + ] + } +} +``` + +**Unary operator** (isEmpty): +```javascript +{ + "conditions": { + "string": [ + { + "operation": "isEmpty" + // → value1 required + // → value2 should NOT be set + // → singleValue should be true (auto-added) + } + ] + } +} +``` + +**Dependency table**: + +| Operator | value1 | value2 | singleValue | +|---|---|---|---| +| equals | Required | Required | false | +| notEquals | Required | Required | false | +| contains | Required | Required | false | +| isEmpty | Required | Hidden | true | +| isNotEmpty | Required | Hidden | true | + +### Example 3: Slack Operation Matrix + +**Scenario**: Different Slack operations show different fields + +```javascript +// post message +{ + "resource": "message", + "operation": "post" + // Shows: channel (required), text (required), attachments, blocks +} + +// update message +{ + "resource": "message", + "operation": "update" + // Shows: messageId (required), text (required), channel (optional) +} + +// delete message +{ + "resource": "message", + "operation": "delete" + // Shows: messageId (required), channel (required) + // Hides: text, attachments, blocks +} + +// get message +{ + "resource": "message", + "operation": "get" + // Shows: messageId (required), channel (required) + // Hides: text, attachments, blocks +} +``` + +**Field visibility matrix**: + +| Field | post | update | delete | get | +|---|---|---|---|---| +| channel | Required | Optional | Required | Required | +| text | Required | Required | Hidden | Hidden | +| messageId | Hidden | Required | Required | Required | +| attachments | Optional | Optional | Hidden | Hidden | +| blocks | Optional | Optional | Hidden | Hidden | + +--- + +## Nested Dependencies + +### What Are They? + +**Definition**: Dependencies within object properties + +**Example**: HTTP Request body.contentType controls body.content structure + +```javascript +{ + "body": { + "contentType": "json", + // → content expects JSON object + "content": { + "key": "value" + } + } +} + +{ + "body": { + "contentType": "form-data", + // → content expects form fields array + "content": [ + { + "name": "field1", + "value": "value1" + } + ] + } +} +``` + +### How to Handle + +**Strategy**: Configure parent first, then children + +```javascript +// Step 1: Parent +{ + "body": { + "contentType": "json" // Set parent first + } +} + +// Step 2: Children (structure determined by parent) +{ + "body": { + "contentType": "json", + "content": { // JSON object format + "key": "value" + } + } +} +``` + +--- + +## Auto-Sanitization and Dependencies + +### What Auto-Sanitization Fixes + +**Operator structure issues** (IF/Switch nodes): + +**Example**: singleValue property +```javascript +// You configure (missing singleValue) +{ + "type": "boolean", + "operation": "isEmpty" + // Missing singleValue +} + +// Auto-sanitization adds it +{ + "type": "boolean", + "operation": "isEmpty", + "singleValue": true // ✅ Added automatically +} +``` + +### What It Doesn't Fix + +**Missing required fields**: +```javascript +// You configure (missing channel) +{ + "resource": "message", + "operation": "post", + "text": "Hello" + // Missing required field: channel +} + +// Auto-sanitization does NOT add +// You must add it yourself +{ + "resource": "message", + "operation": "post", + "channel": "#general", // ← You must add + "text": "Hello" +} +``` + +--- + +## Troubleshooting Dependencies + +### Problem 1: "Field X is required but not visible" + +**Error**: +```json +{ + "type": "missing_required", + "property": "body", + "message": "body is required" +} +``` + +**But you don't see body field in configuration!** + +**Solution**: +```javascript +// Check dependencies +const deps = get_property_dependencies({ + nodeType: "nodes-base.httpRequest" +}); + +// Find that body shows when sendBody=true +// Add sendBody +{ + "method": "POST", + "sendBody": true, // ← Now body appears! + "body": {...} +} +``` + +### Problem 2: "Field disappears when I change operation" + +**Scenario**: +```javascript +// Working configuration +{ + "resource": "message", + "operation": "post", + "channel": "#general", + "text": "Hello" +} + +// Change operation +{ + "resource": "message", + "operation": "update", // Changed + "channel": "#general", // Still here + "text": "Updated" // Still here + // Missing: messageId (required for update!) +} +``` + +**Validation error**: "messageId is required" + +**Why**: Different operation = different required fields + +**Solution**: +```javascript +// Check essentials for new operation +get_node_essentials({ + nodeType: "nodes-base.slack" +}); + +// Configure for update operation +{ + "resource": "message", + "operation": "update", + "messageId": "1234567890", // Required for update + "text": "Updated", + "channel": "#general" // Optional for update +} +``` + +### Problem 3: "Validation passes but field doesn't save" + +**Scenario**: Field hidden by dependencies after validation + +**Example**: +```javascript +// Configure +{ + "method": "GET", + "sendBody": true, // ❌ GET doesn't support body + "body": {...} // This will be stripped +} + +// After save +{ + "method": "GET" + // body removed because method=GET hides it +} +``` + +**Solution**: Respect dependencies from the start + +```javascript +// Correct approach +get_property_dependencies({ + nodeType: "nodes-base.httpRequest" +}); + +// See that body only shows for POST/PUT/PATCH/DELETE +// Use correct method +{ + "method": "POST", + "sendBody": true, + "body": {...} +} +``` + +--- + +## Advanced Patterns + +### Pattern 1: Conditional Required with Fallback + +**Example**: Channel can be string OR expression + +```javascript +// Option 1: String +{ + "channel": "#general" +} + +// Option 2: Expression +{ + "channel": "={{$json.channelName}}" +} + +// Validation accepts both +``` + +### Pattern 2: Mutually Exclusive Fields + +**Example**: Use either ID or name, not both + +```javascript +// Use messageId +{ + "messageId": "1234567890" + // name not needed +} + +// OR use messageName +{ + "messageName": "thread-name" + // messageId not needed +} + +// Dependencies ensure only one is required +``` + +### Pattern 3: Progressive Complexity + +**Example**: Simple mode vs advanced mode + +```javascript +// Simple mode +{ + "mode": "simple", + "text": "{{$json.message}}" + // Advanced fields hidden +} + +// Advanced mode +{ + "mode": "advanced", + "attachments": [...], + "blocks": [...], + "metadata": {...} + // Simple field hidden, advanced fields shown +} +``` + +--- + +## Best Practices + +### ✅ Do + +1. **Check dependencies when stuck** + ```javascript + get_property_dependencies({nodeType: "..."}); + ``` + +2. **Configure parent properties first** + ```javascript + // First: method, resource, operation + // Then: dependent fields + ``` + +3. **Validate after changing operation** + ```javascript + // Operation changed → requirements changed + validate_node_operation({...}); + ``` + +4. **Read validation errors for dependency hints** + ``` + Error: "body required when sendBody=true" + → Hint: Set sendBody=true to enable body + ``` + +### ❌ Don't + +1. **Don't ignore dependency errors** + ```javascript + // Error: "body not visible" → Check displayOptions + ``` + +2. **Don't hardcode all possible fields** + ```javascript + // Bad: Adding fields that will be hidden + ``` + +3. **Don't assume operations are identical** + ```javascript + // Each operation has unique requirements + ``` + +--- + +## Summary + +**Key Concepts**: +- `displayOptions` control field visibility +- `show` = field appears when conditions match +- `hide` = field disappears when conditions match +- Multiple conditions = AND logic +- Multiple values = OR logic + +**Common Patterns**: +1. Boolean toggle (sendBody → body) +2. Resource/operation cascade (different operations → different fields) +3. Type-specific config (string vs boolean conditions) +4. Method-specific fields (GET vs POST) + +**Troubleshooting**: +- Field required but not visible → Check dependencies +- Field disappears after change → Operation changed requirements +- Field doesn't save → Hidden by dependencies + +**Tools**: +- `get_property_dependencies` - See dependency rules +- `get_node_essentials` - See operation requirements +- Validation errors - Hints about dependencies + +**Related Files**: +- **[SKILL.md](SKILL.md)** - Main configuration guide +- **[OPERATION_PATTERNS.md](OPERATION_PATTERNS.md)** - Common patterns by node type diff --git a/docs/skills/n8n-automation/node-configuration/OPERATION_PATTERNS.md b/docs/skills/n8n-automation/node-configuration/OPERATION_PATTERNS.md new file mode 100644 index 0000000..c647800 --- /dev/null +++ b/docs/skills/n8n-automation/node-configuration/OPERATION_PATTERNS.md @@ -0,0 +1,913 @@ +# Operation Patterns Guide + +Common node configuration patterns organized by node type and operation. + +--- + +## Overview + +**Purpose**: Quick reference for common node configurations + +**Coverage**: Top 20 most-used nodes from 525 available + +**Pattern format**: +- Minimal valid configuration +- Common options +- Real-world examples +- Gotchas and tips + +--- + +## HTTP & API Nodes + +### HTTP Request (nodes-base.httpRequest) + +Most versatile node for HTTP operations + +#### GET Request + +**Minimal**: +```javascript +{ + "method": "GET", + "url": "https://api.example.com/users", + "authentication": "none" +} +``` + +**With query parameters**: +```javascript +{ + "method": "GET", + "url": "https://api.example.com/users", + "authentication": "none", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "limit", + "value": "100" + }, + { + "name": "offset", + "value": "={{$json.offset}}" + } + ] + } +} +``` + +**With authentication**: +```javascript +{ + "method": "GET", + "url": "https://api.example.com/users", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "httpHeaderAuth" +} +``` + +#### POST with JSON + +**Minimal**: +```javascript +{ + "method": "POST", + "url": "https://api.example.com/users", + "authentication": "none", + "sendBody": true, + "body": { + "contentType": "json", + "content": { + "name": "John Doe", + "email": "john@example.com" + } + } +} +``` + +**With expressions**: +```javascript +{ + "method": "POST", + "url": "https://api.example.com/users", + "authentication": "none", + "sendBody": true, + "body": { + "contentType": "json", + "content": { + "name": "={{$json.name}}", + "email": "={{$json.email}}", + "metadata": { + "source": "n8n", + "timestamp": "={{$now.toISO()}}" + } + } + } +} +``` + +**Gotcha**: Remember `sendBody: true` for POST/PUT/PATCH! + +#### PUT/PATCH Request + +**Pattern**: Same as POST, but method changes +```javascript +{ + "method": "PUT", // or "PATCH" + "url": "https://api.example.com/users/123", + "authentication": "none", + "sendBody": true, + "body": { + "contentType": "json", + "content": { + "name": "Updated Name" + } + } +} +``` + +#### DELETE Request + +**Minimal** (no body): +```javascript +{ + "method": "DELETE", + "url": "https://api.example.com/users/123", + "authentication": "none" +} +``` + +**With body** (some APIs allow): +```javascript +{ + "method": "DELETE", + "url": "https://api.example.com/users", + "authentication": "none", + "sendBody": true, + "body": { + "contentType": "json", + "content": { + "ids": ["123", "456"] + } + } +} +``` + +--- + +### Webhook (nodes-base.webhook) + +Most common trigger - 813 searches! + +#### Basic Webhook + +**Minimal**: +```javascript +{ + "path": "my-webhook", + "httpMethod": "POST", + "responseMode": "onReceived" +} +``` + +**Gotcha**: Webhook data is under `$json.body`, not `$json`! + +```javascript +// ❌ Wrong +{ + "text": "={{$json.email}}" +} + +// ✅ Correct +{ + "text": "={{$json.body.email}}" +} +``` + +#### Webhook with Authentication + +**Header auth**: +```javascript +{ + "path": "secure-webhook", + "httpMethod": "POST", + "responseMode": "onReceived", + "authentication": "headerAuth", + "options": { + "responseCode": 200, + "responseData": "{\n \"success\": true\n}" + } +} +``` + +#### Webhook Returning Data + +**Custom response**: +```javascript +{ + "path": "my-webhook", + "httpMethod": "POST", + "responseMode": "lastNode", // Return data from last node + "options": { + "responseCode": 201, + "responseHeaders": { + "entries": [ + { + "name": "Content-Type", + "value": "application/json" + } + ] + } + } +} +``` + +--- + +## Communication Nodes + +### Slack (nodes-base.slack) + +Popular choice for AI agent workflows + +#### Post Message + +**Minimal**: +```javascript +{ + "resource": "message", + "operation": "post", + "channel": "#general", + "text": "Hello from n8n!" +} +``` + +**With dynamic content**: +```javascript +{ + "resource": "message", + "operation": "post", + "channel": "={{$json.channel}}", + "text": "New user: {{$json.name}} ({{$json.email}})" +} +``` + +**With attachments**: +```javascript +{ + "resource": "message", + "operation": "post", + "channel": "#alerts", + "text": "Error Alert", + "attachments": [ + { + "color": "#ff0000", + "fields": [ + { + "title": "Error Type", + "value": "={{$json.errorType}}" + }, + { + "title": "Timestamp", + "value": "={{$now.toLocaleString()}}" + } + ] + } + ] +} +``` + +**Gotcha**: Channel must start with `#` for public channels or be a channel ID! + +#### Update Message + +**Minimal**: +```javascript +{ + "resource": "message", + "operation": "update", + "messageId": "1234567890.123456", // From previous message post + "text": "Updated message content" +} +``` + +**Note**: `messageId` required, `channel` optional (can be inferred) + +#### Create Channel + +**Minimal**: +```javascript +{ + "resource": "channel", + "operation": "create", + "name": "new-project-channel", // Lowercase, no spaces + "isPrivate": false +} +``` + +**Gotcha**: Channel name must be lowercase, no spaces, 1-80 chars! + +--- + +### Gmail (nodes-base.gmail) + +Popular for email automation + +#### Send Email + +**Minimal**: +```javascript +{ + "resource": "message", + "operation": "send", + "to": "user@example.com", + "subject": "Hello from n8n", + "message": "This is the email body" +} +``` + +**With dynamic content**: +```javascript +{ + "resource": "message", + "operation": "send", + "to": "={{$json.email}}", + "subject": "Order Confirmation #{{$json.orderId}}", + "message": "Dear {{$json.name}},\n\nYour order has been confirmed.\n\nThank you!", + "options": { + "ccList": "admin@example.com", + "replyTo": "support@example.com" + } +} +``` + +#### Get Email + +**Minimal**: +```javascript +{ + "resource": "message", + "operation": "getAll", + "returnAll": false, + "limit": 10 +} +``` + +**With filters**: +```javascript +{ + "resource": "message", + "operation": "getAll", + "returnAll": false, + "limit": 50, + "filters": { + "q": "is:unread from:important@example.com", + "labelIds": ["INBOX"] + } +} +``` + +--- + +## Database Nodes + +### Postgres (nodes-base.postgres) + +Database operations - 456 templates + +#### Execute Query + +**Minimal** (SELECT): +```javascript +{ + "operation": "executeQuery", + "query": "SELECT * FROM users WHERE active = true LIMIT 100" +} +``` + +**With parameters** (SQL injection prevention): +```javascript +{ + "operation": "executeQuery", + "query": "SELECT * FROM users WHERE email = $1 AND active = $2", + "additionalFields": { + "mode": "list", + "queryParameters": "user@example.com,true" + } +} +``` + +**Gotcha**: ALWAYS use parameterized queries for user input! + +```javascript +// ❌ BAD - SQL injection risk! +{ + "query": "SELECT * FROM users WHERE email = '{{$json.email}}'" +} + +// ✅ GOOD - Parameterized +{ + "query": "SELECT * FROM users WHERE email = $1", + "additionalFields": { + "mode": "list", + "queryParameters": "={{$json.email}}" + } +} +``` + +#### Insert + +**Minimal**: +```javascript +{ + "operation": "insert", + "table": "users", + "columns": "name,email,created_at", + "additionalFields": { + "mode": "list", + "queryParameters": "John Doe,john@example.com,NOW()" + } +} +``` + +**With expressions**: +```javascript +{ + "operation": "insert", + "table": "users", + "columns": "name,email,metadata", + "additionalFields": { + "mode": "list", + "queryParameters": "={{$json.name}},={{$json.email}},{{JSON.stringify($json)}}" + } +} +``` + +#### Update + +**Minimal**: +```javascript +{ + "operation": "update", + "table": "users", + "updateKey": "id", + "columns": "name,email", + "additionalFields": { + "mode": "list", + "queryParameters": "={{$json.id}},Updated Name,newemail@example.com" + } +} +``` + +--- + +## Data Transformation Nodes + +### Set (nodes-base.set) + +Most used transformation - 68% of workflows! + +#### Set Fixed Values + +**Minimal**: +```javascript +{ + "mode": "manual", + "duplicateItem": false, + "assignments": { + "assignments": [ + { + "name": "status", + "value": "active", + "type": "string" + }, + { + "name": "count", + "value": 100, + "type": "number" + } + ] + } +} +``` + +#### Set from Input Data + +**Mapping data**: +```javascript +{ + "mode": "manual", + "duplicateItem": false, + "assignments": { + "assignments": [ + { + "name": "fullName", + "value": "={{$json.firstName}} {{$json.lastName}}", + "type": "string" + }, + { + "name": "email", + "value": "={{$json.email.toLowerCase()}}", + "type": "string" + }, + { + "name": "timestamp", + "value": "={{$now.toISO()}}", + "type": "string" + } + ] + } +} +``` + +**Gotcha**: Use correct `type` for each field! + +```javascript +// ❌ Wrong type +{ + "name": "age", + "value": "25", // String + "type": "string" // Will be string "25" +} + +// ✅ Correct type +{ + "name": "age", + "value": 25, // Number + "type": "number" // Will be number 25 +} +``` + +--- + +### Code (nodes-base.code) + +JavaScript execution - 42% of workflows + +#### Simple Transformation + +**Minimal**: +```javascript +{ + "mode": "runOnceForAllItems", + "jsCode": "return $input.all().map(item => ({\n json: {\n name: item.json.name.toUpperCase(),\n email: item.json.email\n }\n}));" +} +``` + +**Per-item processing**: +```javascript +{ + "mode": "runOnceForEachItem", + "jsCode": "// Process each item\nconst data = $input.item.json;\n\nreturn {\n json: {\n fullName: `${data.firstName} ${data.lastName}`,\n email: data.email.toLowerCase(),\n timestamp: new Date().toISOString()\n }\n};" +} +``` + +**Gotcha**: In Code nodes, use `$input.item.json` or `$input.all()`, NOT `{{...}}`! + +```javascript +// ❌ Wrong - expressions don't work in Code nodes +{ + "jsCode": "const name = '={{$json.name}}';" +} + +// ✅ Correct - direct access +{ + "jsCode": "const name = $input.item.json.name;" +} +``` + +--- + +## Conditional Nodes + +### IF (nodes-base.if) + +Conditional logic - 38% of workflows + +#### String Comparison + +**Equals** (binary): +```javascript +{ + "conditions": { + "string": [ + { + "value1": "={{$json.status}}", + "operation": "equals", + "value2": "active" + } + ] + } +} +``` + +**Contains** (binary): +```javascript +{ + "conditions": { + "string": [ + { + "value1": "={{$json.email}}", + "operation": "contains", + "value2": "@example.com" + } + ] + } +} +``` + +**isEmpty** (unary): +```javascript +{ + "conditions": { + "string": [ + { + "value1": "={{$json.email}}", + "operation": "isEmpty" + // No value2 - unary operator + // singleValue: true added by auto-sanitization + } + ] + } +} +``` + +**Gotcha**: Unary operators (isEmpty, isNotEmpty) don't need value2! + +#### Number Comparison + +**Greater than**: +```javascript +{ + "conditions": { + "number": [ + { + "value1": "={{$json.age}}", + "operation": "larger", + "value2": 18 + } + ] + } +} +``` + +#### Boolean Comparison + +**Is true**: +```javascript +{ + "conditions": { + "boolean": [ + { + "value1": "={{$json.isActive}}", + "operation": "true" + // Unary - no value2 + } + ] + } +} +``` + +#### Multiple Conditions (AND) + +**All must match**: +```javascript +{ + "conditions": { + "string": [ + { + "value1": "={{$json.status}}", + "operation": "equals", + "value2": "active" + } + ], + "number": [ + { + "value1": "={{$json.age}}", + "operation": "larger", + "value2": 18 + } + ] + }, + "combineOperation": "all" // AND logic +} +``` + +#### Multiple Conditions (OR) + +**Any can match**: +```javascript +{ + "conditions": { + "string": [ + { + "value1": "={{$json.status}}", + "operation": "equals", + "value2": "active" + }, + { + "value1": "={{$json.status}}", + "operation": "equals", + "value2": "pending" + } + ] + }, + "combineOperation": "any" // OR logic +} +``` + +--- + +### Switch (nodes-base.switch) + +Multi-way routing - 18% of workflows + +#### Basic Switch + +**Minimal**: +```javascript +{ + "mode": "rules", + "rules": { + "rules": [ + { + "conditions": { + "string": [ + { + "value1": "={{$json.status}}", + "operation": "equals", + "value2": "active" + } + ] + } + }, + { + "conditions": { + "string": [ + { + "value1": "={{$json.status}}", + "operation": "equals", + "value2": "pending" + } + ] + } + } + ] + }, + "fallbackOutput": "extra" // Catch-all for non-matching +} +``` + +**Gotcha**: Number of rules must match number of outputs! + +--- + +## AI Nodes + +### OpenAI (nodes-langchain.openAi) + +AI operations - 234 templates + +#### Chat Completion + +**Minimal**: +```javascript +{ + "resource": "chat", + "operation": "complete", + "messages": { + "values": [ + { + "role": "user", + "content": "={{$json.prompt}}" + } + ] + } +} +``` + +**With system prompt**: +```javascript +{ + "resource": "chat", + "operation": "complete", + "messages": { + "values": [ + { + "role": "system", + "content": "You are a helpful assistant specialized in customer support." + }, + { + "role": "user", + "content": "={{$json.userMessage}}" + } + ] + }, + "options": { + "temperature": 0.7, + "maxTokens": 500 + } +} +``` + +--- + +## Schedule Nodes + +### Schedule Trigger (nodes-base.scheduleTrigger) + +Time-based workflows - 28% have schedule triggers + +#### Daily at Specific Time + +**Minimal**: +```javascript +{ + "rule": { + "interval": [ + { + "field": "hours", + "hoursInterval": 24 + } + ], + "hour": 9, + "minute": 0, + "timezone": "America/New_York" + } +} +``` + +**Gotcha**: Always set timezone explicitly! + +```javascript +// ❌ Bad - uses server timezone +{ + "rule": { + "interval": [...] + } +} + +// ✅ Good - explicit timezone +{ + "rule": { + "interval": [...], + "timezone": "America/New_York" + } +} +``` + +#### Every N Minutes + +**Minimal**: +```javascript +{ + "rule": { + "interval": [ + { + "field": "minutes", + "minutesInterval": 15 + } + ] + } +} +``` + +#### Cron Expression + +**Advanced scheduling**: +```javascript +{ + "mode": "cron", + "cronExpression": "0 */2 * * *", // Every 2 hours + "timezone": "America/New_York" +} +``` + +--- + +## Summary + +**Key Patterns by Category**: + +| Category | Most Common | Key Gotcha | +|---|---|---| +| HTTP/API | GET, POST JSON | Remember sendBody: true | +| Webhooks | POST receiver | Data under $json.body | +| Communication | Slack post | Channel format (#name) | +| Database | SELECT with params | Use parameterized queries | +| Transform | Set assignments | Correct type per field | +| Conditional | IF string equals | Unary vs binary operators | +| AI | OpenAI chat | System + user messages | +| Schedule | Daily at time | Set timezone explicitly | + +**Configuration Approach**: +1. Use patterns as starting point +2. Adapt to your use case +3. Validate configuration +4. Iterate based on errors +5. Deploy when valid + +**Related Files**: +- **[SKILL.md](SKILL.md)** - Configuration workflow and philosophy +- **[DEPENDENCIES.md](DEPENDENCIES.md)** - Property dependency rules diff --git a/docs/skills/n8n-automation/node-configuration/README.md b/docs/skills/n8n-automation/node-configuration/README.md new file mode 100644 index 0000000..b3efb5e --- /dev/null +++ b/docs/skills/n8n-automation/node-configuration/README.md @@ -0,0 +1,364 @@ +# n8n Node Configuration + +Expert guidance for operation-aware node configuration with property dependencies. + +## Overview + +**Skill Name**: n8n Node Configuration +**Priority**: Medium +**Purpose**: Teach operation-aware configuration with progressive discovery and dependency awareness + +## The Problem This Solves + +Node configuration patterns: + +- get_node_essentials is the primary discovery tool (18s avg from search → essentials) +- 91.7% success rate with essentials-based configuration +- 56 seconds average between configuration edits + +**Key insight**: Most configurations only need essentials, not full schema! + +## What This Skill Teaches + +### Core Concepts + +1. **Operation-Aware Configuration** + - Resource + operation determine required fields + - Different operations = different requirements + - Always check requirements when changing operation + +2. **Property Dependencies** + - Fields appear/disappear based on other field values + - displayOptions control visibility + - Conditional required fields + - Understanding dependency chains + +3. **Progressive Discovery** + - Start with get_node_essentials (91.7% success) + - Escalate to get_property_dependencies if needed + - Use get_node_info only when necessary + - Right tool for right job + +4. **Configuration Workflow** + - Identify → Discover → Configure → Validate → Iterate + - Average 2-3 validation cycles + - Read errors for dependency hints + - 56 seconds between edits average + +5. **Common Patterns** + - Resource/operation nodes (Slack, Sheets) + - HTTP-based nodes (HTTP Request, Webhook) + - Database nodes (Postgres, MySQL) + - Conditional logic nodes (IF, Switch) + +## File Structure + +``` +n8n-node-configuration/ +├── SKILL.md (692 lines) +│ Main configuration guide +│ - Configuration philosophy (progressive disclosure) +│ - Core concepts (operation-aware, dependencies) +│ - Configuration workflow (8-step process) +│ - get_node_essentials vs get_node_info +│ - Property dependencies deep dive +│ - Common node patterns (4 categories) +│ - Operation-specific examples +│ - Conditional requirements +│ - Anti-patterns and best practices +│ +├── DEPENDENCIES.md (671 lines) +│ Property dependencies reference +│ - displayOptions mechanism +│ - show vs hide rules +│ - Multiple conditions (AND logic) +│ - Multiple values (OR logic) +│ - 4 common dependency patterns +│ - Using get_property_dependencies +│ - Complex dependency examples +│ - Nested dependencies +│ - Auto-sanitization interaction +│ - Troubleshooting guide +│ - Advanced patterns +│ +├── OPERATION_PATTERNS.md (783 lines) +│ Common configurations by node type +│ - HTTP Request (GET/POST/PUT/DELETE) +│ - Webhook (basic/auth/response) +│ - Slack (post/update/create) +│ - Gmail (send/get) +│ - Postgres (query/insert/update) +│ - Set (values/mapping) +│ - Code (per-item/all-items) +│ - IF (string/number/boolean) +│ - Switch (rules/fallback) +│ - OpenAI (chat completion) +│ - Schedule (daily/interval/cron) +│ - Gotchas and tips for each +│ +└── README.md (this file) + Skill metadata and statistics +``` + +**Total**: ~2,146 lines across 4 files + 4 evaluations + +## Usage Statistics + +Configuration metrics: + +| Metric | Value | Insight | +|---|---|---| +| get_node_essentials | Primary tool | Most popular discovery pattern | +| Success rate (essentials) | 91.7% | Essentials sufficient for most | +| Avg time search→essentials | 18 seconds | Fast discovery workflow | +| Avg time between edits | 56 seconds | Iterative configuration | + +## Tool Usage Pattern + +**Most common discovery pattern**: +``` +search_nodes → get_node_essentials (18s average) +``` + +**Configuration cycle**: +``` +get_node_essentials → configure → validate → iterate (56s avg per edit) +``` + +## Key Insights + +### 1. Progressive Disclosure Works + +**91.7% success rate** with get_node_essentials proves most configurations don't need full schema. + +**Strategy**: +1. Start with essentials +2. Escalate to dependencies if stuck +3. Use full schema only when necessary + +### 2. Operations Determine Requirements + +**Same node, different operation = different requirements** + +Example: Slack message +- `operation="post"` → needs channel + text +- `operation="update"` → needs messageId + text (different!) + +### 3. Dependencies Control Visibility + +**Fields appear/disappear based on other values** + +Example: HTTP Request +- `method="GET"` → body hidden +- `method="POST"` + `sendBody=true` → body required + +### 4. Configuration is Iterative + +**Average 56 seconds between edits** shows configuration is iterative, not one-shot. + +**Normal workflow**: +1. Configure minimal +2. Validate → error +3. Add missing field +4. Validate → error +5. Adjust value +6. Validate → valid ✅ + +### 5. Common Gotchas Exist + +**Top 5 gotchas** from patterns: +1. Webhook data under `$json.body` (not `$json`) +2. POST needs `sendBody: true` +3. Slack channel format (`#name`) +4. SQL parameterized queries (injection prevention) +5. Timezone must be explicit (schedule nodes) + +## Usage Examples + +### Example 1: Basic Configuration Flow + +```javascript +// Step 1: Get essentials +const info = get_node_essentials({ + nodeType: "nodes-base.slack" +}); + +// Step 2: Configure for operation +{ + "resource": "message", + "operation": "post", + "channel": "#general", + "text": "Hello!" +} + +// Step 3: Validate +validate_node_operation({...}); +// ✅ Valid! +``` + +### Example 2: Handling Dependencies + +```javascript +// Step 1: Configure HTTP POST +{ + "method": "POST", + "url": "https://api.example.com/create" +} + +// Step 2: Validate → Error: "sendBody required" +// Step 3: Check dependencies +get_property_dependencies({ + nodeType: "nodes-base.httpRequest" +}); +// Shows: body visible when sendBody=true + +// Step 4: Fix +{ + "method": "POST", + "url": "https://api.example.com/create", + "sendBody": true, + "body": { + "contentType": "json", + "content": {...} + } +} +// ✅ Valid! +``` + +### Example 3: Operation Change + +```javascript +// Initial config (post operation) +{ + "resource": "message", + "operation": "post", + "channel": "#general", + "text": "Hello" +} + +// Change operation +{ + "resource": "message", + "operation": "update", // Changed! + // Need to check new requirements +} + +// Get essentials for update operation +get_node_essentials({nodeType: "nodes-base.slack"}); +// Shows: messageId required, channel optional + +// Correct config +{ + "resource": "message", + "operation": "update", + "messageId": "1234567890.123456", + "text": "Updated" +} +``` + +## When This Skill Activates + +**Trigger phrases**: +- "how to configure" +- "what fields are required" +- "property dependencies" +- "get_node_essentials vs get_node_info" +- "operation-specific" +- "field not visible" + +**Common scenarios**: +- Configuring new nodes +- Understanding required fields +- Field appears/disappears unexpectedly +- Choosing between discovery tools +- Switching operations +- Learning common patterns + +## Integration with Other Skills + +### Works With: +- **n8n MCP Tools Expert** - How to call discovery tools correctly +- **n8n Validation Expert** - Interpret missing_required errors +- **n8n Expression Syntax** - Configure expression fields +- **n8n Workflow Patterns** - Apply patterns with proper node config + +### Complementary: +- Use MCP Tools Expert to learn tool selection +- Use Validation Expert to fix configuration errors +- Use Expression Syntax for dynamic field values +- Use Workflow Patterns to understand node relationships + +## Testing + +**Evaluations**: 4 test scenarios + +1. **eval-001-property-dependencies.json** + - Tests understanding of displayOptions + - Guides to get_property_dependencies + - Explains conditional requirements + +2. **eval-002-operation-specific-config.json** + - Tests operation-aware configuration + - Identifies resource + operation pattern + - References OPERATION_PATTERNS.md + +3. **eval-003-conditional-fields.json** + - Tests unary vs binary operators + - Explains singleValue dependency + - Mentions auto-sanitization + +4. **eval-004-essentials-vs-info.json** + - Tests tool selection knowledge + - Explains progressive disclosure + - Provides success rate statistics + +## Success Metrics + +**Before this skill**: +- Using get_node_info for everything (slow, overwhelming) +- Not understanding property dependencies +- Confused when fields appear/disappear +- Not aware of operation-specific requirements +- Trial and error configuration + +**After this skill**: +- Start with get_node_essentials (91.7% success) +- Understand displayOptions mechanism +- Predict field visibility based on dependencies +- Check requirements when changing operations +- Systematic configuration approach +- Know common patterns by node type + +## Coverage + +**Node types covered**: Top 20 most-used nodes + +| Category | Nodes | Coverage | +|---|---|---| +| HTTP/API | HTTP Request, Webhook | Complete | +| Communication | Slack, Gmail | Common operations | +| Database | Postgres, MySQL | CRUD operations | +| Transform | Set, Code | All modes | +| Conditional | IF, Switch | All operator types | +| AI | OpenAI | Chat completion | +| Schedule | Schedule Trigger | All modes | + +## Related Documentation + +- **n8n-mcp MCP Server**: Provides discovery tools +- **n8n Node API**: get_node_essentials, get_property_dependencies, get_node_info +- **n8n Schema**: displayOptions mechanism, property definitions + +## Version History + +- **v1.0** (2025-10-20): Initial implementation + - SKILL.md with configuration workflow + - DEPENDENCIES.md with displayOptions deep dive + - OPERATION_PATTERNS.md with 20+ node patterns + - 4 evaluation scenarios + +## Author + +Conceived by Romuald Członkowski - [www.aiadvisors.pl/en](https://www.aiadvisors.pl/en) + +Part of the n8n-skills meta-skill collection. diff --git a/docs/skills/n8n-automation/node-configuration/SKILL.md b/docs/skills/n8n-automation/node-configuration/SKILL.md new file mode 100644 index 0000000..9bbfdfe --- /dev/null +++ b/docs/skills/n8n-automation/node-configuration/SKILL.md @@ -0,0 +1,774 @@ +--- +name: n8n-node-configuration +description: Operation-aware node configuration guidance. Use when configuring nodes, understanding property dependencies, determining required fields, choosing between get_node_essentials and get_node_info, or learning common configuration patterns by node type. +--- + +# n8n Node Configuration + +Expert guidance for operation-aware node configuration with property dependencies. + +--- + +## Configuration Philosophy + +**Progressive disclosure**: Start minimal, add complexity as needed + +Configuration best practices: +- get_node_essentials is the most used discovery pattern +- 56 seconds average between configuration edits +- 91.7% success rate with essentials-based configuration + +**Key insight**: Most configurations need only essentials, not full schema! + +--- + +## Core Concepts + +### 1. Operation-Aware Configuration + +**Not all fields are always required** - it depends on operation! + +**Example**: Slack node +```javascript +// For operation='post' +{ + "resource": "message", + "operation": "post", + "channel": "#general", // Required for post + "text": "Hello!" // Required for post +} + +// For operation='update' +{ + "resource": "message", + "operation": "update", + "messageId": "123", // Required for update (different!) + "text": "Updated!" // Required for update + // channel NOT required for update +} +``` + +**Key**: Resource + operation determine which fields are required! + +### 2. Property Dependencies + +**Fields appear/disappear based on other field values** + +**Example**: HTTP Request node +```javascript +// When method='GET' +{ + "method": "GET", + "url": "https://api.example.com" + // sendBody not shown (GET doesn't have body) +} + +// When method='POST' +{ + "method": "POST", + "url": "https://api.example.com", + "sendBody": true, // Now visible! + "body": { // Required when sendBody=true + "contentType": "json", + "content": {...} + } +} +``` + +**Mechanism**: displayOptions control field visibility + +### 3. Progressive Discovery + +**Use the right tool for the right job**: + +1. **get_node_essentials** (91.7% success rate) + - Quick overview + - Required fields + - Common options + - **Use first** - covers 90% of needs + +2. **get_property_dependencies** (for complex nodes) + - Shows what fields depend on others + - Reveals conditional requirements + - Use when essentials isn't enough + +3. **get_node_info** (full schema) + - Complete documentation + - All possible fields + - Use when essentials + dependencies insufficient + +--- + +## Configuration Workflow + +### Standard Process + +``` +1. Identify node type and operation + ↓ +2. Use get_node_essentials + ↓ +3. Configure required fields + ↓ +4. Validate configuration + ↓ +5. If dependencies unclear → get_property_dependencies + ↓ +6. Add optional fields as needed + ↓ +7. Validate again + ↓ +8. Deploy +``` + +### Example: Configuring HTTP Request + +**Step 1**: Identify what you need +```javascript +// Goal: POST JSON to API +``` + +**Step 2**: Get essentials +```javascript +const info = get_node_essentials({ + nodeType: "nodes-base.httpRequest" +}); + +// Returns: method, url, sendBody, body, authentication required/optional +``` + +**Step 3**: Minimal config +```javascript +{ + "method": "POST", + "url": "https://api.example.com/create", + "authentication": "none" +} +``` + +**Step 4**: Validate +```javascript +validate_node_operation({ + nodeType: "nodes-base.httpRequest", + config, + profile: "runtime" +}); +// → Error: "sendBody required for POST" +``` + +**Step 5**: Add required field +```javascript +{ + "method": "POST", + "url": "https://api.example.com/create", + "authentication": "none", + "sendBody": true +} +``` + +**Step 6**: Validate again +```javascript +validate_node_operation({...}); +// → Error: "body required when sendBody=true" +``` + +**Step 7**: Complete configuration +```javascript +{ + "method": "POST", + "url": "https://api.example.com/create", + "authentication": "none", + "sendBody": true, + "body": { + "contentType": "json", + "content": { + "name": "={{$json.name}}", + "email": "={{$json.email}}" + } + } +} +``` + +**Step 8**: Final validation +```javascript +validate_node_operation({...}); +// → Valid! ✅ +``` + +--- + +## get_node_essentials vs get_node_info + +### Use get_node_essentials When: + +**✅ Starting configuration** (91.7% success rate) +```javascript +get_node_essentials({ + nodeType: "nodes-base.slack" +}); +``` + +**Returns**: +- Required fields +- Common options +- Basic examples +- Operation list + +**Fast**: ~18 seconds average (from search → essentials) + +### Use get_node_info When: + +**✅ Essentials insufficient** +```javascript +get_node_info({ + nodeType: "nodes-base.slack" +}); +``` + +**Returns**: +- Full schema +- All properties +- Complete documentation +- Advanced options + +**Slower**: More data to process + +### Decision Tree + +``` +┌─────────────────────────────────┐ +│ Starting new node config? │ +├─────────────────────────────────┤ +│ YES → get_node_essentials │ +└─────────────────────────────────┘ + ↓ +┌─────────────────────────────────┐ +│ Essentials has what you need? │ +├─────────────────────────────────┤ +│ YES → Configure with essentials │ +│ NO → Continue │ +└─────────────────────────────────┘ + ↓ +┌─────────────────────────────────┐ +│ Need dependency info? │ +├─────────────────────────────────┤ +│ YES → get_property_dependencies │ +│ NO → Continue │ +└─────────────────────────────────┘ + ↓ +┌─────────────────────────────────┐ +│ Still need more details? │ +├─────────────────────────────────┤ +│ YES → get_node_info │ +└─────────────────────────────────┘ +``` + +--- + +## Property Dependencies Deep Dive + +### displayOptions Mechanism + +**Fields have visibility rules**: + +```javascript +{ + "name": "body", + "displayOptions": { + "show": { + "sendBody": [true], + "method": ["POST", "PUT", "PATCH"] + } + } +} +``` + +**Translation**: "body" field shows when: +- sendBody = true AND +- method = POST, PUT, or PATCH + +### Common Dependency Patterns + +#### Pattern 1: Boolean Toggle + +**Example**: HTTP Request sendBody +```javascript +// sendBody controls body visibility +{ + "sendBody": true // → body field appears +} +``` + +#### Pattern 2: Operation Switch + +**Example**: Slack resource/operation +```javascript +// Different operations → different fields +{ + "resource": "message", + "operation": "post" + // → Shows: channel, text, attachments, etc. +} + +{ + "resource": "message", + "operation": "update" + // → Shows: messageId, text (different fields!) +} +``` + +#### Pattern 3: Type Selection + +**Example**: IF node conditions +```javascript +{ + "type": "string", + "operation": "contains" + // → Shows: value1, value2 +} + +{ + "type": "boolean", + "operation": "equals" + // → Shows: value1, value2, different operators +} +``` + +### Using get_property_dependencies + +**Example**: +```javascript +const deps = get_property_dependencies({ + nodeType: "nodes-base.httpRequest" +}); + +// Returns dependency tree +{ + "dependencies": { + "body": { + "shows_when": { + "sendBody": [true], + "method": ["POST", "PUT", "PATCH", "DELETE"] + } + }, + "queryParameters": { + "shows_when": { + "sendQuery": [true] + } + } + } +} +``` + +**Use this when**: Validation fails and you don't understand why field is missing/required + +--- + +## Common Node Patterns + +### Pattern 1: Resource/Operation Nodes + +**Examples**: Slack, Google Sheets, Airtable + +**Structure**: +```javascript +{ + "resource": "", // What type of thing + "operation": "", // What to do with it + // ... operation-specific fields +} +``` + +**How to configure**: +1. Choose resource +2. Choose operation +3. Use get_node_essentials to see operation-specific requirements +4. Configure required fields + +### Pattern 2: HTTP-Based Nodes + +**Examples**: HTTP Request, Webhook + +**Structure**: +```javascript +{ + "method": "", + "url": "", + "authentication": "", + // ... method-specific fields +} +``` + +**Dependencies**: +- POST/PUT/PATCH → sendBody available +- sendBody=true → body required +- authentication != "none" → credentials required + +### Pattern 3: Database Nodes + +**Examples**: Postgres, MySQL, MongoDB + +**Structure**: +```javascript +{ + "operation": "", + // ... operation-specific fields +} +``` + +**Dependencies**: +- operation="executeQuery" → query required +- operation="insert" → table + values required +- operation="update" → table + values + where required + +### Pattern 4: Conditional Logic Nodes + +**Examples**: IF, Switch, Merge + +**Structure**: +```javascript +{ + "conditions": { + "": [ + { + "operation": "", + "value1": "...", + "value2": "..." // Only for binary operators + } + ] + } +} +``` + +**Dependencies**: +- Binary operators (equals, contains, etc.) → value1 + value2 +- Unary operators (isEmpty, isNotEmpty) → value1 only + singleValue: true + +--- + +## Operation-Specific Configuration + +### Slack Node Examples + +#### Post Message +```javascript +{ + "resource": "message", + "operation": "post", + "channel": "#general", // Required + "text": "Hello!", // Required + "attachments": [], // Optional + "blocks": [] // Optional +} +``` + +#### Update Message +```javascript +{ + "resource": "message", + "operation": "update", + "messageId": "1234567890", // Required (different from post!) + "text": "Updated!", // Required + "channel": "#general" // Optional (can be inferred) +} +``` + +#### Create Channel +```javascript +{ + "resource": "channel", + "operation": "create", + "name": "new-channel", // Required + "isPrivate": false // Optional + // Note: text NOT required for this operation +} +``` + +### HTTP Request Node Examples + +#### GET Request +```javascript +{ + "method": "GET", + "url": "https://api.example.com/users", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "httpHeaderAuth", + "sendQuery": true, // Optional + "queryParameters": { // Shows when sendQuery=true + "parameters": [ + { + "name": "limit", + "value": "100" + } + ] + } +} +``` + +#### POST with JSON +```javascript +{ + "method": "POST", + "url": "https://api.example.com/users", + "authentication": "none", + "sendBody": true, // Required for POST + "body": { // Required when sendBody=true + "contentType": "json", + "content": { + "name": "John Doe", + "email": "john@example.com" + } + } +} +``` + +### IF Node Examples + +#### String Comparison (Binary) +```javascript +{ + "conditions": { + "string": [ + { + "value1": "={{$json.status}}", + "operation": "equals", + "value2": "active" // Binary: needs value2 + } + ] + } +} +``` + +#### Empty Check (Unary) +```javascript +{ + "conditions": { + "string": [ + { + "value1": "={{$json.email}}", + "operation": "isEmpty", + // No value2 - unary operator + "singleValue": true // Auto-added by sanitization + } + ] + } +} +``` + +--- + +## Handling Conditional Requirements + +### Example: HTTP Request Body + +**Scenario**: body field required, but only sometimes + +**Rule**: +``` +body is required when: + - sendBody = true AND + - method IN (POST, PUT, PATCH, DELETE) +``` + +**How to discover**: +```javascript +// Option 1: Read validation error +validate_node_operation({...}); +// Error: "body required when sendBody=true" + +// Option 2: Check dependencies +get_property_dependencies({ + nodeType: "nodes-base.httpRequest" +}); +// Shows: body → shows_when: sendBody=[true], method=[POST,PUT,PATCH,DELETE] + +// Option 3: Try minimal config and iterate +// Start without body, validation will tell you if needed +``` + +### Example: IF Node singleValue + +**Scenario**: singleValue property appears for unary operators + +**Rule**: +``` +singleValue should be true when: + - operation IN (isEmpty, isNotEmpty, true, false) +``` + +**Good news**: Auto-sanitization fixes this! + +**Manual check**: +```javascript +get_property_dependencies({ + nodeType: "nodes-base.if" +}); +// Shows operator-specific dependencies +``` + +--- + +## Configuration Anti-Patterns + +### ❌ Don't: Over-configure Upfront + +**Bad**: +```javascript +// Adding every possible field +{ + "method": "GET", + "url": "...", + "sendQuery": false, + "sendHeaders": false, + "sendBody": false, + "timeout": 10000, + "ignoreResponseCode": false, + // ... 20 more optional fields +} +``` + +**Good**: +```javascript +// Start minimal +{ + "method": "GET", + "url": "...", + "authentication": "none" +} +// Add fields only when needed +``` + +### ❌ Don't: Skip Validation + +**Bad**: +```javascript +// Configure and deploy without validating +const config = {...}; +n8n_update_partial_workflow({...}); // YOLO +``` + +**Good**: +```javascript +// Validate before deploying +const config = {...}; +const result = validate_node_operation({...}); +if (result.valid) { + n8n_update_partial_workflow({...}); +} +``` + +### ❌ Don't: Ignore Operation Context + +**Bad**: +```javascript +// Same config for all Slack operations +{ + "resource": "message", + "operation": "post", + "channel": "#general", + "text": "..." +} + +// Then switching operation without updating config +{ + "resource": "message", + "operation": "update", // Changed + "channel": "#general", // Wrong field for update! + "text": "..." +} +``` + +**Good**: +```javascript +// Check requirements when changing operation +get_node_essentials({ + nodeType: "nodes-base.slack" +}); +// See what update operation needs (messageId, not channel) +``` + +--- + +## Best Practices + +### ✅ Do + +1. **Start with get_node_essentials** + - 91.7% success rate + - Faster than get_node_info + - Sufficient for most needs + +2. **Validate iteratively** + - Configure → Validate → Fix → Repeat + - Average 2-3 iterations is normal + - Read validation errors carefully + +3. **Use property dependencies when stuck** + - If field seems missing, check dependencies + - Understand what controls field visibility + - get_property_dependencies reveals rules + +4. **Respect operation context** + - Different operations = different requirements + - Always check essentials when changing operation + - Don't assume configs are transferable + +5. **Trust auto-sanitization** + - Operator structure fixed automatically + - Don't manually add/remove singleValue + - IF/Switch metadata added on save + +### ❌ Don't + +1. **Jump to get_node_info immediately** + - Try essentials first + - Only escalate if needed + - Full schema is overwhelming + +2. **Configure blindly** + - Always validate before deploying + - Understand why fields are required + - Check dependencies for conditional fields + +3. **Copy configs without understanding** + - Different operations need different fields + - Validate after copying + - Adjust for new context + +4. **Manually fix auto-sanitization issues** + - Let auto-sanitization handle operator structure + - Focus on business logic + - Save and let system fix structure + +--- + +## Detailed References + +For comprehensive guides on specific topics: + +- **[DEPENDENCIES.md](DEPENDENCIES.md)** - Deep dive into property dependencies and displayOptions +- **[OPERATION_PATTERNS.md](OPERATION_PATTERNS.md)** - Common configuration patterns by node type + +--- + +## Summary + +**Configuration Strategy**: +1. Start with get_node_essentials (91.7% success) +2. Configure required fields for operation +3. Validate configuration +4. Check dependencies if stuck +5. Iterate until valid (avg 2-3 cycles) +6. Deploy with confidence + +**Key Principles**: +- **Operation-aware**: Different operations = different requirements +- **Progressive disclosure**: Start minimal, add as needed +- **Dependency-aware**: Understand field visibility rules +- **Validation-driven**: Let validation guide configuration + +**Related Skills**: +- **n8n MCP Tools Expert** - How to use discovery tools correctly +- **n8n Validation Expert** - Interpret validation errors +- **n8n Expression Syntax** - Configure expression fields +- **n8n Workflow Patterns** - Apply patterns with proper configuration