diff --git a/apps/docs/content/docs/en/tools/table.mdx b/apps/docs/content/docs/en/tools/table.mdx
index 388005b772c..e7cf5993704 100644
--- a/apps/docs/content/docs/en/tools/table.mdx
+++ b/apps/docs/content/docs/en/tools/table.mdx
@@ -275,7 +275,11 @@ Filters use MongoDB-style operators for flexible querying:
| `$lte` | Less than or equal | `{"quantity": {"$lte": 10}}` |
| `$in` | In array | `{"status": {"$in": ["active", "pending"]}}` |
| `$nin` | Not in array | `{"type": {"$nin": ["spam", "blocked"]}}` |
-| `$contains` | String contains | `{"email": {"$contains": "@gmail.com"}}` |
+| `$contains` | String contains (case-insensitive) | `{"email": {"$contains": "@gmail.com"}}` |
+| `$ncontains` | Does not contain (case-insensitive; matches empty cells) | `{"email": {"$ncontains": "@spam.com"}}` |
+| `$startsWith` | Starts with (case-insensitive) | `{"name": {"$startsWith": "Dr."}}` |
+| `$endsWith` | Ends with (case-insensitive) | `{"file": {"$endsWith": ".pdf"}}` |
+| `$empty` | Cell is empty (`true`) or non-empty (`false`) | `{"phone": {"$empty": true}}` |
### Combining Filters
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx
index bd683c62515..12f5bb10fb8 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx
@@ -12,7 +12,7 @@ import {
} from '@/components/emcn'
import { ChevronDown, Plus } from '@/components/emcn/icons'
import type { Filter, FilterRule } from '@/lib/table'
-import { COMPARISON_OPERATORS } from '@/lib/table/query-builder/constants'
+import { COMPARISON_OPERATORS, VALUELESS_OPERATORS } from '@/lib/table/query-builder/constants'
import { filterRulesToFilter, filterToRules } from '@/lib/table/query-builder/converters'
const OPERATOR_LABELS = Object.fromEntries(
@@ -71,7 +71,9 @@ export function TableFilter({ columns, filter, onApply, onClose }: TableFilterPr
}, [])
const handleApply = useCallback(() => {
- const validRules = rulesRef.current.filter((r) => r.column && r.value)
+ const validRules = rulesRef.current.filter(
+ (r) => r.column && (r.value || VALUELESS_OPERATORS.has(r.operator))
+ )
onApply(filterRulesToFilter(validRules))
}, [onApply])
@@ -197,16 +199,20 @@ const FilterRuleRow = memo(function FilterRuleRow({
- onUpdate(rule.id, 'value', e.target.value)}
- onKeyDown={(e) => {
- if (e.key === 'Enter') onApply()
- }}
- placeholder='Enter a value'
- className='h-[28px] flex-1 rounded-[5px] border border-[var(--border)] bg-transparent px-2 text-[var(--text-secondary)] text-xs outline-none placeholder:text-[var(--text-subtle)] hover-hover:border-[var(--border-1)] focus:border-[var(--border-1)]'
- />
+ {VALUELESS_OPERATORS.has(rule.operator) ? (
+
+ ) : (
+ onUpdate(rule.id, 'value', e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') onApply()
+ }}
+ placeholder='Enter a value'
+ className='h-[28px] flex-1 rounded-[5px] border border-[var(--border)] bg-transparent px-2 text-[var(--text-secondary)] text-xs outline-none placeholder:text-[var(--text-subtle)] hover-hover:border-[var(--border-1)] focus:border-[var(--border-1)]'
+ />
+ )}