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.
| Part | Example | What it does |
|---|---|---|
| Keyword and name | mutation AddTask | Marks the operation as a write and names it for logs |
| Variable definitions | ($input: TaskInput!) | Declares typed values sent separately as JSON |
| Root field | addTask(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.
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 }
}
}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
}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;
}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 }
}
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 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.
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.
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.
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.
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.
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.
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.
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.