GraphQL Mutations in Data SDK
GraphQL mutations are operations that create, update, or delete Salesforce records. Use the graphql.mutate() method for all mutation operations.
Unlike queries, mutations:
Are never cached and don’t write to the cache
Return MutationResult<T> with only data and errors fields
Don’t provide subscription support
Don’t write to the cache, so they won’t affect subsequent query results
1 const result = await dataSdk . graphql ?. mutate < MutationResponse , MutationVariables > ( {
2 mutation: MUTATION_STRING ,
3 variables: { ... } ,
4 operationName? : "..."
5 } ) ;
6
7 // Check for errors
8 if ( result ?. errors ?. length ) {
9 throw new Error ( result . errors . map ( ( e ) = > e . message ) . join ( "; " ) ) ;
10 }
11
12 // Use the returned data
13 const createdRecord = result ?. data ;
Create Record
Create a new record using the <ObjectName>Create mutation.
Step 1: Define the Mutation
A mutation operation follows the RecordCreate name pattern, for example, AccountCreate. See Mutations Schema in the GraphQL API Developer Guide .
1 # uiBundles/yourAppName/src/api/account/query/createAccount.graphql
2 mutation CreateAccount ( $input : AccountCreateInput ! ) {
3 uiapi {
4 AccountCreate ( input : $input ) {
5 Record {
6 Id
7 Name {
8 value
9 }
10 Industry {
11 value
12 }
13 Phone {
14 value
15 }
16 Website {
17 value
18 }
19 }
20 }
21 }
22 }
Step 2: Generate Types
Run npm run graphql:codegen to generate TypeScript types.
1 // Generated in src/api/graphql-operations-types.ts
2 export type CreateAccountMutation = {
3 uiapi : {
4 AccountCreate : {
5 Record : {
6 Id : string ;
7 Name : { value : string } | null ;
8 Industry : { value : string } | null ;
9 Phone : { value : string } | null ;
10 Website : { value : string } | null ;
11 } ;
12 } ;
13 } ;
14 } ;
15
16 export type CreateAccountMutationVariables = {
17 input : AccountCreateInput ;
18 } ;
19
20 export type AccountCreateInput = {
21 Account : {
22 Name ?: string ;
23 Industry ?: string ;
24 Phone ?: string ;
25 Website ?: string ;
26 // ... other fields
27 } ;
28 } ;
Step 3: Implement the Mutation Function
Implement the client-side mutation function that invokes dataSdk.graphql?.mutate() with the generated types.
1 // uiBundles/yourAppName/src/api/account/accountCreate.ts
2 import { createDataSDK } from "@salesforce/platform-sdk/data" ;
3 import CREATE_ACCOUNT from "./mutation/createAccount.graphql?raw" ;
4 import type {
5 CreateAccountMutation ,
6 CreateAccountMutationVariables ,
7 } from "../graphql-operations-types" ;
8
9 export interface CreateAccountInput {
10 name : string ;
11 industry ?: string ;
12 phone ?: string ;
13 website ?: string ;
14 }
15
16 export async function createAccount ( input : CreateAccountInput ) {
17 const dataSdk = await createDataSDK ( ) ;
18
19 const result = await dataSdk . graphql ?. mutate <
20 CreateAccountMutation ,
21 CreateAccountMutationVariables
22 > ( {
23 mutation: CREATE_ACCOUNT ,
24 variables: {
25 input: {
26 Account: {
27 Name: input . name ,
28 Industry: input . industry ,
29 Phone: input . phone ,
30 Website: input . website ,
31 } ,
32 } ,
33 } ,
34 } ) ;
35
36 if ( result ?. errors ?. length ) {
37 const errorMessages = result . errors . map ( ( e ) = > e . message ) . join ( "; " ) ;
38 throw new Error ( `Failed to create account: ${ errorMessages } ` ) ;
39 }
40
41 return result ?. data ?. uiapi ?. AccountCreate ?. Record ;
42 }
Step 4: Use in Your Component
This component example shows how to call createAccount, handle loading and error states, and react to the created result.
1 import { useState } from "react" ;
2 import { createAccount } from "./api/accounts" ;
3
4 function CreateAccountForm ( ) {
5 const [ name , setName ] = useState ( "" ) ;
6 const [ industry , setIndustry ] = useState ( "" ) ;
7 const [ loading , setLoading ] = useState ( false ) ;
8 const [ error , setError ] = useState < string | null > ( null ) ;
9
10 async function handleSubmit ( e : React . FormEvent ) {
11 e . preventDefault ( ) ;
12 setLoading ( true ) ;
13 setError ( null ) ;
14
15 try {
16 const newAccount = await createAccount ( { name , industry } ) ;
17 console . log ( "Created account:" , newAccount ) ;
18 // Clear form
19 setName ( "" ) ;
20 setIndustry ( "" ) ;
21 // Optionally navigate or refresh data
22 } catch ( err ) {
23 setError ( ( err as Error ) . message ) ;
24 } finally {
25 setLoading ( false ) ;
26 }
27 }
28
29 return (
30 < form onSubmit = { handleSubmit } >
31 < input
32 value = { name }
33 onChange = { ( e ) = > setName ( e.target.value ) }
34 placeholder = "Account Name"
35 required
36 / >
37 < input
38 value = { industry }
39 onChange = { ( e ) = > setIndustry ( e.target.value ) }
40 placeholder = "Industry"
41 / >
42 < button type = "submit" disabled = { loading } >
43 { loading ? "Creating..." : "Create Account" }
44 < / button >
45 { error && < div className = "error" > { error } < / div > }
46 < / form >
47 ) ;
48 }
Update Record
Update an existing record using the <ObjectName>Update mutation.
Step 1: Define the Mutation
1 # uiBundles/yourAppName/src/api/account/query/updateAccount.graphql
2 mutation UpdateAccount ( $input : AccountUpdateInput ! ) {
3 uiapi {
4 AccountUpdate ( input : $input ) {
5 Record {
6 Id
7 Name {
8 value
9 }
10 Industry {
11 value
12 }
13 AnnualRevenue {
14 value
15 }
16 Phone {
17 value
18 }
19 }
20 }
21 }
22 }
Step 2: Implement the Mutation Function
Implement the update function: call dataSdk.graphql?.mutate() with the update payload, then check and handle any returned errors.
1 import { createDataSDK } from "@salesforce/platform-sdk/data" ;
2 import UPDATE_ACCOUNT from "./mutation/updateAccount.graphql?raw" ;
3 import type {
4 UpdateAccountMutation ,
5 UpdateAccountMutationVariables ,
6 } from "../graphql-operations-types" ;
7
8 export interface UpdateAccountInput {
9 id : string ;
10 name ?: string ;
11 industry ?: string ;
12 annualRevenue ?: number ;
13 phone ?: string ;
14 }
15
16 export async function updateAccount ( input : UpdateAccountInput ) {
17 const dataSdk = await createDataSDK ( ) ;
18
19 const result = await dataSdk . graphql ?. mutate <
20 UpdateAccountMutation ,
21 UpdateAccountMutationVariables
22 > ( {
23 mutation: UPDATE_ACCOUNT ,
24 variables: {
25 input: {
26 Id: input . id ,
27 Account: {
28 Name: input . name ,
29 Industry: input . industry ,
30 AnnualRevenue: input . annualRevenue ,
31 Phone: input . phone ,
32 } ,
33 } ,
34 } ,
35 } ) ;
36
37 if ( result ?. errors ?. length ) {
38 const errorMessages = result . errors . map ( ( e ) = > e . message ) . join ( "; " ) ;
39 throw new Error ( `Failed to update account: ${ errorMessages } ` ) ;
40 }
41
42 return result ?. data ?. uiapi ?. AccountUpdate ?. Record ;
43 }
Step 3: Use in Your Component
This form component demonstrates using updateAccount and calling onSuccess after a successful update, while displaying errors when present.
1 // uiBundles/yourAppName/src/api/account/accountUpdate.ts
2 import { useState , useEffect } from "react" ;
3 import { updateAccount } from "./api/accounts" ;
4
5 interface EditAccountFormProps {
6 accountId : string ;
7 initialName : string ;
8 initialIndustry ?: string ;
9 onSuccess ?: ( ) = > void ;
10 }
11
12 function EditAccountForm ( {
13 accountId ,
14 initialName ,
15 initialIndustry ,
16 onSuccess ,
17 } : EditAccountFormProps ) {
18 const [ name , setName ] = useState ( initialName ) ;
19 const [ industry , setIndustry ] = useState ( initialIndustry || "" ) ;
20 const [ loading , setLoading ] = useState ( false ) ;
21 const [ error , setError ] = useState < string | null > ( null ) ;
22
23 async function handleSubmit ( e : React . FormEvent ) {
24 e . preventDefault ( ) ;
25 setLoading ( true ) ;
26 setError ( null ) ;
27
28 try {
29 await updateAccount ( {
30 id: accountId ,
31 name ,
32 industry ,
33 } ) ;
34 onSuccess ?. ( ) ;
35 } catch ( err ) {
36 setError ( ( err as Error ) . message ) ;
37 } finally {
38 setLoading ( false ) ;
39 }
40 }
41
42 return (
43 < form onSubmit = { handleSubmit } >
44 < input
45 value = { name }
46 onChange = { ( e ) = > setName ( e.target.value ) }
47 placeholder = "Account Name"
48 required
49 / >
50 < input
51 value = { industry }
52 onChange = { ( e ) = > setIndustry ( e.target.value ) }
53 placeholder = "Industry"
54 / >
55 < button type = "submit" disabled = { loading } >
56 { loading ? "Updating..." : "Update Account" }
57 < / button >
58 { error && < div className = "error" > { error } < / div > }
59 < / form >
60 ) ;
61 }
Delete Record
Delete a record using the <ObjectName>Delete mutation.
Step 1: Define the Mutation
Define the delete mutation in a .graphql file that accepts the delete input and returns the deleted record Id.
1 # uiBundles/yourAppName/src/api/account/query/deleteAccount.graphql
2 mutation DeleteAccount ( $input : AccountDeleteInput ! ) {
3 uiapi {
4 AccountDelete ( input : $input ) {
5 Id
6 }
7 }
8 }
Delete mutations only return the ID of the deleted record, not a Record object.
Step 2: Implement the Mutation Function
Implement the delete function that calls dataSdk.graphql?.mutate(), validates the result, and returns the deleted ID to callers.
1 import { createDataSDK } from "@salesforce/platform-sdk/data" ;
2 import DELETE_ACCOUNT from "./mutation/deleteAccount.graphql?raw" ;
3 import type {
4 DeleteAccountMutation ,
5 DeleteAccountMutationVariables ,
6 } from "../graphql-operations-types" ;
7
8 export async function deleteAccount ( id : string ) : Promise < string > {
9 const dataSdk = await createDataSDK ( ) ;
10
11 const result = await dataSdk . graphql ?. mutate <
12 DeleteAccountMutation ,
13 DeleteAccountMutationVariables
14 > ( {
15 mutation: DELETE_ACCOUNT ,
16 variables: {
17 input: {
18 Id: id ,
19 } ,
20 } ,
21 } ) ;
22
23 if ( result ?. errors ?. length ) {
24 const errorMessages = result . errors . map ( ( e ) = > e . message ) . join ( "; " ) ;
25 throw new Error ( `Failed to delete account: ${ errorMessages } ` ) ;
26 }
27
28 return result ?. data ?. uiapi ?. AccountDelete ?. Id || "" ;
29 }
Step 3: Use in Your Component
This example shows a button component that calls deleteAccount, displays progress, and handles any error returned by the mutation.
1 // uiBundles/yourAppName/src/api/account/accountDelete.ts
2 import { useState } from "react" ;
3 import { deleteAccount } from "./api/accounts" ;
4
5 interface DeleteAccountButtonProps {
6 accountId : string ;
7 accountName : string ;
8 onSuccess ?: ( ) = > void ;
9 }
10
11 function DeleteAccountButton ( { accountId , accountName , onSuccess } : DeleteAccountButtonProps ) {
12 const [ loading , setLoading ] = useState ( false ) ;
13 const [ error , setError ] = useState < string | null > ( null ) ;
14
15 async function handleDelete ( ) {
16 const confirmed = window . confirm ( `Are you sure you want to delete " ${ accountName } "?` ) ;
17
18 if ( ! confirmed ) return ;
19
20 setLoading ( true ) ;
21 setError ( null ) ;
22
23 try {
24 await deleteAccount ( accountId ) ;
25 onSuccess ?. ( ) ;
26 } catch ( err ) {
27 setError ( ( err as Error ) . message ) ;
28 } finally {
29 setLoading ( false ) ;
30 }
31 }
32
33 return (
34 < div >
35 < button onClick = { handleDelete } disabled = { loading } className = "danger-button" >
36 { loading ? "Deleting..." : "Delete" }
37 < / button >
38 { error && < div className = "error" > { error } < / div > }
39 < / div >
40 ) ;
41 }
Refresh Queries After Mutations
Since mutations don’t update the cache, you need to manually refresh related queries to reflect the changes in the UI.
Pattern 1: Refresh with QueryResult
If you have access to the QueryResult object from a previous query, call refresh():
1 import { useEffect , useState } from "react" ;
2 import { createDataSDK } from "@salesforce/platform-sdk/data" ;
3 import { createAccount } from "./api/accounts" ;
4 import type { QueryResult } from "@salesforce/platform-sdk/data" ;
5
6 function AccountManager ( ) {
7 const [ accountsResult , setAccountsResult ] = useState < QueryResult < AccountsData > > ( ) ;
8 const [ accounts , setAccounts ] = useState < Account [ ] > ( [ ] ) ;
9
10 useEffect ( ( ) = > {
11 async function loadAccounts ( ) {
12 const dataSdk = await createDataSDK ( ) ;
13 const result = await dataSdk . graphql ?. query < AccountsData > ( {
14 query: GET_ACCOUNTS ,
15 } ) ;
16
17 setAccountsResult ( result ) ;
18 setAccounts ( result ?. data ?. uiapi ?. query ?. Account ?. edges ?. map ( ( e ) = > e . node ) || [ ] ) ;
19
20 // Subscribe to updates
21 result ?. subscribe ( ( snapshot ) = > {
22 setAccounts ( snapshot . data ?. uiapi ?. query ?. Account ?. edges ?. map ( ( e ) = > e . node ) || [ ] ) ;
23 } ) ;
24 }
25 loadAccounts ( ) ;
26 } , [ ] ) ;
27
28 async function handleCreateAccount ( name : string ) {
29 try {
30 // Perform the mutation
31 await createAccount ( { name } ) ;
32
33 // Refresh the query to show the new record
34 if ( accountsResult ) {
35 await accountsResult . refresh ( ) ;
36 }
37 } catch ( error ) {
38 console . error ( "Failed to create account:" , error ) ;
39 }
40 }
41
42 return (
43 < div >
44 < button onClick = { ( ) = > handleCreateAccount ( "New Account" ) } > Create Account < / button >
45 < ul >
46 { accounts . map ( ( account ) = > (
47 < li key = { account. Id } > { account.Name?. value } < / li >
48 ) ) }
49 < / ul >
50 < / div >
51 ) ;
52 }
Pattern 2: Re-query After Mutation
If you don’t have access to the QueryResult, re-execute the query.
1 async function handleCreateAccount ( name : string ) {
2 try {
3 // Perform the mutation
4 await createAccount ( { name } ) ;
5
6 // Re-query
7 await loadAccounts ( ) ;
8 } catch ( error ) {
9 console . error ( "Failed to create account:" , error ) ;
10 }
11 }
Pattern 3: Optimistic Updates
You can choose to update the UI before the mutation completes.
1 async function handleDeleteAccount ( accountId : string ) {
2 // Optimistically remove from UI
3 setAccounts ( ( prev ) = > prev . filter ( ( a ) = > a . Id !== accountId ) ) ;
4
5 try {
6 await deleteAccount ( accountId ) ;
7 // Success - optimistic update was correct
8 } catch ( error ) {
9 console . error ( "Failed to delete account:" , error ) ;
10 // Revert optimistic update
11 if ( accountsResult ) {
12 await accountsResult . refresh ( ) ;
13 }
14 }
15 }
Mutation Best Practices
Here are several best practices for working with GraphQL mutation.
Select Id in Return Fields
Include Id in your mutation return fields.
1 mutation CreateAccount ( $input : AccountCreateInput ! ) {
2 uiapi {
3 AccountCreate ( input : $input ) {
4 Record {
5 Id # Always include this
6 Name {
7 value
8 }
9 }
10 }
11 }
12 }
Use Type-Safe Input Interfaces
Define interfaces for mutation inputs for type safety.
1 export interface CreateContactInput {
2 firstName : string ;
3 lastName : string ;
4 accountId ?: string ;
5 email ?: string ;
6 phone ?: string ;
7 }
8
9 export async function createContact ( input : CreateContactInput ) {
10 // Type-safe mutation implementation
11 }
Handle Loading States
Show loading states during mutations to notify users on what to expect.
1 const [ isCreating , setIsCreating ] = useState ( false ) ;
2
3 async function handleCreate ( ) {
4 setIsCreating ( true ) ;
5 try {
6 await createAccount ( { name: "New Account" } ) ;
7 } finally {
8 setIsCreating ( false ) ;
9 }
10 }
Provide User Feedback
Show success and error messages to communicate expectations with users.
1 const [ message , setMessage ] = useState < { type : "success" | "error" ; text : string } | null > ( null ) ;
2
3 async function handleCreate ( ) {
4 try {
5 await createAccount ( { name: "New Account" } ) ;
6 setMessage ( { type: "success" , text: "Account created successfully!" } ) ;
7 } catch ( error ) {
8 setMessage ( { type: "error" , text: ( error as Error ) . message } ) ;
9 }
10 }
Clean Up Form State
Reset form fields after successful mutations.
1 async function handleSubmit ( e : React . FormEvent ) {
2 e . preventDefault ( ) ;
3 try {
4 await createAccount ( { name , industry } ) ;
5 // Clear form on success
6 setName ( "" ) ;
7 setIndustry ( "" ) ;
8 } catch ( error ) {
9 // Handle error
10 }
11 }
Batch Related Mutations
If you must perform multiple mutations, consider batching them.
1 async function createAccountWithContacts (
2 accountData : CreateAccountInput ,
3 contacts : CreateContactInput [ ] ,
4 ) {
5 // Create account first
6 const account = await createAccount ( accountData ) ;
7
8 if ( ! account ?. Id ) {
9 throw new Error ( "Failed to create account" ) ;
10 }
11
12 // Create contacts with the new account ID
13 const contactPromises = contacts . map ( ( contact ) = >
14 createContact ( { ... contact , accountId: account . Id } ) ,
15 ) ;
16
17 await Promise . all ( contactPromises ) ;
18
19 return account ;
20 }
See Also