Expression System

Master DeepChain's powerful expression syntax for dynamic data access

Expression System

Status: Production-Ready βœ… Version: 1.0 Last Updated: November 2025

Overview

Think of expressions as the glue that connects your workflow nodes together. The DeepChain Expression System lets you pass data between nodes using a simple {{ }} syntax. You can reference outputs from previous nodes, access trigger data, and use workflow settings to build dynamic, responsive workflows.

In just a few minutes, you'll be able to route data through your entire workflow without writing any code.

Quick Start

The Simplest Expression

Let's say you have a webhook that sends you a user ID, and you want to fetch that user's data. Here's how:

// Reference a node output by name (most common)
{{ $node["API Request"].json.data.userId }}

// Reference data from the trigger
{{ $trigger.webhookPayload.email }}

// Reference workflow settings
{{ $workflow.settings.apiKey }}

// Combine multiple expressions in one URL
{{ $node["Config"].json.baseUrl }}/users/{{ $trigger.userId }}
// Result: "https://api.example.com/users/user-123"

Tip: Start with the simplest expression you need. You can always make it more complex later!

Using the Data Mapper (Recommended)

You don't have to type these by hand. The Data Mapper helps you build expressions visually:

  1. Click the πŸ”— icon next to any configuration field
  2. Browse the data tree on the left (shows all previous nodes, trigger data, and settings)
  3. Click any value to insert its expression automatically
  4. See a live preview of what the expression will return
  5. Click Insert to add it to your field

This is the easiest way to avoid typos and understand your data structure.

Accessing Node Data

Reference a Node by Name

When you want to use output from a previous node, reference it by name:

// Simple field access
{{ $node["API Request"].json.userId }}

// Nested fields
{{ $node["Get User"].json.profile.email }}

// Array access (get first user)
{{ $node["Search Results"].json.users[0].name }}

// Specific array element
{{ $node["Products"].json.items[2].price }}

Best Practice: Use node names instead of IDs. Names are human-readable and stay the same even if you reorganize your workflow.

The .json Part

Every node output is already a JSON object, so you'll see .json in most expressions. It just means "the actual data from this node."

{{ $node["MyNode"].json.firstName }}    // Get firstName field
{{ $node["MyNode"].json }}              // Get entire output

Real-World Example

Let's say you have:

  1. Webhook Node sends you { "customerId": "123" }
  2. Fetch Customer node outputs { "id": "123", "name": "Alice", "email": "alice@example.com" }
  3. You want to Send Email to that customer

You'd use:

// To field:
{{ $node["Fetch Customer"].json.email }}

// Subject:
Hello {{ $node["Fetch Customer"].json.name }}!

// Result sends to: alice@example.com with subject "Hello Alice!"

Accessing Trigger Data

Your workflow is triggered by somethingβ€”a webhook, schedule, or manual click. That trigger data is always available:

// Webhook sends: { "action": "approve", "userId": "456" }
{{ $trigger.action }}          // "approve"
{{ $trigger.userId }}          // "456"

// Schedule trigger
{{ $trigger.scheduledTime }}   // When the schedule ran
{{ $trigger.cronExpression }}  // The cron pattern used

// Manual trigger with custom input
{{ $trigger.inputData.notes }} // Whatever you typed

Accessing Workflow Settings

Every workflow has settings (API keys, timeouts, etc.):

// Your workflow's name and ID
{{ $workflow.name }}

// Custom settings you configured
{{ $workflow.settings.apiKey }}
{{ $workflow.settings.maxRetries }}
{{ $workflow.metadata.department }}

Security Note: Use $workflow.settings for API keys instead of hardcoding them in your nodes!

Powerful Expression Features

Combining Multiple Expressions

You can mix expressions and text in a single field:

// Build a dynamic URL
"https://{{ $node["Config"].json.domain }}/api/v{{ $node["Config"].json.version }}/users"
// If domain is "api.example.com" and version is "2":
// Result: "https://api.example.com/api/v2/users"

// Create a personalized message
"Dear {{ $node["User"].json.firstName }}, your balance is ${{ $node["Account"].json.balance }}"
// Result: "Dear Alice, your balance is $1,250.50"

Tip: You can put as many {{ }} expressions as you need in one field. They're all evaluated and combined.

Navigating Deep Data Structures

Most real APIs return nested data. You can drill down as deep as you need:

{{ $node["API"].json.data.users[0].address.city }}
{{ $node["Response"].json.result.nested.deeply.buried.value }}

// With arrays of objects
{{ $node["Orders"].json.items[0].customer.email }}

// Optional chaining (if available)
{{ $node["User"].json?.profile?.email ?? "no-email@example.com" }}

Error Handling (Don't Worry!)

If you make a mistake, DeepChain handles it gracefully:

  • Invalid expression syntax? The field keeps its original text value
  • Referencing a missing node? You'll get a clear error message
  • Accessing a field that doesn't exist? Returns empty instead of crashing

This means your workflows won't break unexpectedly.

Built-In Functions Reference

Here are all the functions you can use in expressions. Each includes a practical example you can copy and paste.

String Functions

Function Example Result
length {{ $node["Name"].json.value.length }} Number of characters
toUpperCase() {{ $node["Email"].json.address.toUpperCase() }} "ALICE@EXAMPLE.COM"
toLowerCase() {{ $node["Domain"].json.name.toLowerCase() }} "example"
substring(start, end) {{ $node["Code"].json.code.substring(0, 3) }} First 3 chars
includes(text) {{ $node["Email"].json.includes("@example.com") }} true/false
startsWith(text) {{ $node["Code"].json.startsWith("ERR") }} true/false
split(delimiter) {{ $node["Path"].json.split("/") }} ["api", "users"]
trim() {{ $node["Input"].json.trim() }} Removes spaces
replace(find, replace) {{ $node["Text"].json.replace("old", "new") }} "new text"

Array Functions

Function Example Result
length {{ $node["Items"].json.length }} Number of items
[index] {{ $node["Items"].json[0] }} First item
includes(value) {{ $node["Status"].json.includes("active") }} true/false
indexOf(value) {{ $node["List"].json.indexOf("alice") }} 2
join(delimiter) {{ $node["Tags"].json.join(", ") }} "tag1, tag2, tag3"
reverse() {{ $node["Items"].json.reverse() }} Reversed array
slice(start, end) {{ $node["Items"].json.slice(0, 3) }} First 3 items

Math Operations

Operation Example Result
Addition {{ $node["Price"].json + 10 }} 110
Subtraction {{ $node["Total"].json - 20 }} 80
Multiplication {{ $node["Price"].json * 1.1 }} 110 (with 10% tax)
Division {{ $node["Total"].json / 2 }} 50
Comparison {{ $node["Amount"].json > 100 }} true/false

Object Operations

Operation Example Result
Property access {{ $node["User"].json.email }} "alice@example.com"
Nested access {{ $node["User"].json.profile.phone }} "555-1234"
Optional chaining {{ $node["User"].json?.settings?.theme ?? "light" }} Value or default
Keys {{ Object.keys($node["Data"].json) }} ["name", "email"]

Conditional Operations

Operation Example Result
Ternary operator {{ $node["Status"].json === "active" ? "Yes" : "No" }} "Yes"
AND operator {{ $node["Age"].json > 18 && $node["Verified"].json }} true/false
OR operator {{ $node["Plan"].json === "pro" || $node["IsAdmin"].json }} true/false
NOT operator {{ !$node["Deleted"].json }} true/false

Real-World Examples

Example 1: Email Personalization

You receive a form submission and want to send a personalized email:

// Trigger data: { name: "Alice", email: "alice@example.com", amount: 500 }

To: {{ $trigger.email }}
Subject: Your ${{ $trigger.amount }} order confirmation!
Body:
Hi {{ $trigger.name }},
Thank you for your order of ${{ $trigger.amount }}.
Order #{{ $node["Save Order"].json.orderId }}

Example 2: Conditional Routing

Use expressions in an If Node to route based on data:

// Field to check:
{{ $node["Get User"].json.accountBalance }}

// Condition: greater than 1000
// If true β†’ route to "Premium Services"
// If false β†’ route to "Standard Services"

Example 3: Building Dynamic URLs

Create URLs that change based on configuration:

// API Node URL field:
{{ $workflow.settings.apiBaseUrl }}/v{{ $workflow.settings.apiVersion }}/users/{{ $trigger.userId }}/orders

// If apiBaseUrl = "https://api.example.com" and version = "3":
// Final URL: https://api.example.com/v3/users/alice123/orders

Example 4: Data Aggregation

Combine data from multiple nodes:

// In a Transform Node, build a complete object:
{
  "userId": {{ $trigger.userId }},
  "userName": "{{ $node["Fetch User"].json.name }}",
  "email": "{{ $node["Fetch User"].json.email }}",
  "accountBalance": {{ $node["Get Balance"].json.amount }},
  "lastLogin": "{{ $node["Get User"].json.lastLogin }}",
  "requestedAt": "{{ $trigger.timestamp }}"
}

Example 5: Text Processing

Extract and transform text:

// Extract domain from email
{{ $trigger.email.split("@")[1] }}
// If email is "alice@example.com", result is "example.com"

// Convert status to display text
{{ $node["Status"].json.code === 200 ? "Success" : "Failed" }}

// Combine first and last name
{{ $node["User"].json.firstName + " " + $node["User"].json.lastName }}

Common Patterns & Recipes

Pattern 1: Safe Access with Defaults

Sometimes data might not exist. Use the ?? operator for defaults:

// If email doesn't exist, use a placeholder
{{ $node["User"].json.email ?? "no-email@example.com" }}

// If status is missing, default to "pending"
{{ $node["Order"].json.status ?? "pending" }}

// Chain multiple levels with optional chaining
{{ $node["User"].json?.profile?.theme ?? "light" }}

Pattern 2: Array Filtering

Find specific items in an array:

// Get all active users (assume array of user objects)
{{ $node["Users"].json.filter(u => u.status === "active") }}

// Get first premium user
{{ $node["Users"].json.find(u => u.plan === "premium") }}

// Count items matching a condition
{{ $node["Orders"].json.filter(o => o.total > 100).length }}

Pattern 3: Array Transformation

Transform data from one format to another:

// Extract just the emails from users
{{ $node["Users"].json.map(u => u.email) }}

// Build price list with 10% markup
{{ $node["Products"].json.map(p => ({
  name: p.name,
  price: p.basePrice * 1.1
})) }}

Pattern 4: Checking Multiple Conditions

// All conditions must be true
{{ $node["User"].json.age > 18 && $node["User"].json.verified && $node["User"].json.hasPayment }}

// At least one condition must be true
{{ $node["Plan"].json === "pro" || $node["Plan"].json === "business" || $node["IsAdmin"].json }}

// Negate a condition
{{ !$node["IsDeleted"].json }}

Pattern 5: Type Checking

// Check if value is a number
{{ typeof $node["Amount"].json === "number" }}

// Check if value is an array
{{ Array.isArray($node["Items"].json) }}

// Check if string contains text
{{ $node["Email"].json.includes("@example.com") }}

Common Mistakes & How to Avoid Them

Mistake 1: Referencing a Node That Hasn't Run Yet

❌ Wrong:

// This fails if "Process Data" node comes after the current node
{{ $node["Process Data"].json.result }}

βœ… Correct: Only reference nodes that appear before the current node in your workflow.

Tip: Check your workflow diagram. If there's no arrow from source node to your node, you can't reference it yet.

Mistake 2: Forgetting the .json Part

❌ Wrong:

{{ $node["User"].firstName }}  // Missing .json

βœ… Correct:

{{ $node["User"].json.firstName }}  // Has .json

Mistake 3: Using Wrong Quotes

❌ Wrong:

{{ $node['User'].json.firstName }}  // Single quotes can confuse the parser

βœ… Correct:

{{ $node["User"].json.firstName }}  // Double quotes are safer

Mistake 4: Typos in Node Names

❌ Wrong:

{{ $node["Get Usr"].json.email }}  // Typo: "Usr" instead of "User"

βœ… Correct: Use the Data Mapper to avoid typing node names manually. It shows you the exact name.

Mistake 5: Complex Logic in Expressions

❌ Wrong:

{{
  $node["Orders"].json
    .filter(o => o.status === "pending")
    .map(o => ({...complex transformation...}))
    .reduce(...complex calculation...)
}}

βœ… Correct: Break complex transformations into separate nodes (Transform Node, Script Node, etc.).

Expressions are great for passing data, not for complex logic.

Best Practices Checklist

  • βœ… Keep it simple β€” Simple expressions are easier to debug and understand
  • βœ… Use node names β€” Way more readable than IDs
  • βœ… Test with Data Mapper β€” Avoid typos and see your data structure
  • βœ… Reference earlier nodes only β€” Don't create circular references
  • βœ… Use sensible defaults β€” Use ?? for optional fields
  • βœ… Document complex expressions β€” Add a comment about what it does
  • ❌ Don't hardcode sensitive data β€” Use $workflow.settings instead
  • ❌ Don't nest deeply β€” Keep it flat for readability

Expression Cheat Sheet

Quick Reference for All Syntaxes

// Node References
{{ $node["NodeName"].json.field }}
{{ $node["NodeName"].json.array[0] }}
{{ $node["NodeName"].json.nested.deep.field }}

// Trigger References
{{ $trigger.field }}
{{ $trigger.body.userId }}
{{ $trigger.headers.authorization }}

// Workflow References
{{ $workflow.name }}
{{ $workflow.settings.apiKey }}
{{ $workflow.metadata.department }}

// Combining Text and Expressions
"Hello {{ $node["User"].json.name }}!"

// String Methods
{{ $trigger.email.toLowerCase() }}
{{ $node["Text"].json.substring(0, 5) }}
{{ $node["Path"].json.split("/") }}

// Array Methods
{{ $node["Items"].json.length }}
{{ $node["Items"].json[0] }}
{{ $node["Items"].json.join(", ") }}

// Math
{{ $node["Price"].json + 10 }}
{{ $node["Total"].json * 1.1 }}

// Comparisons
{{ $node["Status"].json === "active" }}
{{ $node["Amount"].json > 100 }}

// Conditional
{{ $node["Status"].json === "active" ? "Yes" : "No" }}

// Safe Access
{{ $node["User"].json.email ?? "unknown@example.com" }}
{{ $node["User"].json?.profile?.theme ?? "light" }}

// Filtering Arrays
{{ $node["Items"].json.filter(x => x.active) }}

// Transforming Arrays
{{ $node["Items"].json.map(x => x.name) }}

Troubleshooting

Expression Not Working?

Problem: Expression shows as literal text instead of being evaluated.

Solutions:

  1. Make sure you're using {{ }} (double curly braces)
  2. Check the node name is spelled exactly right (copy from Data Mapper)
  3. Verify the node has already executed (it appears before this node)
  4. Use the Data Mapper to validate your expression

Getting Undefined or Null

Problem: Expression evaluates to undefined or null.

Solutions:

  1. Check the field name is spelled correctly
  2. Use optional chaining: {{ $node["User"].json?.email ?? "none" }}
  3. Verify the field actually exists in the data
  4. Use the Data Mapper preview to see the actual data structure

Node Name Contains Spaces or Special Characters

Problem: Node name has spaces or hyphens.

Solution: Just use the name as-is in quotes:

{{ $node["My API Call"].json.data }}
{{ $node["Process-Data"].json.result }}

Array Index Out of Bounds

Problem: Trying to access index that doesn't exist.

Solution: Add a length check first:

{{ $node["Items"].json.length > 0 ? $node["Items"].json[0].name : "No items" }}

Tips for Success

  1. Use Data Mapper β€” Don't type expressions manually. It's way easier to click.

  2. Test early β€” Use a simple test execution to verify expressions work.

  3. Start simple β€” Get basic data passing working first, then add complexity.

  4. Break it into steps β€” If it's complex, spread logic across multiple nodes.

  5. Name your nodes clearly β€” "Get User Data" is better than "API Request 1".

  6. Add comments β€” In the node name or description, explain what data it provides.

Next Steps

  • Try an example: Build a simple 3-node workflow using expressions
  • Explore Data Mapper: Click the πŸ”— icon in any field to see it in action
  • Read more patterns: Check out data-flow-patterns.md for visual examples
  • Build something real: Create a webhook β†’ fetch data β†’ send email workflow

Need help? Check the example workflows in your DeepChain installation!