Firebase Data Connect lets you create connectors for your PostgreSQL
instances managed with Google Cloud SQL. These connectors are combinations of a
queries and mutations for using your data from your schema.
|
| **Note:** Follow the complete series on building Data Connect
| schemas and connectors, which covers:
|
| - [Schema development](https://firebase.google.com/docs/data-connect/schemas-guide)
| - [Query development](https://firebase.google.com/docs/data-connect/queries-guide)
| - Mutation development (this guide)

The [Get started guide](https://firebase.google.com/docs/data-connect/quickstart) introduced a movie
review app schema for PostgreSQL.

That guide also introduced both deployable and ad hoc administrative operations,
including mutations.

- **Deployable mutations** are those you implement to call from client apps in a connector, with API endpoints you define. Data Connect integrates authentication and authorization into these mutations, and generates client SDKs based on your API.
- **Ad hoc administrative mutations** are run from privileged environments to populate and manage tables. You can create and execute them in the Firebase console, from privileged environments using the Firebase Admin SDK, and in local development environments using our Data Connect VS Code extension.

This guide takes a deeper look at **deployable mutations**.

## Features of Data Connect mutations

Data Connect lets you to perform basic mutations in the all the ways
you'd expect given a PostgreSQL database:

- Perform CRUD operations
- Manage multi-step operations with transactions

But with Data Connect's extensions to GraphQL, you can implement
advanced mutations for faster, more efficient apps:

- Use **key scalars** returned by many operations to simplify repeated operations on records
- Use **server values** to populate data with operations provided by the server
- Perform queries in the course of a multi-step mutation operations to look up data, saving lines of code and round trips to the server.

| **Note:** Before using this guide, review the movie review schema.  
|
| #### Movie review app schema
|
| ```graphql
| # Movies
| type Movie
|   @table {
|   id: UUID! @default(expr: "uuidV4()")
|   title: String!
|   releaseYear: Int
|   genre: String
|   rating: Int
|   description: String
|   tags: [String]
| }
|
| # Movie Metadata
| # Movie - MovieMetadata is a one-to-one relationship
| type MovieMetadata
|   @table {
|   movie: Movie! @ref
|   director: String
| }
|
| // ... existing code ...
| extend type Movie {
|   movieMetadata: MovieMetadata
|   movieMetadatas_on_movie: MovieMetadata
| }
|
| # Actors
| # Suppose an actor can participate in multiple movies and movies can have multiple actors
| # Movie - Actors (or vice versa) is a many to many relationship
| type Actor @table {
|   id: UUID! @default(expr: "uuidV4()")
|   name: String!
| }
|
| # Join table for many-to-many relationship for movies and actors
| # The 'key' param signifies the primary keys of this table
| # In this case, the keys are [movieId, actorId], the foreign key fields of the reference fields [movie, actor]
| type MovieActor @table {
|   movie: Movie!
|   actor: Actor!
|   role: String! # "main" or "supporting"
|   # optional other fields
| }
|
| type User @table {
|   # `@default(expr: "auth.uid")` sets it to Firebase Auth UID during insert and upsert.
|   id: String! @default(expr: "auth.uid")
|   username: String!
| }
|
| # Reviews is a join table tween User and Movie.
| # It has a composite primary keys `userUid` and `movieId`.
| # A user can leave reviews for many movies. A movie can have reviews from many users.
| # User  <-> Review is a one-to-many relationship
| # Movie <-> Review is a one-to-many relationship
| # Movie <-> User is a many-to-many relationship
| type Review @table {
|   user: User!
|   movie: Movie!
|   rating: Int
|   reviewText: String
|   reviewDate: Date! @default(expr: "request.time")
|     
| ```

## Use generated fields to implement mutations

Your Data Connect operations will extend a set of fields
automatically generated by Data Connect based on the types and type
relationships in your schema. These fields are generated by local tooling
whenever you edit your schema.
| **Tip:** To discover the generated fields while building operations, use the query editor in the Firebase console, or our Visual Studio Code extension.
You can use generated fields to implement mutations, from creating, updating, and deleting individual records in single tables, to more complex multi-table updates.

Assume your schema contains a `Movie` type and an associated `Actor` type.
Data Connect generates `movie_insert`,
`movie_update`, `movie_delete` fields, and more.
**Note:** These mutation fields return and let you pass a *key scalar* type, here a `Movie_Key`, to identify records. These are created based on your [schema](https://firebase.google.com/docs/data-connect/mutations-guide#key_scalars_and_server_values).  

### Mutation with the
`movie_insert` field

|---|---|
| The `movie_insert` field represents a mutation to create a single record in the `Movie` table. | Use this field to create a single movie. ```graphql mutation CreateMovie($data: Movie_Data!) { movie_insert(data: $data) { key } } ``` |

### Mutation with the
`movie_update` field

|---|---|
| The `movie_update` field represents a mutation to update a single record in the `Movie` table. | Use this field to update a single movie by its key. ```graphql mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) { movie_update(key: $myKey, data: $data) { key } } ``` |

### Mutation with the
`movie_delete` field

|---|---|
| The `movie_delete` field represents a mutation to delete a single record in the `Movie` table. | Use this field to delete a single movie by its key. ```graphql mutation DeleteMovie($myKey: Movie_Key!) { movie_delete(key: $myKey) { key } } ``` |

| **Tip:** The schema definition language also lets you explicitly control how names are generated for fields using [`singular` and `plural` arguments for the `@table` directive](https://firebase.google.com/docs/reference/data-connect/gql/directive#customizations_2).

## Essential elements of a mutation

Data Connect mutations are GraphQL mutations with Data Connect
extensions. Just as with a regular GraphQL mutation, you can define an operation
name and a list of GraphQL variables.

Data Connect extends GraphQL queries with customized **directives** like
`@auth` and `@transaction`.
| **Tip:** You can use AI assistance to help you create and test GraphQL mutations in the Firebase console and in our local tooling. Learn more at [Use AI assistance for scheams, queries and mutations](https://firebase.google.com/docs/data-connect/ai-assistance).

So the following mutation has:

- A `mutation` type definition
- A `SignUp` operation (mutation) name
- A single variable `$username` operation argument
- A single directive, `@auth`
- A single field `user_insert`.

    mutation SignUp($username: String!) @auth(level: USER) {
      user_insert(data: {
        id_expr: "auth.uid"
        username: $username
      })
    }

Every mutation argument requires a type declaration, a built-in like `String`,
or a custom, schema-defined type like `Movie`.
| **Note:** The example shows how to sign up an account as yourself. Anyone that logs in can create a user account for themselves.

## Write basic mutations

You can start writing mutations to create, update and delete individual records
from your database.
| **Tip:** You can use Gemini in Firebase to help you create and run GraphQL mutations in the Firebase console. Learn more at [Use AI assistance for
| queries and mutations](https://firebase.google.com/docs/data-connect/ai-assistance).

### Create

Let's do basic creates.  

    # Create a movie based on user input
    mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
      movie_insert(data: {
        title: $title
        releaseYear: $releaseYear
        genre: $genre
        rating: $rating
      })
    }

    # Create a movie with default values
    mutation CreateMovie2 {
      movie_insert(data: {
        title: "Sherlock Holmes"
        releaseYear: 2009
        genre: "Mystery"
        rating: 5
      })
    }

Or an upsert.  

    # Movie upsert using combination of variables and literals
    mutation UpsertMovie($title: String!) {
      movie_upsert(data: {
        title: $title
        releaseYear: 2009
        genre: "Mystery"
        rating: 5
        genre: "Mystery/Thriller"
      })
    }

### Perform updates

Here are updates. Producers and directors certainly hope that those average
ratings are on trend.

The `movie_update` field contains an expected `id` argument to identify a record
and a `data` field you can use to set values in this update.  

    mutation UpdateMovie(
      $id: UUID!,
      $genre: String!,
      $rating: Int!,
      $description: String!
    ) {
      movie_update(id: $id,
        data: {
          genre: $genre
          rating: $rating
          description: $description
        })
    }

To perform multiple updates, use the `movie_updateMany` field.  

    # Multiple updates (increase all ratings of a genre)
    mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
      movie_updateMany(
        where: { genre: { eq: $genre } },
        data:
          {
            rating: $rating
          })
    }

#### Use increment, decrement, append, and prepend operations with `_update`

While in `_update` and `_updateMany` mutations you can explicitly set values in
`data:`, it often makes more sense to apply an operator like increment to update
values.

To modify the earlier update example, assume you want to increment the rating
of a particular movie. You can use `rating_update` syntax with the `inc`
operator.  

    mutation UpdateMovie(
      $id: UUID!,
      $ratingIncrement: Int!
    ) {
      movie_update(id: $id, data: {
        rating_update: {
          inc: $ratingIncrement
        }
      })
    }

Data Connect supports the following operators for field updates:

- `inc` to increment `Int`, `Int64`, `Float`, `Date` and `Timestamp` data types
- `dec` to decrement `Int`, `Int64`, `Float`, `Date` and `Timestamp` data types

| **Note:** For these operations, if an existing numeric is `null`, then it is treated as `0`. If an existing `Date` or `Timestamp` is `null`, then it is treated as the current date or time.

For lists, you can also update with individual values or lists of values using:

- `add` to append item(s) if they are not already present to list types, except Vector lists
- `remove` to remove all items, if present, from list types, except Vector lists
- `append` to append item(s) to list types, except Vector lists
- `prepend` to prepend item(s) to list types, except Vector lists

| **Note:** For these operations, if the existing list field is `null`, it is treated as an empty list.
| **Tip:** For details of the interface, see the [`_Update` and `_ListUpdate` reference documentation entries](https://firebase.google.com/docs/reference/data-connect/gql/input_object).

### Perform deletes

You can of course delete movie data. Film preservationists will certainly
want the physical films to be maintained for as long as possible.  

    # Delete by key
    mutation DeleteMovie($id: UUID!) {
      movie_delete(id: $id)
    }

Here you can use `_deleteMany`.  

    # Multiple deletes
    mutation DeleteUnpopularMovies($minRating: Int!) {
      movie_deleteMany(where: { rating: { le: $minRating } })
    }

### Write mutations on relations

Observe how to use the implicit `_upsert` mutation on a relation.  

    # Create or update a one to one relation
    mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
      movieMetadata_upsert(
        data: { movie: { id: $movieId }, director: $director }
      )
    }

| **Tip:** The complete set of GraphQL language extensions for Data Connect is [documented in the language reference guide](https://firebase.google.com/docs/reference/data-connect).

## Design schemas for efficient mutations

Data Connect provides two important features that allow you to
write more efficient mutations and save round-trip operations.

**Key scalars** are concise object identifiers that Data Connect
automatically assembles from key fields in your schemas. Key scalars are about
efficiency, allowing you to find in a single call information about the identity
and structure of your data. They are especially useful when you want to perform
sequential actions on new records and need a unique identifier to pass to
upcoming operations, and also when you want to access relational keys to
perform additional more complex operations.

Using **server values** , you can effectively let the server dynamically populate
fields in your tables using stored or readily-computable values according to
particular server-side CEL expressions in the `expr` argument. For example, you
can define a field with a timestamp applied when the field is accessed using
the time stored in an operation request, `updatedAt: Timestamp!
@default(expr: "request.time")`.

## Write advanced mutations: let Data Connect supply values using `field_expr` syntax

As discussed in [key scalars and server values](https://firebase.google.com/docs/data-connect/mutations-guide#key_scalars_and_server_values),
you can design your schema so that the server populates values for common
fields like `id`s and dates in response to client requests.

In addition, you can make use of data, such as user IDs, sent in
Data Connect `request` objects from client apps.

When you implement mutations, use `field_expr` syntax to trigger
server-generated updates or access data from requests. For example, to pass
the authorization `uid` stored in a request to an `_upsert` operation, pass
`"auth.uid"` in the `userId_expr` field.  

    # Add a movie to the user's favorites list
    mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
      favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
    }

    # Remove a movie from the user's favorites list
    mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
      favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
    }

Or, in a familiar to-do list app, when creating a new to-do list, you could
pass `id_expr` to instruct the server to auto-generate a UUID for the list.  

    mutation CreateTodoListWithFirstItem(
      $listName: String!
    ) @transaction {
      # Step 1
      todoList_insert(data: {
        id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
        name: $listName,
      })
    }

For more information, see the `_Expr` scalars in the
[scalars reference](https://firebase.google.com/docs/reference/data-connect/gql/scalar).

## Write advanced mutations: multi-step operations

There are many situations in which you might want to include multiple write
fields (like inserts) in one mutation. You might also want to read your database
during execution of a mutation to lookup and verify existing data before
performing, for example, inserts or updates. These options save round trip
operations and hence costs.

Data Connect lets you perform multi-step logic in your mutations by
supporting:

- Multiple write fields

- Multiple read fields in your mutations (using the `query` field keyword).

- The [`@transaction` directive](https://firebase.google.com/docs/data-connect/mutations-guide#query-mutation-directives), which provides
  transaction support familiar from relational databases.

- The [`@check` directive](https://firebase.google.com/docs/data-connect/mutations-guide#query-mutation-directives), which lets
  you evaluate the contents of reads using CEL expressions, and based
  on the results of such evaluation:

  - Proceed with creates, updates and deletes defined by a mutation
  - Proceed to return the results of a query field
  - Use returned messages to perform appropriate logic in your client code
- The [`@redact` directive](https://firebase.google.com/docs/data-connect/mutations-guide#query-mutation-directives), which lets you omit
  query field results from wire protocol results.

- The CEL `response` binding, which stores the accumulated results of all
  mutations and queries performed in a complex, multi-step operation. You can
  access the `response` binding:

  - In `@check` directives, through the `expr:` argument
  - With server values, using `field_expr` syntax

### The `@transaction` directive

Support for multi-step mutations includes error handling using transactions.

The `@transaction` directive enforces that a mutation - with either a single
write field (for example, `_insert` or `_update`) or with multiple
write fields - always run in a database transaction.

- Mutations without `@transaction` execute each root field one after another
  in sequence. The operation surfaces any errors as partial field errors, but not
  the impacts of the subsequent executions.

- Mutations with `@transaction` are guaranteed to either fully succeed or fully
  fail. If any of the fields within the transaction fails, the entire transaction
  is rolled back.

### The `@check` and `@redact` directives

The `@check` directive verifies that specified fields are present in query
results. A Common Expression Language (CEL) expression is used to test field
values. The default behavior of the directive is to check for and reject
nodes whose value is `null` or `[]` (empty lists).

The `@redact` directive redacts a part of the response from the client. Redacted
fields are still evaluated for side effects (including data changes and
`@check`) and the results are still available to later steps in CEL expressions.

#### Use `@check`, `@check(message:)` and `@redact`

A major use for `@check` and `@redact` is looking up related data to decide
whether certain operations should be authorized, using the lookup in logic but
hiding it from clients. Your query can return useful messages for correct
handling in client code.

For illustration, the following query field checks whether a requestor has an
appropriate "admin" role to view users who can edit a movie.  

    query GetMovieEditors($movieId: UUID!) @auth(level: USER) {
      moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
        role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
      }
      moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
        user {
          id
          username
        }
      }
    }

To learn more about `@check` and `@redact` directives in authorization checks,
refer to the [discussion of authorization data lookup](https://firebase.google.com/docs/data-connect/authorization-and-security#authz-data-lookup).

#### Use `@check` to validate keys

Some mutation fields, such as `_update`, may no-op if a record with a specified
key does not exist. Similarly, lookups may return null or an empty list. These
are not considered errors and therefore won't trigger rollbacks.

To guard against this result, test whether keys can be found using the `@check`
directive.  

    # Delete by key, error if not found
    mutation MustDeleteMovie($id: UUID!) @transaction {
      movie_delete(id: $id) @check(expr: "this != null", message: "Movie not found, therefore nothing is deleted")
    }

| **Tip:** See the [directives reference for the `@check` and `@redact` directives](https://firebase.google.com/docs/reference/data-connect/gql/directive) and the [CEL expression reference](https://firebase.google.com/docs/data-connect/cel-reference).

### Use the `response` binding to chain multi-step mutations

The basic approach to creating related records, for example a new `Movie` and
an associated `MovieMetadata` entry, is to:

1. Call an `_insert` mutation for `Movie`
2. Store the returned key of the created movie
3. Then, call a second `_insert` mutation to create the `MovieMetadata` record.

But with Data Connect, you can handle this common case in a single
multi-step operation by accessing the *results* of the first `_insert` in the
second `_insert`.

Making a successful movie review app is a lot of work. Let's track our to-do
list with a new example.

#### Use `response` to set fields with server values

In the following to-do list mutation:

- The `response` binding represents the partial response object so far, which includes all top-level mutation fields before the current one.
- The results of the initial `todoList_insert` operation, which returns the `id` (key) field, are accessed later in `response.todoList_insert.id` so we can immediately insert a new to-do item.

    mutation CreateTodoListWithFirstItem(
      $listName: String!,
      $itemContent: String!
    ) @transaction {
      # Sub-step 1:
      todoList_insert(data: {
        id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
        name: $listName,
      })
      # Sub-step 2:
      todo_insert(data: {
        listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
        content: $itemContent,
      })
    }

| **Note:** Results of sub-steps are available in the same `@transaction`, so you still get the atomicity guarantees as you'd expect from a relational database.

#### Use `response` to validate fields using `@check`

`response` is available in `@check(expr: "...")` as well, so you can use it to
build even more complicated server-side logic. Combined with `query { ... }` steps
in mutations, you can achieve a lot more without any additional client-server
roundtrips.

In the following example, note: that `@check` already has access to `response.query`
because a `@check` always runs after the step it is attached to.
**Tip:** You can hide results of intermediate steps from the wire and clients using `@redact`. Redacted fields are still available in `response` for use in later steps.  

    mutation CreateTodoInNamedList(
      $listName: String!,
      $itemContent: String!
    ) @transaction {
      # Sub-step 1: Look up List.id by its name
      query
      @check(expr: "response.query.todoLists.size() > 0", message: "No such TodoList with the name!")
      @check(expr: "response.query.todoLists.size() < 2", message: "Ambiguous listName!") {
        todoLists(where: { name: $listName }) {
          id
        }
      }
      # Sub-step 2:
      todo_insert(data: {
        listId_expr: "response.todoLists[0].id" # <-- Now we have the parent list ID to insert to
        content: $itemContent,
      })
    }

For more information about the `response` binding, see the
[CEL reference](https://firebase.google.com/docs/data-connect/cel-reference#the_response_binding).

### Understand interrupted operations with `@transaction` and `query @check`

Multi-step mutations can encounter errors:

- Database operations may fail.
- query `@check` logic may terminate operations.

Data Connect recommends that you use the `@transaction` directive with
your multi-step mutations. This results in a more consistent database and
mutation results that are easier to handle in client code:

- At the first error or failed `@check`, the operation will terminate, so there is no need to manage execution of any subsequent fields or evaluation of CEL.
- Rollbacks are performed in response to database errors or `@check` logic, yielding a consistent database state.
- A rollback error is always returned to client code.

There may be some use cases where you choose not to use `@transaction`: you
might opt for eventual consistency if, for example, you need higher throughput,
scalability or availability. However, you need to manage your database and your
client code to allow for the results:

- If one field fails due to database operations, subsequent fields will continue to execute. However, failed `@check`s still terminate the entire operation.
- Rollbacks are not performed, meaning a mixed database state with some successful updates and some failed updates.
- Your operations with `@check` may give more inconsistent results if your `@check` logic uses the results of reads and/or writes in a previous step.
- The result returned to client code will contain a more complex mix of success and failure responses to be handled.

## Directives for Data Connect mutations

In addition to the directives you use in defining types and tables,
Data Connect provides the `@auth`, `@check`, `@redact` and
`@transaction` directives for augmenting the behavior of operations.

| Directive | Applicable to | Description |
|---|---|---|
| `@auth` | Queries and mutations | Defines the authorization policy for a query or mutation. See the [authorization and attestation guide](https://firebase.google.com/docs/data-connect/authorization-and-security#auth-directive). |
| `@check` | `query` fields in multi-step operations | Verifies that specified fields are present in query results. A Common Expression Language (CEL) expression is used to test field values. See [Multi-step operations](https://firebase.google.com/docs/data-connect/mutations-guide#multi-step-operations). |
| `@redact` | Queries | Redacts a part of the response from the client. See [Multi-step operations](https://firebase.google.com/docs/data-connect/mutations-guide#multi-step-operations). |
| `@transaction` | Mutations | Enforces that a mutation always run in a database transaction. See [Multi-step operations](https://firebase.google.com/docs/data-connect/mutations-guide#multi-step-operations). |

| **Tip:** Complete reference documentation for these directives is available in the [directives reference guide](https://firebase.google.com/docs/reference/data-connect/gql/directive).

## Next steps

|
| **Note:** This guide is part of a complete series on building
| Data Connect schemas and connectors, which covers:
|
| - [Schema development](https://firebase.google.com/docs/data-connect/schemas-guide).
| - [Query development](https://firebase.google.com/docs/data-connect/queries-guide).
| - Mutation development (this guide).

You may be interested in:

- Generating mutations for your apps using [AI assistance tools](https://firebase.google.com/docs/data-connect/ai-assistance)
- Authorizing your mutations per the [authorization guide](https://firebase.google.com/docs/data-connect/authorization-and-security)
- Calling mutations from your client code for [web](https://firebase.google.com/docs/data-connect/web-sdk), [iOS](https://firebase.google.com/docs/data-connect/ios-sdk), [Android](https://firebase.google.com/docs/data-connect/android-sdk) and [Flutter](https://firebase.google.com/docs/data-connect/flutter-sdk).
- Performing [bulk data operations with mutations](https://firebase.google.com/docs/data-connect/data-seeding-bulk-operations)