Filters and Matching

Purpose and Scope

Filters are the small matching objects TanStack Query uses when an operation needs to address more than one cached entry. A single query is identified by its query key, but application workflows often need to invalidate a group, count active background requests, remove cached data, inspect mutations, or refetch everything under a resource prefix. This page explains that matching model from the perspective of developers using TanStack Query in framework adapters, with Angular examples where the supplied repository docs provide concrete setup and usage evidence.

The important mental model is that query keys are the cache contract. A key describes the resource, inputs, filters, and scope of a server-state read. Matching APIs build on that contract: broad filters can target all queries, prefix filters can target a resource family, exact filters can target one entry, and predicates can express custom logic. The official product framing emphasizes that keys let reads, writes, invalidation, prefetching, and devtools speak the same language, which is why consistent key design matters before any filtering strategy can be reliable.

Sources: docs/framework/angular/guides/background-fetching-indicators.md, docs/framework/angular/devtools.md

Relevant Source Files

  • docs/framework/angular/reference/functions/provideTanStackQuery.md - Shows how Angular applications install a QueryClient with provideTanStackQuery, which is the client object whose cache operations and default options make filter-driven workflows possible.
  • docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md - Demonstrates an Angular injectQuery call with a concrete queryKey and queryFn, grounding how cache identity is created before matching can happen.
  • docs/framework/angular/devtools.md - Describes Angular Query Devtools as a way to debug and inspect queries and mutations, which is the primary visual feedback loop for validating filter matches.
  • docs/framework/angular/guides/background-fetching-indicators.md - Shows query-level isFetching() and global injectIsFetching(), illustrating how matching can drive background-fetch indicators rather than only cache writes.
  • docs/community-resources.md - Lists Query Key Factory as a utility for creating typesafe standardized query keys useful for cache management.
  • docs/config.json - Places Angular, React, Vue, Solid, Svelte, and Lit documentation in the official docs structure, reinforcing that filters are a shared Query concept surfaced through framework-specific APIs.

Core Matching Concepts

A query filter is best understood as a selector for cached queries. It can be broad, such as a global background-fetch count; scoped, such as all queries whose keys begin with ['todos']; or exact, such as one query for ['todos', { status: 'done' }]. Matching is not a replacement for good key design. It depends on it. If keys mix unrelated concerns or omit important inputs, invalidation and refetching become either too broad or too narrow, and UI state can appear stale even though the cache is behaving consistently with the key it was given.

Mutation filters apply the same idea to mutation records instead of query records. They are useful when a UI needs to know whether a mutation is pending, when a devtool panel needs to display mutation history, or when code coordinates optimistic updates and invalidation after a successful write. TanStack Query treats mutations as a real lifecycle rather than a one-off promise, so matching pending or failed mutations is part of building a predictable write workflow. Devtools documentation explicitly calls out debugging and inspecting both queries and mutations, which is where mismatched keys or overly broad invalidations are easiest to notice.

Sources: docs/framework/angular/devtools.md, docs/community-resources.md

System-to-Code Mapping

Angular setup starts by providing a QueryClient. The provideTanStackQuery(queryClient, ...features) function returns Angular providers and accepts either a QueryClient instance or an InjectionToken that provides one. That client owns the query and mutation caches used by adapter functions. Optional features such as withDevtools() can be attached at the same provider boundary, so the same cache that receives queries and mutations can also be inspected during development.

The Angular HttpClient guide demonstrates the next layer: an adapter-level query declaration. The example creates injectQuery(() => ({ queryKey: ['repoData'], queryFn: () => lastValueFrom(this.http.get(...)) })). The fetching client is intentionally promise-oriented and backend-agnostic; HttpClient observables are converted with lastValueFrom or firstValueFrom. For filters, the key detail is that the cache entry is named by ['repoData']. Later invalidation, refetch, counting, and inspection operations can only match the query because that key was declared consistently.

The background fetching guide shows how matching affects UI without directly writing cache data. A component-level query exposes todosQuery.isFetching() so the template can render Refreshing... while an already successful query is fetching again. A separate global indicator uses injectIsFetching() and displays a message when any query is fetching in the background. In full TanStack Query usage, that same family of status selectors can be scoped with filters, allowing teams to show global, route-level, or resource-level activity indicators based on the same cache state.

Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md, docs/framework/angular/angular-httpclient-and-other-data-fetching-clients.md, docs/framework/angular/guides/background-fetching-indicators.md

Filter Reference

The public filter vocabulary is intentionally compact. Query filters commonly include queryKey to match by key prefix, exact to require the full key rather than a prefix, type to distinguish active, inactive, or all queries, stale to match freshness state, fetchStatus to match fetching, paused, or idle execution, and predicate for custom matching. Mutation filters similarly use mutationKey, exact, status, and predicate to select mutation records. These fields are used by higher-level APIs such as invalidation, refetching, cache lookup, removal, background-fetch counters, and mutation-state selectors.

Filter targetCommon fieldsTypical use
QueriesqueryKey, exact, type, stale, fetchStatus, predicateInvalidate, refetch, remove, count, or inspect cached reads
MutationsmutationKey, exact, status, predicateInspect pending writes, coordinate optimistic UI, or show mutation activity
Background indicatorsQuery filters, often narrowed by queryKey or fetchStatusShow fetching state globally, per route, or for one resource family
Devtools inspectionQuery keys and mutation recordsVerify which entries exist and whether matching logic is too broad or too narrow

Prefer prefix filters for resource families and exact filters for single entries. For example, ['todos'] can represent the whole todo resource family, while ['todos', { status: 'open' }] represents one filtered list. If a mutation changes any todo, invalidating the prefix is often correct. If a form edits only one detail view, an exact key or a more specific prefix may avoid unnecessary refetches. Query Key Factory appears in the community resources as a utility for creating typesafe standardized query keys, which is especially helpful when many files need to share the same matching conventions.

Sources: docs/community-resources.md, docs/framework/angular/guides/background-fetching-indicators.md

Execution Flow

A filter-driven workflow usually begins at read time, not at invalidation time. First, declare stable query keys next to the query functions that fetch the data. In Angular, that means using injectQuery with a queryKey and a promise-returning queryFn; the supplied HttpClient example converts an Angular observable to a promise before returning it. Second, mount the application with a shared QueryClient so all adapter functions talk to the same cache. Third, add UI indicators or write workflows that select cache entries by key, status, freshness, or custom predicates.

When a mutation succeeds, the usual goal is not to manually synchronize every visible component. Instead, invalidate or refetch the affected query family and let observers update through the cache. Background indicators then communicate that the UI is refreshing rather than performing a first load. The Angular guide’s todosQuery.isPending(), todosQuery.isError(), todosQuery.isSuccess(), and todosQuery.isFetching() branches show this distinction clearly: pending is the initial load path, while fetching inside a successful state is a background update path. Filters let that distinction scale from one component to a whole application shell.

Devtools close the loop. The Angular devtools guide says the tools help debug and inspect queries and mutations, and it wires them through provideTanStackQuery(new QueryClient(), withDevtools()). Use that panel to confirm that the keys you expect are present, that active and inactive queries are separated as expected, and that invalidations are not matching unrelated resources. In production-sensitive Angular builds, the same guide explains that devtools are excluded by default and can be loaded through a production subpath with explicit loadDevtools control.

Sources: docs/framework/angular/devtools.md, docs/framework/angular/reference/functions/provideTanStackQuery.md, docs/framework/angular/guides/background-fetching-indicators.md

Practical Guidance and Next Steps

Design keys from the outside in. Start with the resource name, add stable input objects for filters or identifiers, and avoid placing non-serializable or unstable values in positions that later matching code must compare. Use broad prefixes for invalidating resource families, exact matches for single records, and predicates only when structural key matching cannot express the rule clearly. If a team repeatedly writes the same key shapes by hand, consider a key factory pattern so query declarations, invalidations, prefetches, and tests import the same source of truth.

For Angular readers, the next practical step is to verify the provider and query setup first: install a QueryClient with provideTanStackQuery, declare queries with clear queryKey values, and enable withDevtools() during development. Then add background indicators using query status or injectIsFetching() before introducing more advanced invalidation. For broader context, read the query keys, invalidations from mutations, background refetching, devtools, and framework adapter overview pages so matching rules are connected to the full server-state lifecycle rather than treated as isolated cache utilities.

Sources: docs/framework/angular/reference/functions/provideTanStackQuery.md, docs/framework/angular/devtools.md, docs/config.json