Module 3: Nodes and Connections
Data Between Nodes: JSON and Expressions for Non-Devs
Capsule overview
We've reached the moment we mentioned in the module introduction: JSON. If you've never seen it, you may feel a bit of tension opening this capsule. Good news: JSON is much simpler than it looks, and n8n workflows use only the most basic version.
The data that travels between nodes in n8n is in JSON. When a trigger captures a form, the form data arrives in JSON at the first node. When a Slack node sends a message and returns a confirmation, that confirmation is in JSON. When OpenAI returns a response to you, it's in JSON. That's why knowing how to read basic JSON and write expressions that reference specific JSON fields is the highest-leverage skill for building functional workflows.
This capsule teaches you: what JSON is with business analogies, how to read the structure, how to write {{...}} expressions to reference fields, and the most common patterns you'll use 95% of the time. By the end you stop looking at JSON with distrust and start reading it as structured information you can use.
What is JSON, in a simple analogy?
Imagine you fill out a contact form with these fields:
- Name: Maria López
- Email: maria@example.com
- Message: I want information
Now imagine that form "converts" into structured text so a computer can understand it:
{
"name": "Maria López",
"email": "maria@example.com",
"message": "I want information"
}
That's JSON. The braces { } open and close an "object." Each field has a name in quotes, a colon :, and a value. Values can be text (in quotes), numbers (without quotes), or more nested objects.
That's practically all the syntax you need for n8n. Let's go deeper.
Value types in JSON
A value in JSON can be:
| Type | Syntax | Example |
|---|---|---|
| Text (string) | In double quotes | "Hi" |
| Number | Without quotes | 42 or 3.14 |
| Boolean | true or false | true |
| Null | null | null |
| Object | Braces {} with fields | {"name": "Maria"} |
| Array (list) | Brackets [] with values | [1, 2, 3] |
{
"name": "Maria", // string
"age": 30, // number
"is_vip": true, // boolean
"phone": null, // null (empty field)
"address": { // nested object
"city": "CDMX",
"postal_code": "01000"
},
"tags": ["premium", "frequent"] // array of strings
}
Data you'll see in n8n
The typical data that travels between nodes is objects or arrays of objects. For example:
Case 1: 1 item with form data
{
"name": "Maria",
"email": "maria@example.com",
"type": "VIP"
}
Case 2: Multiple items (50 Sheets rows)
[
{ "name": "Maria", "email": "maria@x.com", "type": "VIP" },
{ "name": "Luis", "email": "luis@x.com", "type": "Pro" },
{ "name": "Ana", "email": "ana@x.com", "type": "Free" }
// ... 47 more
]
Case 3: Object with nested data (API response)
{
"user": {
"id": 123,
"profile": {
"name": "Maria",
"preferences": {
"language": "es",
"notifications": true
}
}
},
"permissions": ["read", "write"]
}
n8n shows these in the Input/Output zone of the node editor (capsule 02). The more nested, the deeper the JSON.
Expressions: how to reference data
Here's the magic. In any field of a node, you can write an expression in double braces {{ ... }} that evaluates at run time:
Channel: #{{$json.type}}-clients
n8n replaces {{$json.type}} with the real value of the type field from the input JSON. If type = "VIP", the final channel is #VIP-clients.
Basic syntax
| Expression | What it does |
|---|---|
{{$json.name}} | Reads the name field of the input JSON |
{{$json.user.name}} | Reads the name field inside the user object (nested) |
{{$json.tags[0]}} | Reads the first element of the tags array |
{{$json.email}} | Reads the email field |
The pattern: {{$json.FIELD_NAME}}. For nested fields: {{$json.parent.child.grandchild}}. For arrays: {{$json.array[0]}} (index starts at 0).
Simple operations
n8n allows basic operations inside expressions:
{{$json.price * 1.16}} // multiply (tax)
{{$json.name + " " + $json.last_name}} // concatenate
{{$json.quantity >= 10}} // comparison (returns true/false)
{{$json.email.toLowerCase()}} // convert to lowercase
Special variables
| Variable | What it is |
|---|---|
$json | The JSON of the current item (the most used) |
$now | Current timestamp |
$today | Current date (without time) |
$workflow.name | The workflow's name |
$execution.id | The current execution's ID |
$vars.NAME | Workspace variable (see M02-05) |
$node["Previous Node"].json.field | Access the JSON of a specific node (not just the immediate one) |
"Fix" vs "Expression" mode
In each field of the node editor, n8n lets you toggle between two modes:
Fixed mode
You write a literal value. Example: in the Channel field, you write #general. That's what gets sent.
Expression mode
You activate the "Expression" toggle on the field. Now you can write {{...}}. Whatever you put evaluates before running.
Fixed mode: Channel = #general → always #general
Expression mode: Channel = #{{$json.type}} → varies with data ($json.type = "VIP" → #VIP)
Mixing fixed text + an expression is also valid: Hi {{$json.name}}, welcome is half fixed, half expression.
Common patterns
Pattern 1: Personalized greeting
Email body: "Hi {{$json.name}}, thanks for signing up."
With data { "name": "Maria" } → Hi Maria, thanks for signing up.
Pattern 2: Concatenate fields
Message: "{{$json.name}} {{$json.last_name}} ordered {{$json.product}}"
Pattern 3: Dynamic routing
Channel: #{{$json.area}}-team
With area = "marketing" → #marketing-team. With area = "sales" → #sales-team.
Pattern 4: Simple calculations
Total with tax: {{$json.price * 1.16}}
Pattern 5: Conditionals in an expression (advanced)
{{$json.type === "VIP" ? "Priority attention" : "Normal attention"}}
(This is a JavaScript ternary: condition ? yes_case : no_case)
Pattern 6: Access data from a non-immediate previous node
{{$node["Webhook"].json.email}}
Useful when there are 5 nodes between the Webhook and the current node and you still need the original webhook email.
Drag-and-drop expressions: the magic trick
n8n has a great feature: instead of writing expressions by hand, you can drag fields from the Input panel directly into the field where you want to use them.
- Open the node editor
- In the left column (Input data), find the field you need
- Drag it to the center field where you want it
- n8n generates the correct expression automatically
This avoids typos and guarantees the syntax is correct.
Items and automatic iteration
Remember from capsule 02: if Input has 50 items, n8n iterates automatically. Each iteration has its own $json (the current item of that iteration).
Input: 50 items, each with {name, email}
Email node with field "To: {{$json.email}}"
→ Runs 50 times, each time with the email of the current item
This is powerful: you don't write loops, n8n does them for you.
Troubleshooting
Problem 1: "{{$json.something}} shows undefined"
Cause: the something field doesn't exist in the Input data.
Solution: check Input data in the editor. The real field may be named differently (e.g., firstName instead of name). Use Schema view to see all the available fields.
Problem 2: "My expression appears literally instead of evaluating"
Cause: the field is in Fixed mode instead of Expression.
Solution: click the field's toggle to switch to Expression mode. The {{...}} syntax only evaluates in Expression mode.
Problem 3: "Error: Cannot read property of undefined"
Cause: you're trying to read a nested field where the parent is null. For example $json.user.name when user is null.
Solution: verify that the parent field exists first. Or use optional chaining: {{$json.user?.name}} (returns undefined without an error).
Problem 4: "Drag-and-drop doesn't work"
Cause: you're in a field that doesn't accept expressions, or the mode isn't activated.
Solution: verify that the field supports expressions (most do; some, like predefined dropdowns, don't).
Problem 5: "String concatenation with + doesn't work"
Cause: one of the values is null. "Hi " + null gives "Hi null".
Solution: make sure the fields aren't null. Use a default: {{$json.name || "Customer"}} (if name is null, uses "Customer").
Exercises
Exercise 1: Read simple JSON
Given this JSON:
{
"user": {
"name": "Maria",
"email": "maria@example.com"
},
"items": [
{ "id": 1, "title": "Product A" },
{ "id": 2, "title": "Product B" }
]
}
Write the expression to get:
- The user's name
- The user's email
- The title of the first item
- The ID of the second item
See answers
{{$json.user.name}}→ "Maria"{{$json.user.email}}→ "maria@example.com"{{$json.items[0].title}}→ "Product A"{{$json.items[1].id}}→ 2
Expected result: you know how to navigate nested JSON and arrays. This covers 80% of real cases.
Exercise 2: Build a personalized greeting
Create a workflow:
- Manual Trigger
- Set node with
{ name: "Maria", type: "VIP" } - Another Set node that creates:
greeting = "Hi Maria, you are VIP"(using expressions)
See solution
In the second Set:
Field name: greeting
Field value: Hi {{$json.name}}, you are {{$json.type}}
(Make sure the field is in Expression mode)
Expected output:
{
"name": "Maria",
"type": "VIP",
"greeting": "Hi Maria, you are VIP"
}
Expected result: you master concatenation with expressions. It applies directly to emails, Slack messages, etc.
Exercise 3: Dynamic routing
Create a workflow that, depending on the value of area, sends a message to the corresponding channel:
- Set:
{ area: "marketing", message: "New lead" } - Slack node: Channel =
#{{$json.area}}-team, Text ={{$json.message}}
See verification
If area = "marketing" → sends to #marketing-team.
If you change area = "sales" → sends to #sales-team.
Expected result: you understand the dynamic-routing pattern. A single workflow can behave differently based on data.
Exercise 4: Calculation with an expression
Create a workflow:
- Set:
{ price: 100, quantity: 3 } - Set afterward: calculate
total = price * quantity,total_with_tax = total * 1.16
See solution
Field name: total
Field value: {{$json.price * $json.quantity}}
Field name: total_with_tax
Field value: {{$json.price * $json.quantity * 1.16}}
Output:
{
"price": 100,
"quantity": 3,
"total": 300,
"total_with_tax": 348
}
Expected result: expressions do simple math without needing Code nodes. Useful for totals, discounts, tax, conversions.
Exercise 5: Drag-and-drop
In a workflow with real Input data, try dragging a field from Input into an editor field (e.g., drag email to the "To" field of the Email node).
See expected experience
n8n automatically generates {{$json.email}} in the destination field. It activates Expression mode by itself. Without typing anything.
Expected result: you realize that for 80% of cases you don't need to memorize the syntax — drag-and-drop generates it for you.
Summary
- Data between nodes travels in JSON
- Basic JSON:
{ "field": "value" }. Braces for objects, quotes for strings, numbers without quotes - Types: string, number, boolean, null, nested object, array
- Expressions:
{{$json.field}}to read fields of the input JSON - Nested:
{{$json.parent.child}}. Arrays:{{$json.array[0]}} - Fixed mode (literal) vs Expression mode (
{{...}}evaluated) - Drag-and-drop generates automatic expressions (avoids typos)
- Special variables:
$now,$today,$vars.X,$node["Name"].json - 6 common patterns: greeting, concatenate, routing, calculation, conditional, non-immediate data
- Automatic iteration: 50 items → 50 runs of the node, each with its own
$json
Additional resources
- Expressions Documentation - Complete reference.
- Data Structure in n8n - How items are structured.
- JSON Quickstart - If you've never seen JSON, this is the official reference.
- Cookbook: Common Expressions - Available variables and methods.
Created: May 12, 2026
Version: 1.0