[readonly] markdown buffer
GraphQL Mutations in LWC
GraphQL reads gave LWC a concise client-side data layer, but writes still commonly passed through an Apex controller. In API 66.0 and later, generally available GraphQL mutations close that gap for UI API-supported records.
They do not replace Apex. They remove it from standard CRUD paths where it adds no domain behaviour.
The basic LWC pattern
Mutations are imperative rather than wired:
import { gql, executeMutation } from 'lightning/graphql';
async function runMutation(query, variables, operationName) {
const response = await executeMutation({ query, variables, operationName });
if (response.errors?.length) {
throw new Error(response.errors.map((error) => error.message).join('; '));
}
return response.data;
}
Use variables rather than building GraphQL text from user input, and handle errors on every request.
Create, update and delete
A create operation can stay entirely in the component:
const CREATE_EXPENSE = gql`
mutation CreateExpense($input: Expense__cCreateInput!) {
uiapi {
Expense__cCreate(input: $input) {
Record {
Id
Name { value }
}
}
}
}
`;
const data = await runMutation(CREATE_EXPENSE, {
input: {
Expense__c: {
Description__c: this.description
}
}
}, 'CreateExpense');
Update and delete follow the same shape with the generated object-specific input types. The response contract is stated in the operation rather than an Apex DTO.
For simple forms, that means fewer controller methods, Apex test classes and deployment artefacts. The component owns the interaction while UI API supplies the record operation.
Multiple operations in one request
Aliases let one mutation contain several named operations. Later operations can reference the result of an earlier alias, which supports parent-then-child or create-then-update workflows without another network round trip.
Transactional intent must be explicit:
allOrNone: truerolls everything back when one operation fails.allOrNone: falseallows independent operations to commit, while failed operations and their dependants fail.
Do not leave that choice implicit for a business-critical workflow.
The security boundary moves
GraphQL mutations use UI API behaviour, including the current user's object, field and record access. That is the correct default for UI-driven CRUD.
The migration trap is controller-only validation. If a business rule exists only inside an @AuraEnabled method, a GraphQL write never calls it.
Rules that must hold for every write belong in a layer every path reaches:
- validation rules
- before-save Flows
- triggers or trigger-invoked domain logic
- object permissions, field security and sharing
Client-side checks remain useful feedback. They are not authoritative enforcement.
When GraphQL is the right tool
Use mutations for focused creates, updates and deletes on supported objects when the operation is primarily a UI interaction.
Keep Apex for:
- complex domain rules
- external callouts
- orchestration across services
- specialised transaction or retry behaviour
- a custom error contract the standard mutation response cannot express
Existing Apex-backed components do not need a mass migration. Start with low-risk CRUD screens where the controller is only forwarding fields into DML.
A smaller read/write stack
GraphQL queries and mutations give an LWC one consistent API style for reading, writing and refreshing UI state. The gain is not “no more Apex”. It is being able to reserve Apex for the places where server-side code adds real value.