# Query keys and filters

> How tRPC's query keys are shaped, the filters that invalidate and read them, and keyPrefix for two APIs in one QueryClient.

A query key starts with the procedure's path as an array, then its input and what made it: `[['post', 'byId'], { input: { id: '1' }, type: 'query' }]`. The `type` is `query`, `infinite`, `streamed` or `live`, or `mutation` for a streamed mutation's chunks. A mutation key is the path alone, `[['post', 'create']]`. Since the path is an array, `[['post']]` partially matches every key under `post`.

You rarely write a key: each helper makes its own, and has a function that returns it.

| Helper                                                    | Key or filter for                                                                |
| --------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `trpc.pathKey()`, `trpc.post.pathKey()`                   | everything under a path: queries, infinite, streamed and live queries, mutations |
| `trpc.post.pathFilter(filters?)`                          | the same, as filters for `invalidateQueries`, `cancelQueries`, `removeQueries` … |
| `trpc.post.byId.queryKey({ input })`                      | the exact key `queryOptions()` uses, typed for `getQueryData`                    |
| `trpc.post.byId.queryFilter({ input? })`                  | a procedure's queries, every input or one                                        |
| `trpc.post.list.infiniteKey({ input, initialPageParam })` | the exact key `infiniteOptions()` uses                                           |
| `trpc.post.create.mutationKey()`, `mutationFilter()`      | a procedure's mutations, for `useIsMutating` and `useMutationState`              |
| `trpc.x.streamedKey()`, `trpc.x.liveKey()`                | a subscription's query-cache forms                                               |

## Invalidate and update

```ts
// every post query, whatever its procedure or input
await queryClient.invalidateQueries(trpc.post.pathFilter());
// post.byId for one input
await queryClient.invalidateQueries(
  trpc.post.byId.queryFilter({ input: { id } }),
);

// typed by the key: the procedure's output
const key = trpc.post.byId.queryKey({ input: { id } });
const post = queryClient.getQueryData(key);
// const post: {
//     id: string;
//     title: string;
//     author: string | null;
//     createdAt: string;
// } | undefined
queryClient.setQueryData(key, (old) =>
  old ? { ...old, title: 'Renamed' } : old,
);
```

## Two APIs, one QueryClient

Keys start with the procedure path, so two tRPC APIs with a `post.byId` would share cache entries. Give one a `keyPrefix`:

```ts
const billing = createTrpcQueryUtils({
  client: billingClient,
  keyPrefix: 'billing',
});
billing.post.byId.queryKey({ input: { id } }); // [['billing', 'post', 'byId'], { input: { id }, type: 'query' }]
billing.pathFilter(); // matches only billing's queries
```

Every framework's `createTrpcContext({ keyPrefix: 'billing' })` and `createTrpcRsc({ keyPrefix })` take it too; the server and browser must use the same one.

## Next.js cache tags

`cacheTags(key)` and `revalidationTag(key)` turn a key into Next.js data-cache tags, so a mutation can revalidate exactly what a query key covers ([React Server Components](https://v12.trpc.io/docs/integrations/rsc.md#nextjs-data-cache)).
