# Nested mutations

Nested mutations is the ability to perform mutations on a type other than the root type in GraphQL.

For instance, this standard mutation executed at the top level:

mutation {
  updatePost(id: 1459, title: "New title") {
    title
  }
}

Could also be executed through this nested mutation, on type Post:

mutation {
  post(id: 1459) {
    update(title: "New title") {
      title
    }
  }
}
View PQL query
/?query=
  post(id: 1459).
    update(title: New title).
      title

[View query results]

Mutations can also be nested, modifying data on the result from another mutation.

In this query, we obtain the post entity through Root.post, then execute mutation Post.addComment on it and obtain the created comment object, and finally execute mutation Comment.reply on this latter object:

mutation {
  post(id: 1459) {
    title
    addComment(comment: "Nice tango!") {
      id
      content
      reply(comment: "Can you dance like that?") {
        id
        content
      }
    }
  }
}
View PQL query
/?query=
  post(id: 1459).
    title|
    addComment(comment: Nice tango!).
      id|
      content|
      reply(comment: Can you dance like that?).
        id|
        content

[View query results]

Producing this response:

{
  "data": {
    "post": {
      "title": "A lovely tango, not with leo",
      "addComment": {
        "id": 117,
        "content": "<p>Nice tango!</p>\n",
        "reply": {
          "id": 118,
          "content": "<p>Can you dance like that?</p>\n"
        }
      }
    }
  }
}

# Changing the schema root type to Root

For the standard behavior, queries and mutations are handled separately, through two different root types: The QueryRoot and the MutationRoot.

Standard root types

In this sitatuation, MutationRoot is the only type in the whole GraphQL schema which can contain mutation fields. However, this situation is different with nested mutations, since then every single type can execute a mutation (not just the root type), and at any level of the query (not just at the top).

Since both query and mutation fields can be added to a same type, then the MutationRoot type doesn't make sense anymore, and types QueryRoot and MutationRoot are merged into a single type Root handling both query and mutation fields.

Standard root types

# Removing "duplicate" fields from the root?

With nested mutations, mutation fields may be added two times to the schema:

  • once under the Root type
  • once under the specific type

For instance, these fields can be considered a "duplicate":

  • Root.updatePost
  • Post.update

We can decide to keep both of them, or remove the ones from the Root type, which are redundant. This is accomplished through environment settings (check section at the bottom).

# Validating mutations via the operation type

To execute mutations, the operation type must be mutation. This applies to nested mutations also.

For instance, if this query is executed:

query {
  post(id: 1459) {
    title
    addComment(comment: "Hi there") {
      id
      content
    }
  }
}

it would produce an error, indicating that mutation addComment cannot be executed because the query is using operation type query:

{
  "errors": [
    {
      "message": "Use the operation type 'mutation' to execute mutations",
      "extensions": {
        "type": "Post",
        "id": 1459,
        "field": "addComment(comment:\"Hi there\")"
      }
    }
  ],
  "data": {
    "post": {
      "title": "A lovely tango, not with leo"
    }
  }
}

This query will instead work well:

mutation {
  post(id: 5) {
    title
    update(title: "New title") {
      newTitle: title
    }
  }
}

# Executing a single mutation on multiple objects

Using nested mutations, we can mutate several fields at once without modifying or duplicating any field from the schema, as is usually required for the standard behavior (eg: to accept a param ids: [ID]! for the multiple objects, instead of id: ID!).

For instance, this query adds the same comment to several posts:

mutation {
  posts(limit: 3) {
    title
    addComment(comment: "First comment on several posts") {
      id
      content
      reply(comment: "Response to my own parent comment") {
        id
        content
      }
    }
  }
}
View PQL query
/?query=
  posts(limit: 3).
    title|
    addComment(comment: First comment on several posts).
      id|
      content|
      reply(comment: Response to my own parent comment).
        id|
        content

[View query results]

Which produces this response:

{
  "data": {
    "posts": [
      {
        "title": "Scheduled by Leo",
        "addComment": {
          "id": 126,
          "content": "<p>First comment on several posts</p>\n",
          "reply": {
            "id": 129,
            "content": "<p>Response to my own parent comment</p>\n"
          }
        }
      },
      {
        "title": "COPE with WordPress: Post demo containing plenty of blocks",
        "addComment": {
          "id": 127,
          "content": "<p>First comment on several posts</p>\n",
          "reply": {
            "id": 130,
            "content": "<p>Response to my own parent comment</p>\n"
          }
        }
      },
      {
        "title": "A lovely tango, not with leo",
        "addComment": {
          "id": 128,
          "content": "<p>First comment on several posts</p>\n",
          "reply": {
            "id": 131,
            "content": "<p>Response to my own parent comment</p>\n"
          }
        }
      }
    ]
  }
}

# Visualizing the mutation fields in the schema

Since a type now contains query and mutation fields, we may want to clearly visualize which is which, for instance on the docs when using GraphiQL.

Unfortunately, because there is no isMutation flag available on type __Field when doing introspection, then the solution employed is a bit hacky, and not completely satisfying: prepending label "[Mutation] " on the field's description:

Description for type  in GraphiQL docs

In addition, GraphQL by PoP has added field extensions to type __Field (hidden from the schema, since it is not currently supported by the spec), as to retrieve the custom extension data, as done in this introspection query:

query {
  __schema {
    queryType {
      fields {
        name
        # This field is not currently part of the spec
        extensions
      }
    }
  }
}

Which will produce these results (notice entries with isMutation: true):

{
  "data": {
    "__schema": {
      "queryType": {
        "fields": [
          {
            "name": "addCommentToCustomPost",
            "extensions": {
              "isMutation": true
            }
          },
          {
            "name": "createPost",
            "extensions": {
              "isMutation": true
            }
          },
          {
            "name": "customPost",
            "extensions": []
          },
          {
            "name": "customPosts",
            "extensions": []
          },
          {
            "name": "loginUser",
            "extensions": {
              "isMutation": true
            }
          }
        ]
      }
    }
  }
}

# GraphQL spec

This functionality is currently not part of the GraphQL spec, but it has been requested:

# Configuration

# Environment variables

Environment variable Description Default
ENABLE_NESTED_MUTATIONS Enable using nested mutations false
DISABLE_REDUNDANT_ROOT_TYPE_MUTATION_FIELDS Disable the redundant (or duplicate) mutation fields from the root type (to be set if nested mutations are enabled) false
Last Updated: 11/27/2020, 8:16:57 AM