Every workflow eventually needs to make a decision. Should this order go to the express shipping queue or standard? Does this lead qualify for the sales team or need more nurturing? Is this API response a success that continues the workflow, or an error that triggers an alert?
The If node is how n8n handles these decisions. It evaluates conditions you define and routes data down different paths based on whether those conditions are true or false. Think of it as a fork in the road where your workflow data takes one path or another depending on its characteristics.
The Conditional Routing Problem
Without conditional logic, workflows would treat every piece of data identically. An order for $50 would follow the same path as an order for $5,000. A valid email would be processed the same as an invalid one. Every API response would trigger the same downstream actions regardless of success or failure.
This creates obvious problems. You need different handling for different scenarios. The If node provides that branching logic without requiring any code.
What the If Node Actually Does
The If node examines data against conditions you specify:
- Evaluates conditions using operators like equals, contains, greater than, or regex patterns
- Splits the workflow into two paths: true (condition met) and false (condition not met)
- Supports multiple conditions combined with AND or OR logic
- Works with any data type including strings, numbers, booleans, dates, objects, and arrays
Data flows in, the node checks your conditions, and routes items to the appropriate output. Items meeting the conditions go to the “true” output. Items failing the conditions go to the “false” output.
What You’ll Learn
- When to use the If node versus the Switch node
- All available condition types and operators
- How to combine multiple conditions with AND/OR logic
- Expression-based dynamic conditions for complex scenarios
- The most common mistakes and exactly how to fix them
- Real-world examples you can adapt to your workflows
When to Use the If Node
Before configuring conditions, understand when the If node is the right choice versus other options.
| Scenario | Best Choice | Why |
|---|---|---|
| Binary yes/no decision (valid/invalid, success/fail) | If node | Two outcomes is exactly what If provides |
| Filter items meeting specific criteria | If node | True output gets matching items, false gets rest |
| Route by customer tier (bronze/silver/gold/platinum) | Switch node | Four outcomes needs Switch, not nested Ifs |
| Check if API response succeeded | If node | Success/failure is binary |
| Route by country (10+ countries) | Switch node | Many outcomes require Switch |
| Complex multi-step logic with calculations | Code node | When If becomes unwieldy, code is cleaner |
| Check if field exists before processing | If node | Exists/not exists is binary |
Rule of thumb: If you need exactly two paths (true/false, yes/no, success/fail), use the If node. If you need three or more distinct paths, use the Switch node. If you find yourself nesting multiple If nodes, that is a sign you should probably use Switch instead.
Understanding Conditions
Every If node condition has three parts: the left value, an operator, and (usually) a right value. Understanding each component prevents the most common configuration errors.
Condition Anatomy
Left Value Operator Right Value
--------- -------- -----------
$json.status equals "approved"
Left Value: The data you are checking. Usually an expression referencing input data like {{ $json.status }} or {{ $json.order.total }}.
Operator: How you are comparing the values. Equals, contains, greater than, matches regex, exists, and more.
Right Value: What you are comparing against. Can be a static value like "approved" or 100, or another expression for dynamic comparisons.
Data Types
n8n conditions work with different data types. Selecting the correct type prevents false negatives caused by type mismatches.
| Type | Use For | Example Values |
|---|---|---|
| String | Text comparisons | "pending", "[email protected]" |
| Number | Numeric comparisons | 100, 49.99, 0 |
| Boolean | True/false checks | true, false |
| Date | Date/time comparisons | ISO dates, timestamps |
| Object | Check object properties | JSON objects with key-value pairs |
| Array | Check array contents | Lists like ["a", "b", "c"] |
Common mistake: Comparing a string "100" against a number 100 with the equals operator. These are different types. Enable “Loose Type Validation” in options if your data has inconsistent types, or explicitly convert types in your expressions.
Available Operators
The operator you choose determines how left and right values are compared.
String Operators:
| Operator | True When | Example |
|---|---|---|
| equals | Exact match | "pending" equals "pending" |
| not equals | No match | "pending" not equals "approved" |
| contains | Substring found | "hello world" contains "world" |
| not contains | Substring absent | "hello world" not contains "foo" |
| starts with | Begins with value | "order_123" starts with "order_" |
| ends with | Ends with value | "file.pdf" ends with ".pdf" |
| matches regex | Pattern matches | "ABC123" matches [A-Z]+\d+ |
| not matches regex | Pattern fails | "123" not matches [A-Z]+ |
| is empty | String is "" | Empty string check |
| is not empty | String has content | Non-empty string check |
Number Operators:
| Operator | True When | Example |
|---|---|---|
| equals | Same value | 100 equals 100 |
| not equals | Different value | 100 not equals 50 |
| greater than | Left exceeds Right | 150 greater than 100 |
| less than | Left below Right | 50 less than 100 |
| greater than or equal | Left is at least Right | 100 greater than or equal to 100 |
| less than or equal | Left is at most Right | 99 less than or equal to 100 |
Boolean Operators:
| Operator | True When | Example |
|---|---|---|
| is true | Value is true | true is true |
| is false | Value is false | false is false |
| exists | Value is defined | Field present in data |
| does not exist | Value undefined | Field absent from data |
Object/Array Operators:
| Operator | True When |
|---|---|
| is empty | Object {} or array [] has no items |
| is not empty | Object or array has items |
| exists | Property or array exists |
| does not exist | Property or array is absent |
Strict vs Loose Type Validation
By default, n8n uses strict type validation. The string "100" does not equal the number 100. This catches data quality issues but can cause unexpected false results when your data has inconsistent types.
Loose type validation converts values before comparing. The string "100" equals the number 100 because both convert to the same value.
To enable loose validation:
- Open your If node
- Click Add Option
- Select Type Validation
- Choose Loose
Use loose validation when data comes from external sources with inconsistent typing. Use strict validation when data integrity matters and you want mismatches to fail explicitly.
Combining Conditions: AND vs OR
Real-world decisions often require checking multiple criteria. The If node lets you combine conditions using AND or OR logic.
AND Logic (All Must Be True)
With AND, every condition must pass for the overall result to be true. If any single condition fails, the item goes to the false output.
Example: Route only high-value orders from VIP customers
Condition 1: $json.order_total > 500
AND
Condition 2: $json.customer_tier equals "vip"
Both conditions must be true. A $600 order from a standard customer fails. A $400 order from a VIP customer also fails. Only $500+ orders from VIP customers pass.
OR Logic (Any Can Be True)
With OR, only one condition needs to pass for the overall result to be true. All conditions must fail for the item to go to the false output.
Example: Flag orders needing review
Condition 1: $json.order_total > 1000
OR
Condition 2: $json.is_first_order equals true
OR
Condition 3: $json.payment_method equals "wire_transfer"
If any of these is true, the order gets flagged. A $50 first order passes. A $1500 repeat order with credit card passes. A $200 repeat order with wire transfer passes.
Configuring Multiple Conditions
- Add your first condition normally
- Click Add Condition to add more
- Set the Combine dropdown to either “AND” or “OR”
The combine setting applies to all conditions in the node. You cannot mix AND and OR in a single If node. For complex logic requiring both, either:
- Use nested If nodes (one checks AND conditions, next checks OR conditions)
- Use the Code node for full programming logic
Multiple Conditions Example
Scenario: Only process orders that are:
- Status is “paid” AND
- Shipping country is “US” AND
- Order total is greater than 0
Configuration:
| Condition | Left Value | Operator | Right Value |
|---|---|---|---|
| 1 | {{ $json.status }} | equals | paid |
| 2 | {{ $json.shipping_country }} | equals | US |
| 3 | {{ $json.total }} | greater than | 0 |
Combine: AND
All three must be true. A paid US order for $50 passes. A pending US order fails. A paid UK order fails.
Your First If Node
Let’s build a practical example step by step: filtering orders above $100 for priority processing.
Step 1: Add the If Node
- Open your n8n workflow
- Click + to add a node
- Search for “If”
- Click to add it to your canvas
- Connect it to your data source (webhook, HTTP request, etc.)
Step 2: Configure the Condition
With the If node selected:
- Click in the Value 1 field
- Type
{{ $json.order_total }}(or drag the field from the input panel) - Set the operator dropdown to greater than
- Enter
100in the Value 2 field - Leave the data type as Number
Step 3: Test the Node
- If you have pinned data or previous execution data, click Test step
- Examine the output panel
- You should see results split between True and False outputs
True output: Contains items where order_total is greater than 100
False output: Contains items where order_total is 100 or less
Step 4: Connect Downstream Nodes
The If node has two outputs:
- True output (top): Connect your priority processing nodes here
- False output (bottom): Connect your standard processing nodes here
Each path can have completely different downstream workflows. Priority orders might go to express shipping and VIP notification. Standard orders follow the regular fulfillment path.
Understanding the Outputs
When you test or execute the workflow:
- Items go to exactly one output (never both)
- Each output maintains the full item data
- Downstream nodes receive items in the original order
- Empty outputs are normal when no items match (or all items match)
Expression-Based Conditions
Static conditions work for simple cases. But often you need dynamic comparisons, complex logic, or values calculated from data. Expressions unlock this power.
Using Expressions in Conditions
Both left and right values can be expressions. This enables:
Comparing two fields from the same item:
// Left Value
{{ $json.actual_amount }}
// Operator
equals
// Right Value
{{ $json.expected_amount }}
Comparing against calculated values:
// Left Value
{{ $json.order_total }}
// Operator
greater than
// Right Value
{{ $json.minimum_order * 1.1 }} // 10% above minimum
Referencing data from other nodes:
// Left Value
{{ $json.customer_id }}
// Operator
equals
// Right Value
{{ $('CRM Lookup').item.json.id }}
For detailed expression syntax and patterns, see our comprehensive n8n expressions guide.
Accessing Previous Node Data
When your condition needs data from a specific previous node (not the immediate input), use the $('Node Name') syntax:
// Data from the immediate previous node
{{ $json.fieldName }}
// Data from a specific named node
{{ $('HTTP Request').item.json.status }}
// First item from a node
{{ $('Webhook').first().json.orderId }}
Common mistake: Using $json.fieldName when the data comes from a different branch. This returns undefined because $json refers to the immediate input, not all previous nodes.
Optional Chaining for Safety
When accessing nested properties that might not exist, use optional chaining to prevent errors:
// Unsafe - errors if customer is undefined
{{ $json.customer.email }}
// Safe - returns undefined instead of erroring
{{ $json.customer?.email }}
// With fallback value
{{ $json.customer?.email ?? "unknown" }}
The ?. operator checks if each level exists before accessing the next. Combined with the nullish coalescing operator (??), you can provide sensible defaults.
Complex Expression Examples
Check if array contains a value:
// Left Value
{{ $json.tags.includes("priority") }}
// Operator
equals
// Right Value (Boolean type)
true
Check string length:
// Left Value
{{ $json.description.length }}
// Operator
greater than
// Right Value
100
Date comparison:
// Left Value
{{ new Date($json.created_at) }}
// Operator
greater than
// Right Value
{{ new Date(Date.now() - 24 * 60 * 60 * 1000) }} // 24 hours ago
Test complex expressions before running workflows using our expression validator tool.
Common Mistakes and How to Fix Them
After reviewing hundreds of community forum posts and support requests, these are the mistakes that trip up most users. Each includes the exact fix.
Mistake 1: Type Mismatch in Comparisons
Symptom: Condition should match but always goes to false output.
Cause: Comparing different types. String "100" does not equal number 100 in strict mode.
Example:
API returns: amount = "150" (a string, not a number)
Condition: $json.amount greater than 100 (comparing to number)
Result: False (string "150" vs number 100)
Fix: Either:
- Convert in expression:
{{ Number($json.amount) }} - Enable loose type validation in node options
- Fix the data source to return correct types
Mistake 2: Undefined Field References
Symptom: Error message about undefined values, or condition fails unexpectedly.
Cause: Using $json.fieldName when data comes from a different node than the immediate input.
Example:
Webhook triggers workflow → HTTP Request → If node
If condition uses: {{ $json.webhookField }}
Result: Undefined (because $json refers to HTTP Request output)
Fix: Reference the correct node explicitly:
{{ $('Webhook').item.json.webhookField }}
Mistake 3: Both True and False Branches Executing
Symptom: Nodes connected to both If outputs run, even though only one should.
Cause: A Merge node downstream waits for both inputs, triggering execution of both branches even when one has no data.
Why this happens: n8n’s execution model means Merge nodes can pull data through connected paths even when the If node didn’t send data that way.
Fix:
Option 1: Add a Filter node or another If node after the Merge to filter empty results
Option 2: Restructure workflow to avoid Merge after If nodes
Option 3: Use the “Loose” merge mode and handle empty arrays in downstream logic
Mistake 4: Empty vs Null vs Undefined Confusion
Symptom: “is empty” operator doesn’t work as expected.
Cause: These are three different states:
| State | What It Means | ”Is Empty” Result | ”Exists” Result |
|---|---|---|---|
"" | Empty string | True | True |
null | Null value | False | True |
undefined | Field doesn’t exist | False | False |
[] | Empty array | True | True |
{} | Empty object | True | True |
Fix: Use the correct operator:
- Use exists to check if a field is present at all
- Use is empty to check if a present field has no content
- Use equals with explicit values for null checks
Mistake 5: Expression Syntax Errors
Symptom: Error about invalid expressions or unexpected tokens.
Common causes:
Missing expression brackets:
Wrong: $json.email
Right: {{ $json.email }}
Trailing period:
Wrong: {{ $json.customer. }}
Right: {{ $json.customer }}
Unclosed brackets:
Wrong: {{ $json.items[0].name }
Right: {{ $json.items[0].name }}
Fix: Use our workflow debugger to identify exactly where the expression fails.
Mistake 6: Case Sensitivity Issues
Symptom: String comparison fails even though values look identical.
Cause: "Pending" does not equal "pending" by default.
Fix: Normalize case in your expression:
// Left Value
{{ $json.status.toLowerCase() }}
// Right Value
pending
Or enable case-insensitive comparison in the options (if available for your operator).
If Node vs Switch Node
Choosing between If and Switch affects workflow readability and maintainability. Here’s when to use each.
Comparison Table
| Criteria | If Node | Switch Node |
|---|---|---|
| Number of outputs | 2 (true/false) | Unlimited |
| Best for | Binary decisions | Multiple distinct routes |
| Fallback handling | False output is fallback | Explicit fallback output |
| Complexity | Simpler for two paths | Simpler for 3+ paths |
| Readability | Clear for yes/no | Clear for categorization |
When to Choose If
Use the If node when:
- Binary outcomes: Success/failure, valid/invalid, approved/rejected
- Filtering data: Separating items that match criteria from those that don’t
- Simple threshold checks: Amount above/below limit, date before/after cutoff
- Existence checks: Field present/absent, array empty/populated
When to Choose Switch
Use the Switch node when:
- Multiple categories: Status values (pending/processing/complete/failed)
- Regional routing: Different paths for US/EU/APAC/other
- Tier-based logic: Bronze/silver/gold/platinum customer handling
- Type-based processing: Different actions for order/refund/subscription events
Avoiding Nested If Nodes
A common anti-pattern is nesting multiple If nodes:
If (status = pending)
└── If (priority = high)
└── If (amount > 1000)
└── Process
This becomes hard to follow and maintain. Instead:
Option 1: Use Switch node with combined conditions
Option 2: Use Code node for complex branching logic
Option 3: Rethink the workflow structure to separate concerns
If you find yourself nesting more than two If nodes, that’s a strong signal to refactor.
Real-World Examples
Example 1: Route Orders by Customer Tier
Scenario: VIP customers get priority processing and notifications. Regular customers follow the standard path.
Configuration:
Left Value: {{ $json.customer.tier }}
Operator: equals
Right Value: vip
True branch: Priority queue, VIP email template, expedited shipping False branch: Standard queue, regular email, normal shipping
Example 2: Validate API Response
Scenario: Check if an API request succeeded before processing the response.
Configuration:
Left Value: {{ $json.success }}
Operator: is true
Or for HTTP status codes:
Left Value: {{ $('HTTP Request').item.json.statusCode }}
Operator: equals
Right Value: 200
True branch: Process response data False branch: Log error, send alert, retry logic
Example 3: Filter Valid Email Addresses
Scenario: Only process items with properly formatted email addresses.
Configuration:
Left Value: {{ $json.email }}
Operator: matches regex
Right Value: ^[^\s@]+@[^\s@]+\.[^\s@]+$
True branch: Email is valid format, proceed with processing False branch: Invalid email, log for review or skip
Example 4: Date-Based Routing
Scenario: Handle recent items differently from older ones.
Configuration:
Left Value: {{ new Date($json.created_at) }}
Operator: greater than
Right Value: {{ new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) }}
This checks if the item was created within the last 7 days.
True branch: Recent item, priority handling False branch: Older item, batch processing
Example 5: Check Array Contents
Scenario: Route differently based on whether an order contains specific product categories.
Configuration:
Left Value: {{ $json.items.some(item => item.category === "electronics") }}
Operator: is true
True branch: Contains electronics, apply special handling False branch: No electronics, standard processing
Pro Tips and Best Practices
1. Name Your If Nodes Descriptively
Instead of “If” or “If1”, use names that describe the decision:
- “Is VIP Customer?”
- “Order Over $500?”
- “API Success?”
- “Valid Email Format?”
This makes workflows self-documenting and debugging much easier.
2. Test with Representative Data
Before connecting to production systems, test your If node with:
- Values that should pass (verify true output works)
- Values that should fail (verify false output works)
- Edge cases (exact threshold values, empty strings, null values)
- Real data samples covering all scenarios
Pin data from previous tests to iterate on conditions without re-triggering upstream nodes.
3. Handle Edge Cases Explicitly
Don’t assume data will always be present or formatted correctly:
// Instead of assuming field exists:
{{ $json.amount > 100 }}
// Check first:
{{ $json.amount !== undefined && $json.amount > 100 }}
// Or use optional chaining with fallback:
{{ ($json.amount ?? 0) > 100 }}
4. Document Complex Conditions
When a condition isn’t obvious, add a sticky note in n8n explaining:
- What business rule the condition implements
- Why this threshold was chosen
- Edge cases that might need attention
Future you (and your teammates) will thank you.
5. Keep Conditions Focused
Each If node should make one clear decision. If you find yourself adding many conditions with OR logic, consider whether the workflow design could be simpler.
Sometimes it’s cleaner to:
- Split into multiple If nodes with clear purposes
- Use a Switch node for categorization
- Pre-process data to simplify the condition
6. Monitor for Unexpected Paths
In production, track which output path gets used. If you expect 90% true but see 90% false, something is wrong with either:
- Your condition logic
- The incoming data quality
- Your assumptions about the data
Use n8n’s execution log or add logging nodes to track path distribution.
Our workflow development services can help design robust conditional logic for complex business requirements. For strategic guidance on workflow architecture, explore our consulting services.
Frequently Asked Questions
Why does my If node always go to false even when the condition should match?
This is almost always a type mismatch issue. The most common cause is comparing a string to a number. If your API returns "100" (string) and you compare against 100 (number), strict type checking fails. Check the actual data type in your input by examining the previous node’s output. Either convert the type in your expression using Number($json.amount) or String($json.amount), or enable “Loose Type Validation” in the If node options. Also verify you’re referencing the correct field name, as typos cause undefined values that never match.
How do I check if a field exists versus checking if it’s empty?
These are different checks requiring different operators. Use exists to check whether a field is present in the data at all, even if its value is null or empty. Use is empty to check if a present field has no content (empty string, empty array, empty object). A field can exist but be empty (like name: ""), or not exist at all (field missing from object). If you need both checks, combine them: the field exists AND is not empty. For null specifically, compare with equals against a null value or check in a Code node.
Can I use expressions in both condition values, not just the left side?
Yes. Both the left value and right value fields accept expressions. This enables dynamic comparisons like checking if one field equals another ({{ $json.actual }} equals {{ $json.expected }}), or comparing against calculated values ({{ $json.amount }} greater than {{ $json.limit * 1.1 }}). You can also reference data from other nodes in the right value using {{ $('Node Name').item.json.field }}. This makes conditions fully dynamic based on runtime data rather than just static values.
Why are both my true and false branches executing when only one should?
This counterintuitive behavior typically happens when you have a Merge node downstream from your If node. n8n’s execution model means the Merge node waits for input from all connected branches. This can trigger execution of nodes in both paths even when the If node only sent data down one path. To fix this, either restructure your workflow to avoid Merge after If nodes, add a Filter node after the Merge to remove empty results, or use conditional execution settings on downstream nodes. Check the official n8n documentation on splitting for detailed explanation.
When should I use the If node versus the Switch node for conditional logic?
Use the If node when you have a binary decision with exactly two outcomes: true or false, yes or no, success or failure. Examples include checking if an amount exceeds a threshold, if a field matches a value, or if data is valid. Use the Switch node when you have three or more distinct outcomes that need different handling. Examples include routing by status (pending/processing/complete/failed), by region (US/EU/APAC), or by customer tier (bronze/silver/gold). If you find yourself nesting multiple If nodes to handle different cases, that’s a signal to refactor using a Switch node. Switch nodes are also clearer when you need an explicit fallback for unmatched cases. See our n8n workflow best practices guide for more architectural guidance.