Interacting with the GraphQL API
Interacting with the GraphQL APIHandling mutation payloads

Handling mutation payloads

Mutation fields can be configured to return either of these 2 different entity types:

  • A payload object type
  • Directly the mutated entity

Payload object type

A payload object type contains all the data concerning the mutation:

  • The status of the mutation (success or failure)
  • The errors (if any) using distinctive GraphQL types, or
  • The successfully mutated entity

For instance, mutation updatePost returns an object of type PostUpdateMutationPayload, and we still need to query its field post to retrieve the updated post entity:

mutation UpdatePost {
  updatePost(input: {
    id: 1724,
    title: "New title",
    status: publish
  }) {
    # This is the status of the mutation: SUCCESS or FAILURE
    status
    errors {
      __typename
      ...on ErrorPayload {
        message
      }
    }
    post {
      id
      title
      # This is the status of the post: publish, pending, trash, etc
      status
    }
  }
}

The payload object allows us to represent better the errors, even having a unique GraphQL type per kind of error. This allows us to present different reactions for different errors in the application, thus improving the user experience.

In the example above, if the operation was successful, we will receive:

{
  "data": {
    "updatePost": {
      "status": "SUCCESS",
      "errors": null,
      "post": {
        "id": 1724,
        "title": "Some title",
        "status": "publish"
      }
    }
  }
}

If the user is not logged in, we will receive:

{
  "data": {
    "updatePost": {
      "status": "FAILURE",
      "errors": [
        {
          "__typename": "UserIsNotLoggedInErrorPayload",
          "message": "You must be logged in to create or update custom posts"
        }
      ],
      "post": null
    }
  }
}

If the user doesn't have the permission to edit posts, we will receive:

{
  "data": {
    "updatePost": {
      "status": "FAILURE",
      "errors": [
        {
          "__typename": "LoggedInUserHasNoEditingCustomPostCapabilityErrorPayload",
          "message": "Your user doesn't have permission for editing custom posts."
        }
      ],
      "post": null
    }
  }
}

In this mode, the GraphQL schema will contain plenty of additional MutationPayload, MutationErrorPayloadUnion and ErrorPayload types, so it will have a bigger size:

GraphQL schema with payload object types for mutations

Mutated entity

The mutation will directly return the mutated entity in case of success, or null in case of failure, and any error message will be displayed in the JSON response's top-level errors entry.

For instance, mutation updatePost will return the object of type Post:

mutation UpdatePost {
  updatePost(input: {
    id: 1724,
    title: "New title",
    status: publish
  }) {
    id
    title
    status
  }
}

If the operation was successful, we will receive:

{
  "data": {
    "updatePost": {
      "id": 1724,
      "title": "Some title",
      "status": "publish"
    }
  }
}

In case of errors, these will appear under the errors entry of the response. For instance, if the user is not logged in, we will receive:

{
    "errors": [
      {
        "message": "You must be logged in to create or update custom posts'",
        "locations": [
          {
            "line": 2,
            "column": 3
          }
        ]
      }
  ],
  "data": {
    "updatePost": null
  }
}

We must notice that, as a result, the top-level errors entry will contain not only syntax, schema validation and logic errors (eg: not passing a field argument's name, requesting a non-existing field, or calling _sendHTTPRequest and the network is down respectively), but also "content validation" errors (eg: "you're not authorized to modify this post").

Because there are no additional types added, the GraphQL schema will look leaner:

GraphQL schema without payload object types for mutations

Handling the payload object type for mutations

Let's see how to handle the first option, the payload object type.

Mutations in the schema return some payload object, which provides any error(s) resulting from the mutation, or the modified object if successful (these 2 properties are most likely exclusive: either errors or object will have a value, and the other one will be null).

Errors are provided via some "ErrorPayloadUnion" type, containing all possible errors for that mutation. Every possible error is some "ErrorPayload" type that implements the interface ErrorPayload.

For instance, the operation updatePost returns a PostUpdateMutationPayload, which contains the following fields:

  • status: whether the operation was successful or not, with either value SUCCESS or FAILURE
  • post and postID: the updated post object and its ID, if the update was successful
  • errors: a list of CustomPostUpdateMutationErrorPayloadUnion, if the update failed.

The union type CustomPostUpdateMutationErrorPayloadUnion contains the list of all possible errors that can happen when modifying a custom post:

  • CustomPostDoesNotExistErrorPayload
  • GenericErrorPayload
  • LoggedInUserHasNoEditingCustomPostCapabilityErrorPayload
  • LoggedInUserHasNoPermissionToEditCustomPostErrorPayload
  • LoggedInUserHasNoPublishingCustomPostCapabilityErrorPayload
  • UserIsNotLoggedInErrorPayload

Error type GenericErrorPayload is contained by all "ErrorPayloadUnion" types. It is used whenever the specific reason for the error cannot be pointed out, such as when wp_update_post simply produces WP_Error. This type provides two additional fields: code and data.

Then, to execute the updatePost mutation, we can execute:

mutation UpdatePost(
  $postId: ID!
  $title: String!
) {
  updatePost(
    input: {
      id: $postId,
      title: $title,
    }
  ) {
    status
    errors {
      __typename
      ...on ErrorPayload {
        message
      }
      ...on GenericErrorPayload {
        code
      }
    }
    post {
      id
      title
    }
  }
}

If the operation was successful, we will receive:

{
  "data": {
    "updatePost": {
      "status": "SUCCESS",
      "errors": null,
      "post": {
        "id": 1724,
        "title": "This incredible title"
      }
    }
  }
}

If the user is not logged in, we will receive:

{
  "data": {
    "updatePost": {
      "status": "FAILURE",
      "errors": [
        {
          "__typename": "UserIsNotLoggedInErrorPayload",
          "message": "You must be logged in to create or update custom posts"
        }
      ],
      "post": null
    }
  }
}

If the user doesn't have the permission to edit posts, we will receive:

{
  "data": {
    "updatePost": {
      "status": "FAILURE",
      "errors": [
        {
          "__typename": "LoggedInUserHasNoEditingCustomPostCapabilityErrorPayload",
          "message": "Your user doesn't have permission for editing custom posts."
        }
      ],
      "post": null
    }
  }
}