> ## 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](/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.                                                                      |
| `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 }
  ]
}
```

### 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](/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>
