# Configuration (/docs/client/tanstack-query/config)

---
title: Configuration
description: The optional second argument to createQuestpieQueryOptions sets the key prefix, the error mapping and a pinned locale or stage for the whole proxy.
kind: reference
package: "@questpie/tanstack-query"
---

| Option      | Type                          | Default            | Effect                           |
| ----------- | ----------------------------- | ------------------ | -------------------------------- |
| `keyPrefix` | `QueryKey`                    | `['questpie']`     | Goes on the front of every key   |
| `errorMap`  | `(error: unknown) => unknown` | coerces to `Error` | Wraps every fetcher and mutator  |
| `locale`    | `string`                      | `undefined`        | Pins a locale. See below         |
| `stage`     | `string`                      | `undefined`        | Pins a workflow stage. See below |

Pass nothing and you get all four defaults. That is what the starters do.

```ts title="src/lib/query.ts"
export const q = createQuestpieQueryOptions(client, {
	keyPrefix: ["cms"],
	locale: "sk",
});
```

## keyPrefix

Every query and mutation key starts with it. Pass `[]` and keys start at
`'collections'` with nothing in front. `q.key(parts)` uses the same prefix, so
invalidation keeps working whatever you set. See
[Query keys](/docs/client/tanstack-query/keys).

## errorMap

The default coerces a non-`Error` throw into an `Error`. An `Error` passes
through, a string becomes `new Error(string)`, and anything else becomes
`new Error("Unknown error")`.

It wraps every fetcher and every mutator on collections, globals, routes and
`q.custom`. The stream behind a live query goes through it too. Whatever it
returns is what your `onError` and your error boundary receive. Replace it once
and the whole app agrees on an error shape.

Channels are the exception. `subscription()` and `presence()` build their
`queryFn` straight from the channel iterator, so an error there arrives
unmapped.

```ts
export const q = createQuestpieQueryOptions(client, {
	errorMap: (error) => {
		if (error instanceof MyApiError) return error;
		return new Error("Request failed", { cause: error });
	},
});
```

<Callout type="info" title="A non-retryable realtime error stays non-retryable">
	Live queries re-mark the mapped error when your map drops the flag, so a
	rejected topic still fails fast. Return the error you were given and nothing
	is re-marked.
</Callout>

## locale and stage

These two are pinned for the whole proxy. Both always land in the `locale` and
`stage` slots of every collection and global key. So two proxies with
different locales never share a cache entry.

Where they go beyond the key depends on the builder.

| Builder                                               | What it does with them                           |
| ----------------------------------------------------- | ------------------------------------------------ |
| `collections.find` / `count` / `findOne`              | key only, the call does not receive them         |
| `collections.findVersions`                            | passed as the call's second argument             |
| every collection write                                | passed as the call's second argument             |
| `globals.get`                                         | key only, the call does not receive them         |
| `globals.update` / `revertToVersion` / `findVersions` | merged into the call's options                   |
| `globals.transitionStage`                             | `locale` merged, `stage` not                     |
| routes and channels                                   | routes key on `locale` only, channels on neither |

<Callout type="warn" title="A pinned locale does not translate a plain read">
	`find`, `count`, `findOne` and `globals.get` send only the options you pass.
	Put `locale` in the read options when you want a localized read. The config
	value still splits the cache, which is what you want.
</Callout>

The merge on the global writes happens after your own options, so a pinned
value wins over a per-call one. If two parts of your UI need different
locales, build a second proxy rather than fighting the override.

## Custom queries and mutations

`q.custom` wraps a function of yours with the same prefix and the same error
map. Use it for a third-party endpoint or a composed call.

```ts
const opts = q.custom.query<DashboardData>({
	key: ["dashboard", "summary"],
	queryFn: () => fetchDashboard(),
});

const mut = q.custom.mutation<{ id: string }, void>({
	key: ["dashboard", "refresh"],
	mutationFn: (vars) => refreshDashboard(vars.id),
});
```

You give the suffix and the factory prepends `keyPrefix`. No `locale` or
`stage` slots are interleaved, so the suffix you write is the whole rest of
the key.

## Exported types and helpers

```ts
import type {
	QuestpieQueryOptionsProxy, // the shape of `q`
	QuestpieQueryOptionsConfig, // this page's argument
	QuestpieQueryErrorMap, // (error: unknown) => unknown
	RealtimeQueryConfig, // the live second argument
} from "@questpie/tanstack-query";
```

The package re-exports four React Query types: `QueryKey`, `DefaultError`,
`UseQueryOptions` and `UseMutationOptions`. It also re-exports five realtime
helpers from `questpie/client`. Those are `buildCollectionTopic`,
`buildGlobalTopic`, `applyRealtimeFindEvent`, `applyRealtimeScalarEvent` and
`applyRealtimeSingleEvent`, plus the `RealtimeAPI` and `TopicConfig` types.

You rarely name `QuestpieQueryOptionsProxy` yourself. Export `typeof q` from
`src/lib/query.ts` instead, which is what the starters do.

<Callout type="info" title="The proxies do not enumerate">
	`q.collections`, `q.globals`, `q.channels` and `q.routes` are `Proxy` objects
	with a get trap. `Object.keys()`, spreading and `for…in` give you nothing.
	Reach for a name. `q.custom` and `q.key` are plain.
</Callout>
