Mutations
Send a mutation to the server and update your client-side cache with any changes.
Basic Usage
<script> import { graphql } from '$houdini'
const uncheckItem = graphql(` mutation UncheckItem($id: ID!) { uncheckItem(item: $id) { item { id completed } } } `)</script>
<button onclick={() => uncheckItem.mutate({ id: 'my-item' })}> Uncheck Item</button>mutate invokes the mutation with the variables passed as the first argument. The second argument configures its behavior:
optimisticResponse: a value to apply to the cache immediately, before the server responds. See Optimistic Updates.
Mutations usually do best when combined with at least one fragment grabbing the information needed for the mutation. See Updating Fields below for an example of this pattern.
SvelteKit Form Actions
Using a mutation inside a form action looks the same as anywhere else. Pass the RequestEvent to mutate so SvelteKit’s fetch handling works correctly (header forwarding, handleFetch hook, etc.):
import { graphql } from '$houdini'import { fail } from '@sveltejs/kit'import type { Actions } from './$types'
export const actions: Actions = { add: async (event) => { const data = await event.request.formData() const name = data.get('name')?.toString()
if (!name) { return fail(403, { name: '*' }) }
const addUser = graphql(` mutation AddUser($name: String!) { addUser(name: $name) { id name } } `)
return await addUser.mutate({ name }, { event }) }}Updating Fields
When a mutation updates fields on existing records, Houdini handles the cache update automatically as long as you request the updated data alongside the record’s id.
Here’s a typical pattern combining a fragment with a mutation in the same component:
<script> import { fragment, graphql } from '$houdini' import type { TodoItemRow } from '$houdini'
let { item }: { item: TodoItemRow } = $props()
const data = $derived(fragment(item, graphql(` fragment TodoItemRow on TodoItem { id text completed } `)))
const checkItem = graphql(` mutation CheckItem($id: ID!) { checkItem(item: $id) { item { id completed } } } `)</script>
<li class:completed={$data.completed}> <input name={$data.text} class="toggle" type="checkbox" checked={$data.completed} onclick={() => checkItem.mutate({ id: $data.id })} /> <label for={$data.text}>{$data.text}</label></li>Refetching Changed Data
Most of the time, you’re best off including the changed fields in the mutation itself,
either by requesting them directly or by spreading the fragments your components already
define. That keeps everything to a single round trip and gives the fastest experience.
Sometimes that isn’t possible, though, or it gets unwieldy. In those cases you can use the
@refetch directive, which tells Houdini to find the queries that depend on the returned
entity and refetch them.
mutation FavoriteBook($id: ID!) { favoriteBook(id: $id) { book @refetch { id } }}A few things to keep in mind:
@refetchgoes on the field that returns the entity, not on itsid. That’s how Houdini knows which record changed.- It also works on a field that returns a list of entities, in which case every record in the list is refreshed.
- It can’t be combined with
@listor@paginate, which already keep their data in sync.
Deduplication
By default, nothing stops the same operation from running multiple times simultaneously. The @dedupe directive gives us control over that behavior:
query UserProfile($id: ID!) @dedupe { user(id: $id) { name }}With no arguments, @dedupe prevents a second request from firing if an identical one is already in-flight. To cancel the first request instead of dropping the second, pass cancelFirst: true:
query UserProfile($id: ID!) @dedupe(cancelFirst: true) { user(id: $id) { name }}The match argument controls what counts as "identical":
Operation: dedupe if any execution of this operation is pending, regardless of variables (default)Variables: dedupe only if the variables also matchNone: never dedupe