# Mutations (/docs/client/tanstack-query/mutations)

---
title: Mutations
description: Every write builder on the query-options proxy, the variables its mutate() call takes, and what it resolves to.
kind: reference
package: "@questpie/tanstack-query"
---

Each builder takes no arguments. It returns `mutationOptions()`, and the
variables below go to `mutate()`.

## Collections

| Builder             | `mutate(variables)`                      | Resolves to           |
| ------------------- | ---------------------------------------- | --------------------- |
| `create()`          | the create input                         | the new row           |
| `update()`          | `{ id, data }`                           | the updated row       |
| `delete()`          | `{ id }`                                 | `{ success, data }`   |
| `restore()`         | `{ id }`                                 | the restored row      |
| `purgeById()`       | `{ id }`                                 | `{ success: true }`   |
| `updateMany()`      | `{ where, data }`                        | the rows it wrote     |
| `updateBatch()`     | `{ updates: [{ id, data }] }`            | the rows it wrote     |
| `deleteMany()`      | `{ where }`                              | `{ success, count }`  |
| `revertToVersion()` | `{ id, version }` or `{ id, versionId }` | the reverted row      |
| `transitionStage()` | `{ id, stage, scheduledAt? }`            | the row at that stage |

```ts
const create = useMutation(q.collections.posts.create());
create.mutate({ title: "Hello", slug: "hello" });

const update = useMutation(q.collections.posts.update());
update.mutate({ id: postId, data: { published: true } });

const remove = useMutation(q.collections.posts.delete());
remove.mutate({ id: postId });
```

The create input is your insert shape, nested relation writes included. The
`data` in `update()` is the patch, not the whole row.

### By id, or by filter

`update()` and `delete()` are the single-record calls. They take an `id` and
nothing else identifies the row. To write by filter, reach for the bulk pair.

```ts
const publishAll = useMutation(q.collections.posts.updateMany());
publishAll.mutate({ where: { published: false }, data: { published: true } });
```

`where` is the same typed filter `find()` takes, so a column typo does not
compile. `updateMany()` resolves to the array of rows it wrote, and
`deleteMany()` resolves to `{ success, count }`.

`updateBatch()` is the one that writes different data per row. It takes
`{ updates: [{ id, data }] }` and returns the written rows.

### Soft delete

`restore()` undoes a soft delete and gives you the row back. `purgeById()`
removes an already-deleted row for good, and it is a separate builder because
the server authorizes it separately. It rejects rows that are still active.

<Callout type="info" title="`purgeById` exists only on soft-delete collections">
	The builder is on the type when the collection sets `softDelete: true`. On a
	hard-delete collection it is not there, and reaching for it does not compile.
</Callout>

### Versions and stages

`revertToVersion()` takes `{ id, version }` or `{ id, versionId }` and writes
that past state back. `transitionStage()` moves one row to another workflow
stage without touching its data. A future `scheduledAt` schedules the move
instead of running it now.

Both need the feature turned on in `.options()`. See
[Options](/docs/schema/collections/options).

## Globals

The global builders forward both client arguments, so the variables object
carries the payload and the call options side by side. That means one more
level of nesting than the collection builders have.

| Builder             | `mutate(variables)`    | Resolves to              |
| ------------------- | ---------------------- | ------------------------ |
| `update()`          | `{ data, options? }`   | the updated global       |
| `revertToVersion()` | `{ params, options? }` | the reverted global      |
| `transitionStage()` | `{ params, options? }` | the global at that stage |

```ts
const save = useMutation(q.globals.siteSettings.update());
save.mutate({ data: { siteName: "QUESTPIE" } });

const revert = useMutation(q.globals.siteSettings.revertToVersion());
revert.mutate({ params: { version: 3 } });
```

<Callout type="warn" title="The outer key is the envelope, not a field">
	`mutate({ siteName: "X" })` does not work. The fields go under `data`, and
	the version or stage arguments go under `params`. The optional `options` is
	the client's second argument, so `{ locale }` and `{ with }` belong there.
</Callout>

## Optimistic concurrency

Turn on `optimisticConcurrency` and the row version stops being optional. The
type demands it, so a forgotten `expectedRevision` fails to compile.

| Builder                                         | Where the version goes                          |
| ----------------------------------------------- | ----------------------------------------------- |
| `update`, `delete`, `restore`                   | `expectedRevision` beside `id`                  |
| `purgeById`                                     | `expectedRevision` beside `id`                  |
| `revertToVersion`, `transitionStage`            | `expectedRevision` beside `id`                  |
| `updateMany`, `deleteMany`                      | `expectedRevisions: [{ id, expectedRevision }]` |
| `updateBatch`                                   | `expectedRevision` on each entry                |
| a global `update`                               | `{ data: { data: fields, expectedRevision } }`  |
| a global `revertToVersion` or `transitionStage` | `expectedRevision` inside `params`              |

```ts
update.mutate({ id: postId, expectedRevision: 4, data: { title: "New" } });
```

Every collection row is flat. The version sits beside `id`, in the same object
you pass to `mutate()`.

The two global rows nest instead. The outer key is the mutation variable, and
the inner one is the client's own payload. That is why a global `update` shows
`data` twice, and it is not a typo. See
[Optimistic concurrency](/docs/schema/collections/optimistic-concurrency).

## Keys and invalidation

Mutation keys carry the operation but never the variables, so
`['questpie', 'collections', 'posts', 'create', locale, stage]` is the whole
key. Invalidate the reads yourself in `onSuccess`. See
[Query keys](/docs/client/tanstack-query/keys).

```ts
const create = useMutation({
	...q.collections.posts.create(),
	onSuccess: () =>
		queryClient.invalidateQueries({
			queryKey: q.key(["collections", "posts"]),
		}),
});
```
