skills/n8n-expression-syntax/SKILL.md
Use when writing n8n expressions in node fields, using {{}} syntax with $json/$node/$now variables, or troubleshooting expressions showing as literal text. NEVER for Code node JavaScript (use n8n-code-javascript) or Python (use n8n-code-python).
npx skillsauth add sharkitect-solutions/sharkitect-claude-toolkit n8n-expression-syntaxInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
n8n has TWO field modes. Expressions behave differently in each.
Expressions go inline. No prefix needed.
Hello {{$json.body.name}}, your order is confirmed.
Adjacent text and expressions auto-concatenate. No JS template literals, no + operator.
The = prefix is REQUIRED before the opening braces:
{
"email": "={{$json.body.email}}",
"name": "={{$json.body.name}}",
"timestamp": "={{$now.toISO()}}"
}
Without =, the value {{$json.body.email}} is treated as a literal string.
With = in a text mode field, a literal = appears in the output.
ALL expressions require double curly braces: {{expression}}.
Without them, text is literal. $json.email in a field renders as the string "$json.email".
Access output data of the immediately preceding node:
{{$json.fieldName}}
{{$json.nested.property}}
{{$json['field with spaces']}}
{{$json.items[0].name}}
After a Webhook node, $json contains headers, params, query, AND body (see Webhook Gotcha below). After an HTTP Request node, $json contains the response body directly.
Access any previous node's output by exact name:
{{$node["HTTP Request"].json.data}}
{{$node["Webhook"].json.body.email}}
CRITICAL rules:
$node["HTTP Request"] works, $node["http request"] fails silently.json segment is MANDATORY: $node["Set"].json.value not $node["Set"].valueReturns a Luxon DateTime object. Use Luxon methods only:
{{$now.toFormat('yyyy-MM-dd')}}
{{$now.toFormat('HH:mm:ss')}}
{{$now.plus({days: 7}).toFormat('yyyy-MM-dd')}}
{{$now.minus({hours: 24}).toISO()}}
Standard JS new Date() methods do NOT work on $now.
DateTime.fromISO() is available for parsing date strings.
Access server-configured environment variables:
{{$env.API_KEY}}
{{$env.DATABASE_URL}}
Webhook node output wraps POST data under .body:
Webhook $json structure:
{
"headers": {...},
"params": {...},
"query": {...},
"body": { <-- POST data lives HERE
"name": "John",
"email": "[email protected]"
}
}
WRONG: {{$json.name}} -- returns undefined
RIGHT: {{$json.body.name}} -- returns "John"
This applies everywhere webhook data flows downstream:
{{$json.body.email}}{{$node["Webhook"].json.body.email}}https://api.example.com/users/{{$json.body.user_id}}Query parameters are at {{$json.query.paramName}}, not under body.
Code nodes execute JavaScript/Python directly. Expression braces are WRONG here.
WRONG (in Code node):
const email = '{{$json.email}}';
const name = '={{$json.body.name}}';
These produce literal strings "{{$json.email}}" and "={{$json.body.name}}".
RIGHT (in Code node):
const email = $json.email;
const name = $json.body.name;
// Or via Code node API:
const email = $input.item.json.email;
const allItems = $input.all();
JSON mode fields: = REQUIRED --> "={{$json.body.email}}"
Text mode fields: = FORBIDDEN --> {{$json.body.email}}
Wrong prefix direction:
= in JSON mode: expression treated as literal string= in text mode: literal "=" prepended to output ("[email protected]")| Field Type | Expressions? | Syntax | |-----------------------------|-------------|-------------------------------| | Text/message fields | YES | {{$json.field}} | | JSON/parameter fields | YES | ={{$json.field}} | | URL fields | YES | https://api.com/{{$json.id}} | | Header values | YES | Bearer {{$env.API_KEY}} | | Code node body | NO | Use direct JS: $json.field | | Webhook path field | NO | Static string only | | Credential fields | NO | Use n8n credential system | | Workflow/node name fields | NO | Static string only |
CAUSE: Missing {{}} braces, or missing = prefix in JSON mode.
FIX: Wrap in {{}}. In JSON mode, add = prefix: "={{$json.field}}".
CAUSE: Wrong data path. Parent object does not exist.
COMMON: Accessing $json.name instead of $json.body.name after webhook.
FIX: Verify actual data structure in the expression editor preview. Add missing path segments.
THREE possible causes:
$node["http request"] vs $node["HTTP Request"].json segment: $node["Set"].value vs $node["Set"].json.valueCAUSE: Using = prefix in a text mode field.
FIX: Remove the =. Text mode auto-evaluates {{}} without it.
CAUSE: Triple braces {{{$json.field}}} or empty braces {{}}.
FIX: Use exactly two braces: {{$json.field}}.
Every $node reference must have ALL of these:
{{$node["Exact Name"].json.fieldPath}}
^ ^ ^
| | +-- data path
| +-- MANDATORY (always .json for data, .binary for files)
+-- quotes required, case-sensitive, exact match
Missing any one of these causes silent failure (undefined, not an error).
Dot notation works for simple names: {{$json.email}}
Bracket notation REQUIRED when names contain:
{{$json['first name']}}{{$json['user-id']}}{{$json['123field']}}Node names always use brackets: {{$node["Any Name"].json.field}}
For arrays, use numeric brackets: {{$json.items[0].name}}
WRONG: {{$json.items.0.name}} -- dot notation with numbers fails.
In text mode fields, expressions auto-concatenate with surrounding text:
Hello {{$json.body.name}}, your order #{{$json.body.order_id}} is ready.
WRONG approaches (do not use):
`Hello ${$json.name}`"Hello " + $json.nameProduction workflows receive incomplete data. Fields may be undefined, null, or empty. Unguarded expressions crash the workflow or produce silent "undefined" strings in output.
When the parent object might not exist:
UNSAFE: {{$json.body.user.address.city}}
-- crashes if body, user, or address is undefined
SAFE: {{$json.body?.user?.address?.city}}
-- returns undefined (not an error) if any segment is missing
Use ?. at every uncertain nesting level. After a Webhook, $json.body usually exists but inner fields may not.
When a field might be missing and you need a fallback:
{{$json.body?.name ? $json.body.name : 'Unknown'}}
{{$json.body?.email ? $json.body.email : '[email protected]'}}
{{$json.status ? $json.status : 'pending'}}
The || operator returns the right side if the left is falsy (undefined, null, empty string, 0):
{{$json.body?.name || 'Unknown'}}
{{$json.body?.phone || 'Not provided'}}
WARNING: || treats 0, "", and false as falsy. If those are valid values, use ?? instead.
The ?? operator returns the right side ONLY if the left is null or undefined (not 0 or ""):
{{$json.count ?? 0}} -- preserves count=0 from API
{{$json.body?.discount ?? 0}} -- preserves 0% discount
{{$json.body?.notes ?? ''}} -- preserves empty string
Use ?? for numeric fields and boolean-adjacent values. Use || for string fields where empty = missing.
When an expression returns "undefined" or blank in the output:
Step 1: Check the expression editor preview. Click the field and look at the resolved value. Step 2: Simplify to find the break point:
Try: {{$json}} -- does the whole object exist?
Then: {{$json.body}} -- does body exist?
Then: {{$json.body.user}} -- does user exist?
Then: {{$json.body.user.email}} -- found the missing level
Step 3: Fix with optional chaining + default: {{$json.body?.user?.email || 'missing'}}
Remember the = prefix applies to the whole value:
{
"email": "={{$json.body?.email || '[email protected]'}}",
"name": "={{$json.body?.first_name || 'Customer'}}",
"amount": "={{$json.body?.total ?? 0}}"
}
{{}} in Code nodes -- Code nodes use direct JS/Python. "{{$json.field}}" becomes literal text{{}} in expression fields -- $json.field without braces is treated as literal text= prefix in JSON mode -- ={{$json.field}} not {{$json.field}} in parameter fields{{$json.body.field}}, not {{$json.field}}$node["HTTP Request"] works, $node["http request"] fails silently{{{$json.field}}} is invalid, use {{$json.field}}.json in $node references -- $node["Name"].json.field not $node["Name"].fieldBefore writing any n8n expression:
= prefix.$json.field. No braces..body: $json.body.field.$node["Exact Name"].json.field -- case-sensitive, with .json.$json['field name'], $node["Node Name"].?. for uncertain paths, || 'default' for required output, ?? 0 for numeric fields.development
When the user wants help with paid advertising campaigns on Google Ads, Meta (Facebook/Instagram), LinkedIn, Twitter/X, or other ad platforms. Also use when the user mentions 'PPC,' 'paid media,' 'ad copy,' 'ad creative,' 'ROAS,' 'CPA,' 'ad campaign,' 'retargeting,' or 'audience targeting.' This skill covers campaign strategy, ad creation, audience targeting, and optimization.
testing
--- name: using-sharkitect-methodology description: Use when starting any conversation in a Sharkitect workspace OR before any task involving NEW pricing, positioning, proposal, strategy, plan-execution, or schema-design work — mandates invocation of Sharkitect-specific methodology skills (pricing-strategy, marketing-strategy-pmm, smb-cfo, hq-revenue-ops, executing-plans, brainstorming) under the same anti-rationalization discipline as using-superpowers. Documentation has failed 4 times across H
testing
Use when user says 'end session', 'wrap up', 'stop for the day', 'done for today', 'close out', 'save session', 'wrapping up', or invokes /end-session. Runs the full 9-step end-of-session protocol: resource audit, MEMORY.md update, lessons capture, plan status, pending items, workspace checklist, .tmp/ audit, git commit+push, Supabase brain sync, session brief, summary. Final step schedules a detached self-kill of the current session ONLY (3s delay) so the window closes cleanly. Other claude.exe processes (active workspaces) are NOT touched -- orphan cleanup is handled separately by Claude-Orphan-Cleanup-Hourly with proper age safeguards. Do NOT use for: mid-session quick saves (use session-checkpoint), skill syncing (use sync-skills.py), brain memory queries (use supabase-sync.py pull), document freshness reviews (use document-lifecycle), resource gap detection (use resource-auditor).
testing
Remove signs of AI-generated writing from text. Use when editing or reviewing text to make it sound more natural and human-written. Based on Wikipedia's comprehensive "Signs of AI writing" guide. Detects and fixes patterns including: inflated symbolism, promotional language, superficial -ing analyses, vague attributions, em dash overuse, rule of three, AI vocabulary words, passive voice, negative parallelisms, and filler phrases.