Skip to content

GraphQL Mutation Return Types — Designing Payloads for Create, Update, and Delete Operations

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Graphql Mutation Return Types. We cover key concepts, practical examples, and best practices to help you master this topic.

GraphQL mutation return types define what data is returned after a mutation, typically including the modified object, status information, and user-visible errors for robust client handling.

What You'll Learn

  • Designing mutation payload types
  • Including errors in mutation responses
  • Returning modified objects and status

Why It Matters

Well-designed mutation return types make client error handling reliable. Without structured mutation payloads, clients must parse error messages for common failure modes.

Code Examples

# Mutation payload types
type Mutation {
  createUser(input: CreateUserInput!): CreateUserPayload!
  updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
  deleteUser(id: ID!): DeleteUserPayload!
}

type CreateUserPayload {
  user: User
  errors: [UserError!]
  status: MutationStatus!
}

type UpdateUserPayload {
  user: User
  errors: [UserError!]
  status: MutationStatus!
}

type DeleteUserPayload {
  deletedId: ID
  errors: [UserError!]
  status: MutationStatus!
}

type UserError {
  field: String!
  message: String!
  code: ErrorCode!
}

enum MutationStatus {
  SUCCESS
  PARTIAL_SUCCESS
  VALIDATION_ERROR
  NOT_FOUND
  UNAUTHORIZED
  ERROR
}

enum ErrorCode {
  REQUIRED_FIELD
  INVALID_FORMAT
  DUPLICATE
  NOT_FOUND
  UNAUTHORIZED
  INTERNAL_ERROR
}
// Mutation resolvers with structured responses
const resolvers = {
  Mutation: {
    createUser: async (parent, { input }, { db }) => {
      const errors = [];

      // Validation
      if (!input.name) {
        errors.push({ field: 'name', message: 'Name is required', code: 'REQUIRED_FIELD' });
      }
      if (input.email && !emailRegex.test(input.email)) {
        errors.push({ field: 'email', message: 'Invalid email format', code: 'INVALID_FORMAT' });
      }

      if (errors.length > 0) {
        return { user: null, errors, status: 'VALIDATION_ERROR' };
      }

      try {
        const user = await db.users.create(input);
        return { user, errors: [], status: 'SUCCESS' };
      } catch (error) {
        if (error.code === '23505') { // Unique violation
          errors.push({ field: 'email', message: 'Email already exists', code: 'DUPLICATE' });
          return { user: null, errors, status: 'VALIDATION_ERROR' };
        }
        return { user: null, errors: [], status: 'ERROR' };
      }
    }
  }
};
# Python mutation payload
import strawberry

@strawberry.type
class UserError:
    field: str
    message: str
    code: str

@strawberry.type
class CreateUserPayload:
    user: User | None
    errors: list[UserError] = []
    status: MutationStatus = MutationStatus.SUCCESS

@strawberry.type
class Mutation:
    @strawberry.mutation
    async def create_user(self, input: CreateUserInput) -> CreateUserPayload:
        errors = []
        if not input.name:
            errors.append(UserError(field='name', message='Required', code='REQUIRED'))
        if errors:
            return CreateUserPayload(user=None, errors=errors, status='VALIDATION_ERROR')
        user = await create_user(input)
        return CreateUserPayload(user=user)

Common Mistakes

1. Returning Only the Modified Object

Without status and errors, clients cannot distinguish validation errors from server errors.

2. Throwing Errors for Validation Failures

Use structured error payloads instead of throwing for expected validation failures.

3. Not Including Mutation Status

A status field allows clients to quickly determine the mutation outcome.

4. Exposing Internal Error Details

Do not leak stack traces or internal error messages in mutation payloads.

5. Returning Null for All Error Cases

Always return enough data for clients to handle errors gracefully.

Practice Questions

  1. Why use structured mutation payloads instead of throwing errors?
  2. What should a mutation payload include?
  3. How do you handle partial success in mutations?
  4. What error fields help clients handle failures?
  5. How do you design errors for batch mutations?

Answers:

  1. Structured payloads allow clients to handle validation errors without try-catch.
  2. The modified object, errors array, and status enum.
  3. Return the successfully modified items and errors for failed items.
  4. Field name, error message, and error code for programmatic handling.
  5. Return arrays of successful and failed items with error details per item.

Challenge: Design mutation payloads for a batch import operation that creates multiple users. Handle partial success where some users are created and others fail validation.

FAQ

Should I always use mutation payloads?

Yes, for any mutation that can fail for expected reasons. For simple toggles, returning the modified field may suffice.

How do I handle authentication errors in mutations?

Throw AuthenticationError for auth failures. Use payload errors for business validation failures.

Can I use unions for mutation payloads?

Yes. A union of Success and Error types provides a clean pattern for mutation responses.

What is the difference between errors and throw?

Errors in payloads are expected failures clients can handle. Throwing is for unexpected server errors.

How do I handle optimistic updates with mutation payloads?

Use the status field to indicate if the mutation is pending, confirmed, or rejected.

Mini Project

Build a complete mutation system for a CRUD API with structured payloads for create, update, delete, and batch operations. Include validation errors, not-found errors, authorization errors, and success responses.

What's Next

Learn about subscription filtering for targeted event delivery, then explore subscription context for auth in subscriptions.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro