GraphQL Mutations, With Working Examples

A GraphQL mutation is the operation you send to change data. It starts with the mutation keyword, calls a field on the schema's Mutation type, passes arguments, usually one input object held in a $variable, and selects which fields of the result to return. Top-level mutation fields run one after another, never in parallel.

Mutation syntax in parts
PartExampleWhat it does
Keyword and namemutation AddTaskMarks the operation as a write and names it for logs
Variable definitions($input: TaskInput!)Declares typed values sent separately as JSON
Root fieldaddTask(input: $input)A field defined on the Mutation type
Selection set{ id title done }Fields of the result to send back
Variables JSON{"input": {...}}The actual values, kept out of the query string

Updated

Use the builder below to see a full mutation for a small task list: the operation, the variables JSON your code would send with it, and a typical response. Type a task title and watch the variables update, so you can copy a request that is ready to run against a matching schema.

Mutation builder

The input type groups the new task's fields. The selection set asks for the id the server assigned.

mutation AddTask($input: TaskInput!) {
  addTask(input: $input) {
    id
    title
    done
  }
}
{
  "input": {
    "title": "Write the launch post",
    "done": false
  }
}
{
  "data": {
    "addTask": { "id": "t_101", "title": "Write the launch post", "done": false }
  }
}

The schema behind the examples

Mutation type and input types

Input types use the input keyword. TaskPatch has no required fields, so an update can send only what changed.

type Task {
  id: ID!
  title: String!
  done: Boolean!
}

input TaskInput {
  title: String!
  done: Boolean = false
}

input TaskPatch {
  title: String
  done: Boolean
}

type Mutation {
  addTask(input: TaskInput!): Task!
  updateTask(id: ID!, input: TaskPatch!): Task
  deleteTask(id: ID!): ID
}

Sending it from JavaScript

POST the mutation with fetch

The document and the variables travel as two keys of one JSON body. Check errors even when the status is 200.

const ADD_TASK = `mutation AddTask($input: TaskInput!) {
  addTask(input: $input) { id title done }
}`;

async function addTask(title) {
  const res = await fetch("/graphql", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ query: ADD_TASK, variables: { input: { title } } }),
  });
  const { data, errors } = await res.json();
  if (errors?.length) throw new Error(errors[0].message);
  return data.addTask;
}

Two writes in one operation

Aliases let the same field run twice. The second runs only after the first finishes, but a failure in it does not undo the first.

mutation TwoTasks {
  first: addTask(input: { title: "Draft" }) { id }
  second: addTask(input: { title: "Review" }) { id }
}

Anatomy of a mutation

On the server, the schema declares a Mutation type whose fields are the writes clients may call, each with arguments and a return type. On the client, the operation names one or more of those fields, passes arguments and picks the result fields it wants back.

If the field returns an object type, a selection set is required, just like in a query. Asking for the new id and any fields the screen shows lets the client update its view straight from the response.

Variables and input objects

Variables are declared after the operation name with a dollar sign and a type, such as $input: TaskInput!. The exclamation mark makes the variable required. The values travel in a separate variables object in the request body.

An input object type is declared with the input keyword instead of type. Its fields can only be scalars, enums, lists or other input types, which keeps the shape of what clients send simple to validate.

Errors from a write

If the document is invalid, for example a misspelled field or a missing required variable, the server answers with errors and no data, and nothing is written.

If the write itself fails during execution, the field comes back null and an errors entry carries a message and a path. Many schemas also return expected problems, such as a title that is too long, as fields of a payload type so the client can show them next to the form.

How it works

  1. Choose create, update or delete.
  2. Type a title for the task; the variables JSON updates as you type.
  3. Copy the mutation and the variables, and send them as the query and variables keys of one POST.
  4. Compare your server's reply with the sample response shown below them.

Tips

Frequently asked questions

What is the difference between a query and a mutation?

Both use the same syntax and both can return data. By convention queries only read, and mutations write. The execution rule differs too: query fields may resolve in parallel, while top-level mutation fields run in series.

Why use an input type instead of plain arguments?

An input object groups related values into one typed argument, so the mutation signature stays short and you can add optional fields later without changing every caller.

Can I send a mutation with a GET request?

No. The GraphQL over HTTP guidance lets servers accept GET for query operations only. Mutations go as POST, which also keeps writes away from browser and CDN caches.

Is a mutation with several fields atomic?

No. graphql.org notes that serial execution is not a database transaction. If the second field fails, the first one's change stays, and the response holds data for one and an error for the other.

What should a delete mutation return?

The spec leaves that to you, but the field still needs an output type. Common choices are the deleted object's id, the removed object itself, or a payload type with the id and a success flag.

Related: GraphQL vs REST, GraphQL fragments, GraphQL subscriptions, JavaScript async and fetch.

Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.

More in Guides

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap