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
This commit is contained in:
@@ -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/
|
||||
|
||||
106
docs/skills/n8n-automation/README.md
Normal file
106
docs/skills/n8n-automation/README.md
Normal file
@@ -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** 💙🔥❄️
|
||||
393
docs/skills/n8n-automation/expression-syntax/COMMON_MISTAKES.md
Normal file
393
docs/skills/n8n-automation/expression-syntax/COMMON_MISTAKES.md
Normal file
@@ -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.
|
||||
483
docs/skills/n8n-automation/expression-syntax/EXAMPLES.md
Normal file
483
docs/skills/n8n-automation/expression-syntax/EXAMPLES.md
Normal file
@@ -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.
|
||||
93
docs/skills/n8n-automation/expression-syntax/README.md
Normal file
93
docs/skills/n8n-automation/expression-syntax/README.md
Normal file
@@ -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)
|
||||
516
docs/skills/n8n-automation/expression-syntax/SKILL.md
Normal file
516
docs/skills/n8n-automation/expression-syntax/SKILL.md
Normal file
@@ -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.
|
||||
764
docs/skills/n8n-automation/javascript-code/BUILTIN_FUNCTIONS.md
Normal file
764
docs/skills/n8n-automation/javascript-code/BUILTIN_FUNCTIONS.md
Normal file
@@ -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
|
||||
1110
docs/skills/n8n-automation/javascript-code/COMMON_PATTERNS.md
Normal file
1110
docs/skills/n8n-automation/javascript-code/COMMON_PATTERNS.md
Normal file
File diff suppressed because it is too large
Load Diff
782
docs/skills/n8n-automation/javascript-code/DATA_ACCESS.md
Normal file
782
docs/skills/n8n-automation/javascript-code/DATA_ACCESS.md
Normal file
@@ -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
|
||||
763
docs/skills/n8n-automation/javascript-code/ERROR_PATTERNS.md
Normal file
763
docs/skills/n8n-automation/javascript-code/ERROR_PATTERNS.md
Normal file
@@ -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 = "
|
||||
<div class="container">
|
||||
<p>Hello</p>
|
||||
</div>
|
||||
";
|
||||
// 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 = `
|
||||
<div class="container">
|
||||
<p>Hello</p>
|
||||
</div>
|
||||
`;
|
||||
// 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 = `
|
||||
<div class="${className}">
|
||||
<h1>${title}</h1>
|
||||
<p>${content}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// ✅ 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
|
||||
350
docs/skills/n8n-automation/javascript-code/README.md
Normal file
350
docs/skills/n8n-automation/javascript-code/README.md
Normal file
@@ -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.
|
||||
699
docs/skills/n8n-automation/javascript-code/SKILL.md
Normal file
699
docs/skills/n8n-automation/javascript-code/SKILL.md
Normal file
@@ -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.
|
||||
800
docs/skills/n8n-automation/node-configuration/DEPENDENCIES.md
Normal file
800
docs/skills/n8n-automation/node-configuration/DEPENDENCIES.md
Normal file
@@ -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
|
||||
@@ -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
|
||||
364
docs/skills/n8n-automation/node-configuration/README.md
Normal file
364
docs/skills/n8n-automation/node-configuration/README.md
Normal file
@@ -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.
|
||||
774
docs/skills/n8n-automation/node-configuration/SKILL.md
Normal file
774
docs/skills/n8n-automation/node-configuration/SKILL.md
Normal file
@@ -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": "<entity>", // What type of thing
|
||||
"operation": "<action>", // 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": "<HTTP_METHOD>",
|
||||
"url": "<endpoint>",
|
||||
"authentication": "<type>",
|
||||
// ... 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": "<query|insert|update|delete>",
|
||||
// ... 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": {
|
||||
"<type>": [
|
||||
{
|
||||
"operation": "<operator>",
|
||||
"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
|
||||
Reference in New Issue
Block a user