Skip to content

[Due for payment 2026-08-07] Add filter negation (is not / doesn't have) to the Spend filter UI #93208

Description

@JmillsExpensify

Background

When admins narrow down expenses on the Spend page, they might want to express an exclusion rather than an inclusion: "show me everything that isn't on Adventure Inc," "everything that doesn't have a receipt," "every merchant except Big Buy Supply." Today the filter UI only lets users build positive matches, so to answer an exclusion question they have to either reason in reverse (select every value they do want) or drop into raw search syntax. Both are slow, and the second is invisible to most users.

The good news is that negation is already supported by search. This is purely a front-end initiative: we're exposing a control that lets users build a negated filter from the UI, and reflecting that negation in the resulting filter chip. Nothing about the underlying search syntax or back end changes.

We use is not for value-based filters (Merchant, Workspace, etc.) and doesn't have for presence-based filters (the Has filter), because that's the language that reads naturally for each filter type — you say a merchant "is not" something, but a receipt is something an expense "doesn't have."

Image Image

The full Figma file for this initiative can be found here.

Scope

  • Add a negation toggle to the filter configuration UI for the supported filters only. No new search capability — negation already works in search syntax.
  • This is front-end only. No changes to search syntax, query parsing, or the back end.
  • Supported filters in this release: To, From, Has, Workspace, Status, Currency, Purchase currency, Merchant, Exported to. These are where we're seeing customer demand. Other filters keep their current behavior and can be added later.
  • Reflect the negated state in the filter chip label.

Product and design details

Negation toggle

Each supported filter's configuration surface gains a segmented toggle at the top that flips the filter between its positive and negated form:

  • Value-based filters (To, From, Workspace, Status, Currency, Purchase currency, Merchant, Exported to) use an is / is not toggle.
  • Presence-based filters (Has) use a has / doesn't have toggle.

The toggle defaults to the positive option (is / has). The user picks their value(s) as they do today; the only addition is the toggle that determines whether the selection is matched or excluded.

Filter chip

The chip that appears in the filter bar reflects the negation:

  • Positive: Has: Receipt, Merchant is: Big Buy Supply, Workspace is: Adventure Inc
  • Negated: Doesn't have: Receipt, Merchant is not: Big Buy Supply, Workspace is not: Adventure Inc

Each chip retains its existing dropdown caret (to reopen and reconfigure, including flipping the toggle) and its individual remove (×) affordance. The global Clear action continues to clear all filters.

Combining filters

Negated filters combine with other filters exactly as positive ones do today. For example, Doesn't have: Receipt + Workspace is not: Adventure Inc returns expenses that both lack a receipt and are not on Adventure Inc. There is no special interaction between negation and combination — a negated filter is just another active filter.

Examples

  • A user opens the Has filter, leaves the toggle on doesn't have, and checks Receipt. The chip reads Doesn't have: Receipt and the list shows only expenses without a receipt.
  • A user opens the Merchant filter, flips the toggle to is not, and selects Big Buy Supply. The chip reads Merchant is not: Big Buy Supply and the list excludes those expenses.
  • A user applies Doesn't have: Receipt, then adds a Workspace filter set to is not: Adventure Inc. Both chips appear, and the list reflects both exclusions.
  • A user reopens a negated chip via its caret and flips the toggle back to is / has; the chip and results update to the positive form.
Issue OwnerCurrent Issue Owner: @JS00001

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionDailyKSv2InternalRequires API changes or must be handled by Expensify staffNewFeatureSomething to build that is a new item.

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions