> ## Documentation Index
> Fetch the complete documentation index at: https://archie.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Mutations

> Create, update, and delete records through the auto-generated GraphQL mutations. Includes bulk operations and atomic upserts.

Mutations write data through the GraphQL API. Every table in the [Data Model](/docs/features/backend/data-model/overview) generates a fixed set of mutations automatically — you don't write resolvers or input types by hand.

The examples assume a `students` table with fields like `firstName`, `lastName`, `email`, `age`, and a `city` relationship.

## Auto-generated mutations per table

For each table, Archie generates:

| Mutation                | What it does                                                                                       |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| `create<TableName>`     | Insert a single record.                                                                            |
| `create<TableName>Many` | Insert multiple records in one request.                                                            |
| `update<TableName>`     | Update a single record by id.                                                                      |
| `update<TableName>Many` | Update multiple records by filter, ids, or all — one atomic transaction.                           |
| `delete<TableName>`     | Delete a single record by id.                                                                      |
| `delete<TableName>Many` | Delete multiple records by id.                                                                     |
| `upsert<TableName>`     | Insert or update by a unique field — atomic. Generated for tables with at least one unique column. |

<img src="https://mintcdn.com/archie-e998dbf6/GdaYz5W-YpQoJXsQ/features/backend/graphql-api-explorer/api-explorer-ide-overview.png?fit=max&auto=format&n=GdaYz5W-YpQoJXsQ&q=85&s=11a26f002a033e61af42aea755856dfd" alt="GraphQL mutations overview in API Explorer" width="1603" height="976" data-path="features/backend/graphql-api-explorer/api-explorer-ide-overview.png" />

## Single record mutations

### Create

```graphql theme={null}
mutation {
  createStudents(
    input: {
      firstName: "John"
      lastName: "Doe"
      email: "john.doe@example.com"
      age: 24
      city: "2900562f-d036-486d-be98-9ebf064c27fe"
    }
  ) {
    id
    firstName
    email
    city {
      id
      nameCity
    }
  }
}
```

The `input` object's shape comes directly from the table's schema. Mandatory fields without a default must be present; optional fields can be omitted.

### Update

```graphql theme={null}
mutation {
  updateStudents(
    id: "2685ec12-a4c7-491d-a155-d0b09190993b"
    input: { age: 23 }
  ) {
    id
    firstName
    age
  }
}
```

`update` is a partial update — only the fields in `input` change. Everything else stays as it was.

### Delete

```graphql theme={null}
mutation {
  deleteStudents(id: "2685ec12-a4c7-491d-a155-d0b09190993b")
}
```

Returns `true` on success.

## Multiple record mutations

### Create many

```graphql theme={null}
mutation {
  createStudentsMany(
    inputs: [
      { firstName: "Michael", lastName: "Jones", email: "michael@example.com", age: 24 }
      { firstName: "William", lastName: "Miller", email: "william@example.com", age: 23 }
    ]
  ) {
    success
  }
}
```

`createStudentsMany` runs as a single transaction. If any record fails validation, none are inserted.

For larger or dynamic batches, pass the inputs as a variable so you can reuse the operation:

```graphql theme={null}
mutation CreateStudents($inputs: [StudentsCreateInput!]!) {
  createStudentsMany(inputs: $inputs) {
    success
  }
}
```

```json theme={null}
{
  "inputs": [
    { "firstName": "Michael", "email": "michael@example.com", "age": 24 },
    { "firstName": "William", "email": "william@example.com", "age": 23 }
  ]
}
```

### Update many

`update<TableName>Many` applies the same `data` to every matched row in a single atomic
transaction. You choose the rows with **exactly one** selector:

| Selector    | Selects                                                                          |
| ----------- | -------------------------------------------------------------------------------- |
| `filter`    | Rows matching an equality filter (all fields ANDed together). Must be non-empty. |
| `ids`       | An explicit list of ids. Ids that don't exist are skipped (not an error).        |
| `all: true` | Every row in the table. Must be passed explicitly.                               |

```graphql theme={null}
mutation {
  updateStudentsMany(
    filter: { age: 24 }
    data: { lastName: "Updated" }
  ) {
    success
    totalRequested
    totalSucceeded
    updatedIds
    rolledBack
    rollbackReason
  }
}
```

Update an explicit set of ids (the shift into a variable works the same as create many):

```graphql theme={null}
mutation {
  updateStudentsMany(
    ids: ["2685ec12-a4c7-491d-a155-d0b09190993b", "f47ac10b-58cc-4372-a567-0e02b2c3d479"]
    data: { age: 25 }
  ) {
    success
    totalSucceeded
    updatedIds
  }
}
```

Update every row (note the explicit `all: true` — there is no implicit "update everything"):

```graphql theme={null}
mutation {
  updateStudentsMany(all: true, data: { active: true }) {
    success
    totalSucceeded
  }
}
```

Key rules:

* **Exactly one selector.** Passing none, or more than one of `filter` / `ids` / `all`, is an error.
* **`filter` must be non-empty.** An empty filter is rejected — it is never treated as "update everything". Use `all: true` for that.
* **Always atomic (`ALL_OR_NOTHING`).** There is no best-effort mode and no `transactionMode` argument. If the update fails partway, nothing is changed.
* **`ids` is tolerant.** Ids that no longer exist are silently skipped: `totalRequested` counts the ids you sent, `totalSucceeded` counts the rows actually updated.
* **`updatedAt` is set automatically** on every updated row.
* **Business errors come back in the payload**, not as a top-level GraphQL error. On a rollback (e.g. a constraint violation), the mutation still returns a payload with `rolledBack: true` and `rollbackReason` — always check `success` / `rolledBack`.
* **`updatedIds`** lists the ids that were updated. (It is part of the shared batch result, so it is `null` on `create…Many` / `delete…Many` responses.)

### Delete many

```graphql theme={null}
mutation {
  deleteStudentsMany(
    ids: [
      "2685ec12-a4c7-491d-a155-d0b09190993b",
      "f47ac10b-58cc-4372-a567-0e02b2c3d479"
    ]
  )
}
```

## Nested mutations

Instead of pre-creating related records and passing their foreign keys, you can express the entire graph in one operation. Every relationship field accepts one of:

* `create` — insert a new related record inline.
* `connect` — link to an existing record by a unique field.
* `connectOrCreate` — find by a unique field; create if missing.

```graphql theme={null}
mutation {
  createDeal(
    input: {
      title: "Enterprise License"
      amount: 50000

      contact: {
        create: { firstName: "Juan", lastName: "Pérez", status: "LEAD" }
      }

      user: {
        connect: { id: "user-uuid-123" }
      }

      organization: {
        connectOrCreate: {
          where: { domain: "acme.com" }
          create: { name: "Acme Corp", domain: "acme.com" }
        }
      }
    }
  ) {
    id
    contact { id }
    organization { id }
  }
}
```

The whole nested mutation runs as one transaction. If any step fails, the entire operation rolls back — you won't end up with half-created records.

## Atomic upserts

Tables with at least one unique column get an `upsert<TableName>` mutation. Pass a `where` clause that targets the unique column, plus `create` and `update` payloads. Archie atomically checks if the record exists and runs the matching branch.

```graphql theme={null}
mutation {
  upsertContact(
    where: { email: "juan@example.com" }
    create: {
      firstName: "Juan"
      lastName: "Pérez"
      email: "juan@example.com"
      status: "LEAD"
    }
    update: {
      lastName: "Pérez Updated"
      status: "ACTIVE"
    }
  ) {
    id
    firstName
    lastName
    status
  }
}
```

Use `upsert` instead of "select then insert or update" — it eliminates the race condition between the two queries.

## Permissions

Every mutation is checked against the per-role permissions in [Role-Based Access](/docs/features/backend/app-services/role-based-access). A user without write access to a table will receive an authorization error before the mutation runs. Field-level write rules apply too — fields a role isn't allowed to write are rejected even if the rest of the input is valid.

## FAQ

<AccordionGroup>
  <Accordion title="What's the difference between `update` and `upsert`?">
    `update` requires the record to exist and is keyed by `id`. `upsert` is keyed by any unique column and creates the record if it doesn't exist. Use upsert when you have an external identifier (an email, a Stripe customer ID) and you don't know whether the record already exists.
  </Accordion>

  <Accordion title="Can I update related records in the same mutation?">
    Yes. Nested mutations work on update too — you can pass `create`, `connect`, or `connectOrCreate` to a relationship field on `update<TableName>`. The whole graph is updated atomically.
  </Accordion>

  <Accordion title="What happens if the input is missing a mandatory field?">
    The mutation fails before any data is written, with an error indicating which field is missing. The error includes the field name and the constraint that was violated.
  </Accordion>

  <Accordion title="Are bulk operations faster than looping individual mutations?">
    Yes. `createStudentsMany` runs in one round-trip and one transaction; a client-side loop runs one round-trip per record and serializes them. Use bulk for any batch over a few records.
  </Accordion>

  <Accordion title="Can I get the inserted ids back from a bulk create?">
    The default `createXxxMany` mutation returns `success`. Project a richer payload by selecting more fields in the response if your bulk mutation supports it, or run a follow-up query keyed on the unique values you inserted.
  </Accordion>
</AccordionGroup>
