Data quality makes or breaks your automations. Pull a list of contacts from your CRM, and half might have missing emails. Fetch orders from an API, and some will be test transactions. Receive webhook data, and certain records will fail your validation rules.
The Filter node exists to solve this problem. It examines each item flowing through your workflow, checks it against conditions you define, and passes through only the items that match. Everything else gets dropped, leaving you with clean, validated data for downstream processing.
The Data Cleanup Problem
Without filtering, your workflows process everything identically. Invalid emails get sent to your email service. Test orders trigger real fulfillment. Incomplete records cause downstream nodes to fail with cryptic undefined errors.
You could handle this with If nodes, but that creates unnecessary branching when you only care about keeping the valid items. The Filter node provides a cleaner approach: define your criteria, and only matching items continue through the workflow.
What the Filter Node Does
The Filter node evaluates conditions against each item in your data:
- Passes matching items to the next node in the workflow
- Drops non-matching items silently (no error, they just disappear)
- Supports multiple conditions combined with AND or OR logic
- Works with all data types including strings, numbers, booleans, dates, arrays, and objects
Think of it as a quality gate. Items that meet your standards pass through. Items that fail your criteria get filtered out before they cause problems downstream.
What You’ll Learn
- When to use Filter versus If or Switch nodes
- All available operators for different data types
- How to combine multiple conditions with AND/OR logic
- Expression-based filtering for dynamic conditions
- Common mistakes that cause filtering failures and how to fix them
- Real-world filtering patterns you can adapt immediately
When to Use the Filter Node
Before configuring conditions, understand when Filter is the right choice versus other flow control options.
| Scenario | Best Choice | Why |
|---|---|---|
| Remove invalid records before processing | Filter node | You want to drop bad data, not route it |
| Keep only orders above $100 | Filter node | Single output stream of matching items |
| Route VIPs to priority queue, others to standard | If node | Need two different paths, not just filtering |
| Categorize by region (US/EU/APAC/Other) | Switch node | Multiple distinct paths needed |
| Check if API response succeeded before continuing | If node | Binary branch based on success/failure |
| Remove duplicates from a list | Remove Duplicates node | Specialized node for this purpose |
| Keep only items with valid email format | Filter node | Validation filtering with regex |
| Process paid orders differently from pending | If node | Need both paths to continue |
Rule of thumb: Use Filter when you want to keep only matching items and discard the rest. Use If when you need both the matching and non-matching items to continue down different paths. Use Switch when you have three or more distinct categories.
Understanding Filter Conditions
Every Filter condition has three components that determine whether an item passes or gets dropped.
Condition Anatomy
Left Value Operator Right Value
--------- -------- -----------
$json.status equals "approved"
Left Value: The data field you are checking. Usually an expression like {{ $json.status }} or {{ $json.order.total }} that references a property from the input item.
Operator: The comparison type. Equals, contains, greater than, matches regex, exists, and more. The available operators depend on the data type you select.
Right Value: What you are comparing against. Can be a static value like "approved" or 100, or another expression for dynamic comparisons.
Data Types
Select the correct data type to access the right operators. Mismatched types cause unexpected filtering failures.
| 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 & Time | Date comparisons | ISO dates, timestamps |
| Object | Property existence checks | JSON objects |
| Array | Array content checks | Lists like ["a", "b", "c"] |
String Operators
| Operator | Passes 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 | "report.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 |
For regex patterns, use standard JavaScript regular expression syntax. The pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$ validates basic email format. The pattern ^\d{5}$ matches exactly five digits.
Number Operators
| Operator | Passes 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 at least right | 100 greater than or equal 100 |
| less than or equal | Left at most right | 99 less than or equal 100 |
Boolean Operators
| Operator | Passes When |
|---|---|
| is true | Value equals true |
| is false | Value equals false |
| exists | Field is present and defined |
| does not exist | Field is absent or undefined |
Date & Time Operators
| Operator | Passes When |
|---|---|
| is after | Date occurs after the comparison date |
| is before | Date occurs before the comparison date |
| equals | Dates match exactly |
Date comparisons require properly formatted dates. ISO format (2024-01-15T10:30:00Z) works most reliably. If comparing against “now”, use {{ $now.toISO() }} in your expression.
Array and Object Operators
| Operator | Passes When |
|---|---|
| is empty | Array [] or object {} has no items |
| is not empty | Array or object contains items |
| exists | Property or array exists |
| does not exist | Property or array is absent |
Combining Conditions: AND vs OR
Real filtering often requires checking multiple criteria. The Filter node lets you combine conditions using AND or OR logic.
AND Logic (All Must Be True)
With AND, every condition must pass for the item to continue. If any single condition fails, the item gets filtered out.
Example: Keep only high-value orders from verified customers
Condition 1: $json.order_total > 500
AND
Condition 2: $json.customer_verified equals true
A $600 order from an unverified customer fails. A $400 order from a verified customer also fails. Only orders meeting both criteria pass through.
OR Logic (Any Can Be True)
With OR, only one condition needs to pass for the item to continue. All conditions must fail for the item to get filtered out.
Example: Flag records needing review
Condition 1: $json.status equals "pending"
OR
Condition 2: $json.flagged equals true
OR
Condition 3: $json.error_count > 0
If any of these conditions is true, the item passes. A record that is approved, not flagged, and has zero errors gets filtered out.
Configuring Multiple Conditions
- Add your first condition as normal
- Click Add Condition to create additional conditions
- 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 Filter node. For complex logic requiring both, either chain multiple Filter nodes or use the Code node for full JavaScript filtering.
Your First Filter Node
Walk through a practical example: filtering a list of contacts to keep only those with valid email addresses.
Step 1: Add the Filter Node
- Open your n8n workflow
- Click + to add a node
- Search for “Filter”
- Click to add it to your canvas
- Connect it to your data source
Step 2: Configure the Condition
With the Filter node selected:
- Click in the Value 1 field
- Type
{{ $json.email }}or drag the email field from the input panel - Set the data type dropdown to String
- Set the operator to matches regex
- Enter
^[^\s@]+@[^\s@]+\.[^\s@]+$as the pattern (basic email validation)
Step 3: Test the Node
- If you have pinned data or previous execution data, click Test step
- Examine the output panel
- You should see only items where the email field matches the regex pattern
Items without valid email format are silently dropped. No error, no false output, they simply do not appear in the Filter node’s output.
Step 4: Connect Downstream Nodes
The Filter node has a single output. Connect your processing nodes (send email, update CRM, etc.) knowing that all items reaching them have valid email addresses.
Expression-Based Filtering
Static conditions work for simple cases. Expressions unlock dynamic filtering based on calculated values, data from other nodes, or complex logic.
Using Expressions in Conditions
Both left and right values accept expressions. This enables:
Comparing two fields from the same item:
// Left Value
{{ $json.actual_quantity }}
// Operator
greater than or equal
// Right Value
{{ $json.minimum_quantity }}
This keeps items where actual quantity meets or exceeds the minimum requirement.
Comparing against calculated thresholds:
// Left Value
{{ $json.order_total }}
// Operator
greater than
// Right Value
{{ $json.customer_tier === 'vip' ? 100 : 250 }}
Different thresholds for VIP versus standard customers, evaluated dynamically.
Referencing data from other nodes:
// Left Value
{{ $json.category }}
// Operator
equals
// Right Value
{{ $('Settings Node').item.json.targetCategory }}
Filter based on a category value defined earlier in the workflow.
For detailed expression syntax and patterns, see our comprehensive n8n expressions guide.
Accessing Previous Node Data
When your filter condition needs data from a specific earlier node, use the $('Node Name') syntax:
// Data from the immediate previous node
{{ $json.fieldName }}
// Data from a specific named node
{{ $('HTTP Request').item.json.minOrderValue }}
// First item from a node (useful for configuration data)
{{ $('Config').first().json.threshold }}
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.tier }}
// Safe - returns undefined instead of erroring
{{ $json.customer?.tier }}
// With fallback value using nullish coalescing
{{ $json.customer?.tier ?? "standard" }}
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 specific value:
// Left Value
{{ $json.tags.includes("priority") }}
// Operator
equals
// Right Value (Boolean type)
true
Filter by string length:
// Left Value
{{ $json.description?.length ?? 0 }}
// Operator
greater than
// Right Value
50
Filter items from the last 7 days:
// Left Value
{{ new Date($json.created_at) }}
// Operator
is after
// Right Value
{{ new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) }}
Test complex expressions before running workflows using our expression validator tool.
Common Mistakes and How to Fix Them
These are the issues that cause the most filtering failures, based on community forum patterns and support requests.
Mistake 1: Type Mismatch in Comparisons
Symptom: Filter removes all items even though some should match, or keeps all items when some should be filtered.
Cause: Comparing different types. The string "100" does not equal the number 100 in strict mode.
Example:
API returns: amount = "150" (string, not number)
Condition: $json.amount greater than 100 (comparing to number)
Result: Fails because "150" (string) cannot be compared to 100 (number)
Fix options:
- Convert in expression:
{{ Number($json.amount) }} - Enable Less Strict Type Validation in node options
- Fix the data source to return correct types
The Less Strict Type Validation option tells n8n to attempt type conversion before comparing. Enable it by clicking Add Option in the Filter node and selecting the option.
Mistake 2: Undefined Field References
Symptom: Expression error about undefined values, or condition behaves unexpectedly.
Cause: Using $json.fieldName when the data comes from a different node than the immediate input.
Example:
Webhook → HTTP Request → Filter
Filter 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: Empty vs Null vs Undefined Confusion
Symptom: “is empty” operator does not work as expected.
Cause: These are three different states with different behaviors:
| State | What It Means | ”Is Empty” Result | ”Exists” Result |
|---|---|---|---|
"" | Empty string | True | True |
null | Null value | False | True |
undefined | Field absent | 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
- For null specifically, use equals with an expression:
{{ $json.field === null }}
Mistake 4: 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. For JSON structure issues, the JSON fixer tool can help identify formatting problems.
Mistake 5: Filtering Arrays vs Filtering Items
Symptom: Trying to filter elements within an array field, but the whole item passes or fails.
Cause: The Filter node filters workflow items, not array contents within those items.
Example scenario: You have an item with a tags array ["sales", "priority", "follow-up"] and want to keep items where tags contain “priority”.
Correct approach:
// Left Value
{{ $json.tags.includes("priority") }}
// Operator
equals
// Right Value
true
This checks if the tags array includes “priority” and passes the entire item if true. It does not filter elements within the tags array itself.
To filter array contents: Use the Code node or Edit Fields node to transform the array before the Filter node.
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 compare both sides with the same transformation:
{{ $json.status?.toLowerCase() === "pending" }}
Filter Node vs If Node
This is the most common point of confusion. Here is when to use each.
Comparison Table
| Criteria | Filter Node | If Node |
|---|---|---|
| Number of outputs | 1 (matching items only) | 2 (true and false branches) |
| Non-matching items | Dropped silently | Sent to false output |
| Best for | Data cleanup and validation | Workflow branching |
| Output behavior | Single stream of clean data | Two parallel paths |
| Use case pattern | ”Keep only items where…" | "If condition, do X, else do Y” |
When to Choose Filter
Use the Filter node when:
- You want to remove invalid, incomplete, or irrelevant items
- Non-matching items should be discarded, not processed differently
- You need a single clean output for downstream processing
- You are doing data validation before an operation (email sends, API calls, database writes)
Example scenarios:
- Keep only contacts with valid email format
- Filter orders to only those with status “paid”
- Remove test transactions before generating reports
- Keep only records updated in the last 24 hours
When to Choose If
Use the If node when:
- You need different processing for matching vs non-matching items
- Both outcomes should continue through the workflow
- You want to route items rather than remove them
- Failed conditions need error handling or logging
Example scenarios:
- VIP customers go to priority queue, others to standard
- Valid responses get processed, errors trigger alerts
- Approved items continue, pending items go to review workflow
- High-value orders get special handling, others follow standard path
Decision Flowchart
Ask yourself: “Do I need to do something with the items that do not match?”
- No → Use Filter node
- Yes → Use If node
If you find yourself adding a Filter node after an If node just to remove the false output items, you should have used Filter from the start.
Real-World Examples
Example 1: Keep Only Valid Email Addresses
Scenario: You receive a list of contacts and need to send emails only to those with valid email format.
Left Value: {{ $json.email }}
Operator: matches regex
Right Value: ^[^\s@]+@[^\s@]+\.[^\s@]+$
Items without valid email format are dropped before reaching your email send node, preventing delivery failures.
Example 2: Remove Empty or Null Records
Scenario: API returns a list where some items have empty required fields.
Condition 1:
Left Value: {{ $json.name }}
Operator: is not empty
Condition 2:
Left Value: {{ $json.id }}
Operator: exists
Combine: AND
Only items with both a non-empty name and an existing id field pass through.
Example 3: Filter High-Value Orders
Scenario: Process only orders above a certain threshold for priority handling.
Left Value: {{ $json.order_total }}
Operator: greater than
Right Value: 500
Orders of $500 or less are filtered out. Use Less Strict Type Validation if your order_total might be a string.
Example 4: Keep Items Within Date Range
Scenario: Filter records to only those created in the last 30 days.
Left Value: {{ new Date($json.created_at) }}
Operator: is after
Right Value: {{ new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }}
This calculates 30 days ago from now and keeps only items with a created_at date after that point.
Example 5: Filter by Array Contents
Scenario: Keep only products that are tagged as “featured”.
Left Value: {{ $json.tags?.includes("featured") ?? false }}
Operator: equals
Right Value: true
The optional chaining handles cases where tags might be undefined. The nullish coalescing provides a default of false if tags is missing.
Pro Tips and Best Practices
1. Name Your Filter Nodes Descriptively
Instead of “Filter” or “Filter1”, use names that describe what the filter does:
- “Keep Valid Emails”
- “Orders Over $500”
- “Active Customers Only”
- “Remove Test Data”
This makes workflows self-documenting and debugging much easier.
2. Test with Edge Cases
Before connecting to production systems, test your Filter node with:
- Values that should pass (verify they continue through)
- Values that should fail (verify they get dropped)
- Edge cases (empty strings, null values, zero, boundary values)
- Data type variations (numbers as strings, different date formats)
Pin data from previous tests to iterate on conditions without re-triggering upstream nodes.
3. Handle Null Values Explicitly
Do not assume data will always be present. Protect your expressions:
// Instead of assuming field exists:
{{ $json.amount > 100 }}
// Check first or provide default:
{{ ($json.amount ?? 0) > 100 }}
This prevents undefined errors from crashing your filter conditions.
4. Use Expressions for Dynamic Thresholds
Rather than hardcoding values, reference configuration from earlier nodes:
{{ $json.order_total > $('Settings').first().json.minOrderValue }}
This makes filters adjustable without editing the node directly.
5. Chain Filters for Complex Logic
When you need AND and OR conditions together, chain multiple Filter nodes:
Filter 1 (AND logic): status = "active" AND type = "customer"
↓
Filter 2 (OR logic): region = "US" OR region = "CA"
This provides clearer logic than trying to combine everything in one complex expression.
6. Monitor Filtered vs Passed Items
In production, track how many items get filtered out. If you expect 10% to fail but 90% are getting filtered, something is wrong with either your conditions or your data quality.
Add a Set node before the filter to count items, or use n8n’s execution logs to review input vs output counts.
Our workflow development services can help design robust filtering logic for complex data quality requirements. For strategic guidance on workflow architecture, explore our consulting services.
Frequently Asked Questions
Why does my Filter node remove everything even when items 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 "150" (a string) and you compare with greater than 100 (a number), strict type checking fails. Enable Less Strict Type Validation in the Filter node options to allow automatic type conversion. Alternatively, explicitly convert types in your expression using Number($json.amount) or String($json.id). Also verify you are referencing the correct field name by examining the actual input data in the node’s input panel.
How do I filter items where a field exists and is not empty?
You need two conditions with AND logic. First condition: select the field, use the exists operator to check if the field is present. Second condition: select the same field, use is not empty to check it has content. Set the combine dropdown to AND. This catches three problem states: fields that are missing entirely, fields that are null, and fields that are present but empty strings. For stricter checking including null values, use an expression like {{ $json.field !== null && $json.field !== undefined && $json.field !== "" }} with equals true.
Can I use regex patterns in Filter node conditions?
Yes. Select String as the data type and use either matches regex or not matches regex as the operator. Enter your regex pattern in the right value field without surrounding slashes. For example, to match email addresses use ^[^\s@]+@[^\s@]+\.[^\s@]+$. For phone numbers with country code, use ^\+\d{1,3}\d{10}$. The regex engine follows JavaScript regular expression syntax. Test complex patterns using an online regex tester before adding them to your workflow.
How do I filter items based on array contents?
The Filter node filters workflow items, not elements within arrays. To filter based on array contents, use an expression that returns a boolean. For checking if an array includes a value: {{ $json.tags.includes("priority") }} equals true. For checking array length: {{ $json.items.length }} greater than 0. For checking if any element meets a condition: {{ $json.orders.some(o => o.status === "pending") }} equals true. Use optional chaining for safety: {{ $json.tags?.includes("urgent") ?? false }}. If you need to filter elements within an array itself rather than filtering items, use the Code node with JavaScript array methods like filter() and map().
When should I use Filter node instead of If node for conditional logic?
Use the Filter node when you want to keep only matching items and discard the rest. The non-matching items disappear from the workflow. Use the If node when you need both outcomes to continue down different paths. With If, matching items go to the true output and non-matching items go to the false output, allowing different processing for each. A simple test: ask “Do I need to do something with the items that do not match?” If no, use Filter. If yes, use If. Common Filter scenarios: data validation before API calls, removing test data, keeping records within date ranges. Common If scenarios: routing VIPs differently, handling success vs error responses, processing approved vs pending items differently. See our n8n workflow best practices guide for more architectural guidance.