---
title: Mutations
---

# Mutations

## Getting started

Mutations can be defined the same way as
[strawberry's mutations](https://strawberry.rocks/docs/general/mutations), but instead of
using `@strawberry.mutation`, use `@strawberry_django.mutation`.

Here are the differences between those:

- Strawberry Django's mutation will be sure that the mutation is executed in an async safe
  environment, meaning that if you are running ASGI and you define a `sync` resolver, it will
  automatically be wrapped in a `sync_to_async` call.
- It will better integrate with the [permission integration](./permissions.md)
- It has an option to automatically handle common django errors and return them
  in a standardized way (more on that below)

## Django errors handling

When defining a mutation you can pass `handle_django_errors=True` to make it handle
common django errors, such as `ValidationError`, `PermissionDenied` and `ObjectDoesNotExist`:

```python title="types.py"
@strawberry.type
class Mutation:
    @strawberry_django.mutation(handle_django_errors=True)
    def create_fruit(self, name: str, color: str) -> Fruit:
        if not is_valid_color(color):
            raise ValidationError("The color is not valid")

        # Creation can also raise ValidationError, if the `name` is
        # larger than its allowed `max_length` for example.
        fruit = models.Fruit.objects.create(name=name)
        return cast(Fruit, fruit)
```

The code above would generate following schema:

```graphql title="schema.graphql"
enum OperationMessageKind {
  INFO
  WARNING
  ERROR
  PERMISSION
  VALIDATION
}

type OperationInfo {
  """List of messages returned by the operation."""
  messages: [OperationMessage!]!
}

type OperationMessage {
  """The kind of this message."""
  kind: OperationMessageKind!

  """The error message."""
  message: String!

  """
  The field that caused the error, or `null` if it isn't associated with any particular field.
  """
  field: String

  """The error code, or `null` if no error code was set."""
  code: String
}

type Fruit {
  name: String!
  color: String!
}

union CreateFruitPayload = Fruit | OperationInfo

mutation {
  createFruit(
    name: String!
    color: String!
  ): CreateFruitPayload!
}
```

> [!TIP]
> If all or most of your mutations use this behaviour, you can change the
> default behaviour for `handle_django_errors` by setting
> `MUTATIONS_DEFAULT_HANDLE_ERRORS=True` in your [strawberry django settings](./settings.md)

## Input mutations

Those are defined using `@strawberry_django.input_mutation` and act the same way as
the `@strawberry_django.mutation`, the only difference being that it injects
an [InputMutationExtension](https://strawberry.rocks/docs/general/mutations#the-input-mutation-extension)
in the field, which converts its arguments in a new type (check the extension's docs
for more information).

## CUD mutations

The following CUD mutations are provided by this lib:

- `strawberry_django.mutations.create`: Will create the model using the data from the given input
- `strawberry_django.mutations.update`: Will update the model using the data from the given input
- `strawberry_django.mutations.delete`: Will delete the model using the id from the given input

A basic example would be:

```python title="types.py"
from strawberry import auto
from strawberry_django import mutations, NodeInput
from strawberry.relay import Node


@strawberry_django.type(SomeModel)
class SomeModelType(Node):
    name: auto


@strawberry_django.input(SomeModel)
class SomeModelInput:
    name: auto


@strawberry_django.partial(SomeModel)
class SomeModelInputPartial(NodeInput):
    name: auto


@strawberry.type
class Mutation:
    create_model: SomeModelType = mutations.create(SomeModelInput)
    update_model: SomeModelType = mutations.update(SomeModelInputPartial)
    delete_model: SomeModelType = mutations.delete(NodeInput)
```

Some things to note here:

- Those CUD mutations accept the same arguments as `@strawberry_django.mutation`
  accepts. This allows you to pass `handle_django_errors=True` to it for example.
- The mutation will receive the type in an argument named `"data"` by default.
  To change it to `"info"` for example, you can change it by passing
  `argument_name="info"` to the mutation, or set `MUTATIONS_DEFAULT_ARGUMENT_NAME="info"`
  in your [strawberry django settings](./settings.md) to make it the default when not provided.
- Take note that inputs using `partial` will _not_ automatically mark non-auto fields optional
  and instead will respect explicit type annotations;
  see [partial input types](./types.md#input-types) documentation for examples.
- It's also possible to update or delete a model using a unique identifier other than `id` by passing a `key_attr` argument:

```python
@strawberry_django.partial(SomeModel)
class SomeModelInputPartial:
    unique_field: strawberry.auto


@strawberry.type
class Mutation:
    update_model: SomeModelType = mutations.update(
        SomeModelInputPartial,
        key_attr="unique_field",
    )
    delete_model: SomeModelType = mutations.delete(
        SomeModelInputPartial,
        key_attr="unique_field",
    )
```

## Partial updates with Maybe

When using `partial` inputs for updates, all `auto` fields become optional. However, this makes it
impossible to distinguish between "field not provided" and "field explicitly set to null".

The [`Maybe`](https://strawberry.rocks/docs/types/maybe) type solves this:

- `Maybe[str]`: field is either absent (`None`) or has a value (`Some("hello")`)
- `Maybe[str | None]`: field is absent (`None`), has a value (`Some("hello")`), or explicitly null (`Some(None)`)

```python title="types.py"
from strawberry import Maybe


@strawberry_django.input(models.Fruit)
class FruitUpdateInput:
    id: strawberry.relay.GlobalID
    # name is required, null not allowed
    name: Maybe[str]
    # color is optional, can be explicitly set to null
    color: Maybe[str | None]


@strawberry.type
class Mutation:
    @strawberry_django.mutation
    def update_fruit(self, info, input: FruitUpdateInput) -> Fruit:
        fruit = input.id.resolve_node_sync(info)

        if input.name is not None:
            fruit.name = input.name.value

        if input.color is not None:
            # input.color.value is either a string or None
            fruit.color = input.color.value

        fruit.save()
        return cast(Fruit, fruit)
```

This pattern allows clients to:

- Omit a field entirely (no change)
- Set a field to a value: `{ "name": "Apple" }`
- Set a nullable field to null: `{ "color": null }` (only with `Maybe[T | None]`)

For more details, see the [Strawberry Maybe documentation](https://strawberry.rocks/docs/types/maybe).

## Filtering

> [!CAUTION]
> Filtering on mutations is discouraged as it can potentially alter your entire model collection if there are issues with the filters.

Filters can be added to update and delete mutations. More information in the
[filtering](filters.md) section.

```python title="schema.py"
import strawberry
from strawberry_django import mutations


@strawberry.type
class Mutation:
    updateFruits: list[Fruit] = mutations.update(FruitPartialInput, filters=FruitFilter)
    deleteFruits: list[Fruit] = mutations.delete(filters=FruitFilter)


schema = strawberry.Schema(mutation=Mutation)
```

## Batching

If you need to make multiple creates, updates, or deletes as part of one atomic mutation you can use batching. Batching has a similar syntax except that the mutations take and return a list.

```python title="schema.py"
import strawberry
from strawberry_django import mutations


@strawberry.type
class Mutation:
    createFruits: list[Fruit] = mutations.create(list[FruitPartialInput])
    updateFruits: list[Fruit] = mutations.update(list[FruitPartialInput])
    deleteFruits: list[Fruit] = mutations.delete(list[FruitPartialInput])


schema = strawberry.Schema(mutation=Mutation)
```
