Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/davila7-n8n-expression-syntax/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-12 | ✗→✓ | ▲ Improved | 115% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 124% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 194% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 243% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 159% | 0% |
Expert guide for writing correct n8n expressions in workflows.
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)Access data from the current node:
javascript{{$json.fieldName}} {{$json['field with spaces']}} {{$json.nested.property}} {{$json.items[0].name}}
Access data from any previous node:
javascript{{$node["Node Name"].json.fieldName}} {{$node["HTTP Request"].json.data}} {{$node["Webhook"].json.body.email}}
Important:
Access current date/time:
javascript{{$now}} {{$now.toFormat('yyyy-MM-dd')}} {{$now.toFormat('HH:mm:ss')}} {{$now.plus({days: 7})}}
Access environment variables:
javascript{{$env.API_KEY}} {{$env.DATABASE_URL}}
Most Common Mistake: Webhook data is NOT at the root!
javascript{ "headers": {...}, "params": {...}, "query": {...}, "body": { // ⚠️ USER DATA IS HERE! "name": "John", "email": "john@example.com", "message": "Hello" } }
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.
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']}}
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}}
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}}" }
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();
javascript// ❌ WRONG path: "{{$json.user_id}}/webhook" // ✅ CORRECT path: "user-webhook" // Static paths only
javascript// ❌ WRONG apiKey: "={{$env.API_KEY}}" // ✅ CORRECT Use n8n credential system, not expressions
Expressions must be wrapped in double curly braces.
javascript❌ $json.field ✅ {{$json.field}}
Field or node names with spaces require bracket notation:
javascript❌ {{$json.field name}} ✅ {{$json['field name']}} ❌ {{$node.HTTP Request.json}} ✅ {{$node["HTTP Request"].json}}
Node references are case-sensitive:
javascript❌ {{$node["http request"].json}} // lowercase ❌ {{$node["Http Request"].json}} // wrong case ✅ {{$node["HTTP Request"].json}} // exact match
Don't double-wrap expressions:
javascript❌ {{{$json.field}}} ✅ {{$json.field}}
For complete error catalog with fixes, see COMMON_MISTAKES.md
| 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 |
For real workflow examples, see EXAMPLES.md
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}}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}}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
javascript// First item {{$json.users[0].email}} // Array length {{$json.users.length}} // Last item {{$json.users[$json.users.length - 1].name}}
javascript// Dot notation (no spaces) {{$json.user.email}} // Bracket notation (with spaces or dynamic) {{$json['user data'].email}}
javascript// Concatenation (automatic) Hello {{$json.name}}! // String methods {{$json.email.toLowerCase()}} {{$json.name.toUpperCase()}}
javascript// Direct use {{$json.price}} // Math operations {{$json.price * 1.1}} // Add 10% {{$json.quantity + 5}}
javascript// Ternary operator {{$json.status === 'active' ? 'Active User' : 'Inactive User'}} // Default values {{$json.email || 'no-email@example.com'}}
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')}}
javascript// Substring {{$json.email.substring(0, 5)}} // Replace {{$json.message.replace('old', 'new')}} // Split and join {{$json.tags.split(',').join(', ')}}
"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
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()+, -, *, /, %.bodyEssential Rules:
.bodyMost Common Mistakes:
{{$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:
Need Help? Reference the n8n expression documentation or use n8n-mcp validation tools to check your expressions.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 19,053 | 7,391 | -61% | 1 | 1 | 0% | 1,899 | 4,246 | +124% | 0 | 0 | — |
case-02 | pass→pass | 6,965 | 3,862 | -45% | 1 | 1 | 0% | 1,223 | 3,590 | +194% | 0 | 0 | — |
case-03 | pass→pass | 5,869 | 4,458 | -24% | 1 | 1 | 0% | 1,099 | 3,775 | +243% | 0 | 0 | — |
case-04 | pass→pass | 8,568 | 6,149 | -28% | 1 | 1 | 0% | 1,567 | 4,060 | +159% | 0 | 0 | — |
case-05 | pass→pass | 4,635 | 3,605 | -22% | 1 | 1 | 0% | 812 | 3,609 | +344% | 0 | 0 | — |
case-06 | pass→pass | 3,933 | 3,541 | -10% | 1 | 1 | 0% | 663 | 3,219 | +386% | 0 | 0 | — |
case-07 | pass→pass | 7,082 | 3,590 | -49% | 1 | 1 | 0% | 1,269 | 3,411 | +169% | 0 | 0 | — |
case-08 | pass→pass | 6,423 | 3,055 | -52% | 1 | 1 | 0% | 1,298 | 3,472 | +167% | 0 | 0 | — |
case-09 | pass→pass | 5,106 | 4,235 | -17% | 1 | 1 | 0% | 1,005 | 3,735 | +272% | 0 | 0 | — |
case-10 | pass→pass | 3,912 | 3,293 | -16% | 1 | 1 | 0% | 807 | 3,532 | +338% | 0 | 0 | — |
case-11 | pass→pass | 9,226 | 4,822 | -48% | 1 | 1 | 0% | 1,706 | 3,855 | +126% | 0 | 0 | — |
case-12 | fail→pass | 9,912 | 6,657 | -33% | 1 | 1 | 0% | 1,930 | 4,149 | +115% | 0 | 0 | — |
case-13 | pass→pass | 4,004 | 3,408 | -15% | 1 | 1 | 0% | 809 | 3,540 | +338% | 0 | 0 | — |
case-14 | pass→pass | 3,000 | 2,128 | -29% | 1 | 1 | 0% | 490 | 3,310 | +576% | 0 | 0 | — |
case-15 | pass→pass | 2,500 | 3,186 | +27% | 1 | 1 | 0% | 456 | 3,400 | +646% | 0 | 0 | — |
case-16 | pass→pass | 3,616 | 3,338 | -8% | 1 | 1 | 0% | 823 | 3,619 | +340% | 0 | 0 | — |
case-17 | pass→pass | 7,424 | 3,122 | -58% | 1 | 1 | 0% | 1,462 | 3,475 | +138% | 0 | 0 | — |
case-18 | pass→pass | 6,902 | 5,616 | -19% | 1 | 1 | 0% | 1,502 | 4,112 | +174% | 0 | 0 | — |
case-19 | pass→pass | 2,869 | 3,625 | +26% | 1 | 1 | 0% | 621 | 3,507 | +465% | 0 | 0 | — |
case-20 | pass→pass | 8,751 | 8,965 | +2% | 1 | 1 | 0% | 1,893 | 4,875 | +158% | 0 | 0 | — |
case-21 | pass→pass | 3,606 | 2,862 | -21% | 1 | 1 | 0% | 688 | 3,466 | +404% | 0 | 0 | — |
case-22 | pass→pass | 2,770 | 2,819 | +2% | 1 | 1 | 0% | 517 | 3,301 | +538% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.