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:

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:

TypeSyntaxExample
Text (string)In double quotes"Hi"
NumberWithout quotes42 or 3.14
Booleantrue or falsetrue
Nullnullnull
ObjectBraces {} 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

ExpressionWhat 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

VariableWhat it is
$jsonThe JSON of the current item (the most used)
$nowCurrent timestamp
$todayCurrent date (without time)
$workflow.nameThe workflow's name
$execution.idThe current execution's ID
$vars.NAMEWorkspace variable (see M02-05)
$node["Previous Node"].json.fieldAccess 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.

  1. Open the node editor
  2. In the left column (Input data), find the field you need
  3. Drag it to the center field where you want it
  4. 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:

  1. The user's name
  2. The user's email
  3. The title of the first item
  4. The ID of the second item
See answers
  1. {{$json.user.name}} → "Maria"
  2. {{$json.user.email}} → "maria@example.com"
  3. {{$json.items[0].title}} → "Product A"
  4. {{$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

  1. Expressions Documentation - Complete reference.
  2. Data Structure in n8n - How items are structured.
  3. JSON Quickstart - If you've never seen JSON, this is the official reference.
  4. Cookbook: Common Expressions - Available variables and methods.

Created: May 12, 2026
Version: 1.0