n8n Filter Node Explained: Keep Only the Data You Need (2025)
Most n8n workflows fail not because a node breaks, but because the wrong data slips through. You pull 300 orders and only need the paid ones. You sync 1,000 contacts but only want the ones with an email. You process support tickets but only care about the high-priority ones. If you send everything downstream, you waste API calls, trigger duplicate emails, and pollute your reports. The n8n Filter node is the single cleanest way to solve this: it keeps only the items that meet your conditions and quietly drops the rest.
This guide explains exactly what the Filter node does, how it differs from IF and Switch, how to build conditions the right way, and the type gotchas that quietly break real workflows.
What the n8n Filter node actually does
The Filter node takes the list of items coming into it and evaluates each one against the conditions you set. Items that match pass through the single output. Items that don't match are removed from the run entirely. There is no second branch, no "false" path, no error — the non-matching items simply stop existing downstream.
That mental model matters. Filter is a gate, not a fork. If 50 items come in and 12 match, 12 items leave. Everything after the Filter node only ever sees clean, qualifying data.
Filter vs IF vs Switch: choosing the right node
These three nodes look similar in the conditions builder, but they behave very differently, and picking the wrong one is one of the most common n8n mistakes.
- Filter — one output. Matching items pass; non-matching items are dropped. Use it when you don't care about the rejected items at all. Example: "only continue with paid orders."
- IF — two outputs, true and false. Every item goes somewhere. Use it when you need to do something different with each group. Example: paid orders get a receipt, unpaid orders get a reminder email.
- Switch — many outputs based on rules or a value. Use it to route into three or more paths. Example: send tickets to different queues by priority (low / medium / high / urgent).
Rule of thumb: if you're about to use an IF node and leave the false branch empty, you actually wanted a Filter node. It's cleaner and makes your intent obvious to anyone reading the workflow.
Setting conditions in the Filter node
Inside the node you add one or more conditions. Each condition has three parts: a left value (usually an expression pulling a field), an operator, and a right value to compare against.
The condition editor is typed. You first pick the data type — String, Number, Boolean, Date & Time, or Array/Object — and the available operators change to match. Common ones include:
- String: equals, not equals, contains, starts with, ends with, matches regex, is empty, exists
- Number: equals, larger, larger or equal, smaller, smaller or equal
- Boolean: is true, is false
- Array: contains, length equals, is empty
AND vs OR combinators
When you add more than one condition, you choose how they combine:
- AND — the item must satisfy every condition. "status equals paid and amount larger than 0."
- OR — the item passes if any condition is true. "priority equals high or tag contains vip."
Using expressions in conditions
The left value is almost always an expression. To read a field from the incoming item you use n8n's expression syntax, for example {{ $json.status }} or {{ $json.customer.email }}. You can also compute values inline: {{ $json.total > 100 }} or normalize case with {{ $json.status.toLowerCase() }} before comparing. Expressions are what make the Filter node flexible enough for real data instead of tidy demo data.
Go further than filtering: Filtering cleans your data — but the real value is what you do with it next. Tie the analytics agent (it turns filtered data into insights) to your content and reporting workflows so clean data becomes automatic business intelligence instead of another spreadsheet. Get the Data Analytics AI Agent on Gumroad →
Real-world examples
1. Keep only paid orders
Data type String, left value {{ $json.payment_status }}, operator equals, right value paid. Everything unpaid, refunded, or pending is dropped before you generate invoices.
2. Keep only leads with an email
Data type String, left value {{ $json.email }}, operator exists (or is not empty). This prevents your email node from erroring on blank recipients — one of the top causes of failed runs.
3. Keep only high-priority tickets
Two conditions with OR: {{ $json.priority }} equals high, or {{ $json.priority }} equals urgent. Only the tickets that need a human right now continue to the Slack alert.
4. Dedupe by a flag
If an earlier step tagged already-processed records, filter {{ $json.already_sent }} Boolean is false. Only fresh records move forward, so you never double-send.
A small before/after example
Before the Filter node — a webhook returns five orders:
- Order 1001 — status: paid, total: 49
- Order 1002 — status: pending, total: 20
- Order 1003 — status: paid, total: 0
- Order 1004 — status: refunded, total: 80
- Order 1005 — status: paid, total: 120
Filter conditions (AND): {{ $json.status }} equals paid AND {{ $json.total }} (Number) larger than 0.
After the Filter node — only two items pass:
- Order 1001 — paid, 49
- Order 1005 — paid, 120
Order 1002 is pending, 1003 has a zero total, and 1004 is refunded — all dropped. The receipt node downstream now runs exactly twice, on exactly the right orders.
Gotchas that silently break filters
- Type mismatches. If you compare a Number field using a String condition, "120" and 120 may not match as expected. Always set the data type to match the real field. Numbers arriving as text from APIs are the #1 offender — cast with {{ Number($json.total) }}.
- Empty vs null vs missing. "is empty" and "exists" are not the same. A field that exists but holds an empty string "" passes "exists" but fails "is not empty." Know which state your data is actually in.
- Case sensitivity. String comparisons are case-sensitive. "Paid" does not equal "paid". Normalize with {{ $json.status.toLowerCase() }} on the left value.
- Whitespace. A trailing space ("paid ") fails an equals check. Use .trim() in the expression when data comes from forms or spreadsheets.
- Everything got dropped. If zero items pass, your conditions are probably too strict or the field path is wrong. Pin the incoming JSON and confirm the exact key names before blaming the operator.
Best practices
- Filter as early as possible in the workflow so downstream nodes process less data and run faster.
- Prefer Filter over an IF node with an empty false branch — it reads more clearly.
- Rename the node to describe the rule ("Only paid orders") so the workflow documents itself.
- Normalize case and whitespace in the expression, not with extra nodes.
- When a Filter can output zero items, make sure the next steps handle an empty list gracefully.
Master the Filter node and half of your "why did this run send twice" and "why did this API call fail" problems disappear. Clean input is the foundation of every reliable automation.
Ready to automate? Get this template on Gumroad →