domains / agent-tooling / n8n-expression-syntax / n8n-expression-syntax
The first copy is the substantive procedure: it defines the expression format, core variables, the critical webhook body structure, common patterns, and a detailed transform gatekeeper (expression vs IIFE vs Code node). The second copy is a thin wrapper that defers to a separate detailed guide, adds a risk label, and includes working examples and limitations, but it omits the core syntax rules and the transform gatekeeper. Crowning either one would hide a real alternative: the first has the deep reference, the second has the wrapper structure and examples.
| Repository | Path |
|---|---|
| czlonkowski/n8n-skills | skills/n8n-expression-syntax/SKILL.md |
| sickn33/agentic-awesome-skills | plugins/agentic-awesome-skills-claude/skills/n8n-expression-syntax/SKILL.md |
| Role | Repository | Path |
|---|---|---|
| peer | czlonkowski/n8n-skills | skills/n8n-expression-syntax/SKILL.md |
| peer | sickn33/agentic-awesome-skills | plugins/agentic-awesome-skills-claude/skills/n8n-expression-syntax/SKILL.md |
| adapter | sickn33/agentic-awesome-skills | plugins/agentic-awesome-skills/skills/n8n-expression-syntax/SKILL.md |
| adapter | sickn33/agentic-awesome-skills | skills/n8n-expression-syntax/SKILL.md |
---
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, mapping data between nodes, or referencing webhook data in workflows.
risk: critical
source: community
---
# n8n Expression Syntax
Expert guide for writing correct n8n expressions in workflows.
## When to Use
- You need to write or debug n8n expressions using `{{ ... }}` syntax.
- The task involves `$json`, `$node`, webhook payloads, or expression-related workflow errors.
- You want syntax-correct dynamic values inside n8n nodes and parameters.
## 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}}
```
**Warning**: Some n8n instances have `N8N_BLOCK_ENV_ACCESS_IN_NODE` enabled, which blocks `$env` access entirely. If `$env` returns errors, use alternative approaches:
- Store values in credentials instead
- Use a Set node with manually entered values
- Pass values through webhook query parameters
## 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
```
## The transform gatekeeper
Before you add any node — or write any code — to transform data, walk this order and stop at the first that fits:
1. **Expression** (`{{ ... }}`) in the consuming field. Property access, method chains (`.map().filter().join()`), ternaries, string building, Luxon date math — if it's "take A, produce B" without intermediate variables, it's an expression. This covers most "just transform this" cases.
- **Querying nested JSON** (filter an array, pick fields, sum, sort, flatten) → `$jmespath()` inside that same expression, before you split into items or chain `.map().filter()`. One query replaces a Split Out → Filter → Aggregate chain.
2. **Arrow-function IIFE inside an Edit Fields field.** When the logic needs intermediate variables, branching, or comments but still operates on one item, wrap it in an immediately-invoked arrow function right in the field value:
```
={{ (() => {
const items = $json.line_items;
const subtotal = items.reduce((sum, it) => sum + it.price * it.qty, 0);
const tax = subtotal * 0.08;
return (subtotal + tax).toFixed(2);
})() }}
```
The outer `(...)` brackets the function; the trailing `()` invokes it. Drop either and n8n refuses to run. Inside you get the full expression scope (`$json`, `$('Node')`, `$now`, Luxon) plus `const`/`let`, `if`/`switch`, `try`/`catch`, and regex. No `require`, no `await`.
3. **Code node — last resort.** Only when you need multi-item aggregation across the whole dataset (`$input.all()`), an allowlisted library, or async work.
**Why the order matters.** It's not style — it's readability and performance. The Code node runs in a sandboxed VM with per-invocation setup overhead.
## Working Examples
### 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
```
## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.