Skip to main content
Perform mutations and side effects with the createMutation function. It provides methods to trigger mutations and tracks their state reactively in Svelte 5.

Signature

function createMutation<TData, TError, TVariables, TContext>(
  options: Accessor<CreateMutationOptions<TData, TError, TVariables, TContext>>,
  queryClient?: Accessor<QueryClient>,
): CreateMutationResult<TData, TError, TVariables, TContext>

Parameters

options
Accessor<CreateMutationOptions<TData, TError, TVariables, TContext>>
required
A function (accessor) returning mutation configuration options.
queryClient
Accessor<QueryClient>
Accessor returning a custom QueryClient instance. If not provided, uses the client from context.

Returns

CreateMutationResult<TData, TError, TVariables, TContext>
object
Reactive mutation state and methods.

Type Parameters

  • TData - Type of data returned by the mutation
  • TError - Type of error (defaults to DefaultError)
  • TVariables - Type of variables passed to the mutation (defaults to void)
  • TContext - Type of context returned by onMutate (defaults to unknown)

Examples

Basic Usage (Svelte 5 Runes)

<script lang="ts">
import { createMutation, useQueryClient } from '@tanstack/svelte-query'

const queryClient = useQueryClient()

const mutation = createMutation(() => ({
  mutationFn: async (newTodo) => {
    const res = await fetch('/api/todos', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(newTodo),
    })
    return res.json()
  },
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
}))

function handleCreate() {
  mutation.mutate({ title: 'New Todo', completed: false })
}
</script>

<button onclick={handleCreate} disabled={mutation.isPending}>
  {mutation.isPending ? 'Adding...' : 'Add Todo'}
</button>

{#if mutation.isError}
  <div>Error: {mutation.error?.message}</div>
{/if}

With TypeScript

<script lang="ts">
import { createMutation } from '@tanstack/svelte-query'

interface Todo {
  id: number
  title: string
  completed: boolean
}

interface CreateTodoVariables {
  title: string
  completed: boolean
}

const mutation = createMutation(() => ({
  mutationFn: async (variables: CreateTodoVariables): Promise<Todo> => {
    const res = await fetch('/api/todos', {
      method: 'POST',
      body: JSON.stringify(variables),
    })
    return res.json()
  },
}))

function handleCreate() {
  // Type-safe mutation call
  mutation.mutate({ title: 'Learn Svelte Query', completed: false })
}
</script>

Optimistic Updates

<script lang="ts">
import { createMutation, useQueryClient } from '@tanstack/svelte-query'

const queryClient = useQueryClient()

const mutation = createMutation(() => ({
  mutationFn: updateTodo,
  onMutate: async (updatedTodo) => {
    // Cancel outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['todos'] })
    
    // Snapshot previous value
    const previousTodos = queryClient.getQueryData(['todos'])
    
    // Optimistically update cache
    queryClient.setQueryData(['todos'], (old) => {
      return old.map(todo => 
        todo.id === updatedTodo.id ? updatedTodo : todo
      )
    })
    
    // Return context with snapshot
    return { previousTodos }
  },
  onError: (err, updatedTodo, context) => {
    // Rollback on error
    if (context?.previousTodos) {
      queryClient.setQueryData(['todos'], context.previousTodos)
    }
  },
  onSettled: () => {
    // Always refetch after error or success
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
}))
</script>

Using mutateAsync

<script lang="ts">
import { createMutation } from '@tanstack/svelte-query'

const mutation = createMutation(() => ({
  mutationFn: createTodo,
}))

async function handleSubmit() {
  try {
    const newTodo = await mutation.mutateAsync({ title: 'New Todo' })
    console.log('Created todo:', newTodo)
    // Navigate or show success message
  } catch (error) {
    console.error('Failed to create todo:', error)
  }
}
</script>

<button onclick={handleSubmit}>Submit</button>

Per-Mutation Callbacks

<script lang="ts">
import { createMutation } from '@tanstack/svelte-query'

const mutation = createMutation(() => ({
  mutationFn: createTodo,
}))

function handleCreate() {
  mutation.mutate(
    { title: 'New Todo' },
    {
      onSuccess: (data) => {
        console.log('Created:', data)
      },
      onError: (error) => {
        console.error('Failed:', error)
      },
    }
  )
}
</script>

Reset Mutation State

<script lang="ts">
import { createMutation } from '@tanstack/svelte-query'

const mutation = createMutation(() => ({
  mutationFn: createTodo,
}))

function handleCreate() {
  mutation.reset() // Reset previous state
  mutation.mutate({ title: 'New Todo' })
}
</script>

<button onclick={handleCreate}>Create</button>

{#if mutation.isSuccess}
  <div>Successfully created!</div>
{/if}

Multiple Mutations

<script lang="ts">
import { createMutation, useQueryClient } from '@tanstack/svelte-query'

const queryClient = useQueryClient()

const createMutation = createMutation(() => ({
  mutationFn: createTodo,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
}))

const deleteMutation = createMutation(() => ({
  mutationFn: deleteTodo,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
}))
</script>

<button 
  onclick={() => createMutation.mutate({ title: 'New' })}
  disabled={createMutation.isPending}
>
  Create
</button>

<button 
  onclick={() => deleteMutation.mutate(1)}
  disabled={deleteMutation.isPending}
>
  Delete
</button>

Error Handling with Retry

<script lang="ts">
import { createMutation } from '@tanstack/svelte-query'

const mutation = createMutation(() => ({
  mutationFn: createTodo,
  retry: 3,
  retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
  onError: (error) => {
    console.error('Mutation failed after retries:', error)
  },
}))
</script>

Reactive Mutation Options

<script lang="ts">
import { createMutation } from '@tanstack/svelte-query'

let shouldRetry = $state(true)

const mutation = createMutation(() => ({
  mutationFn: createTodo,
  retry: shouldRetry ? 3 : 0,
}))
</script>

<label>
  <input type="checkbox" bind:checked={shouldRetry} />
  Enable retry
</label>

Form Handling

<script lang="ts">
import { createMutation } from '@tanstack/svelte-query'

let title = $state('')

const mutation = createMutation(() => ({
  mutationFn: createTodo,
  onSuccess: () => {
    title = '' // Clear form on success
  },
}))

function handleSubmit(e: Event) {
  e.preventDefault()
  mutation.mutate({ title, completed: false })
}
</script>

<form onsubmit={handleSubmit}>
  <input 
    type="text" 
    bind:value={title} 
    placeholder="Todo title"
    disabled={mutation.isPending}
  />
  <button type="submit" disabled={mutation.isPending}>
    {mutation.isPending ? 'Adding...' : 'Add Todo'}
  </button>
</form>

{#if mutation.isError}
  <p class="error">{mutation.error?.message}</p>
{/if}

{#if mutation.isSuccess}
  <p class="success">Todo added successfully!</p>
{/if}

Loading States

<script lang="ts">
import { createMutation } from '@tanstack/svelte-query'

const mutation = createMutation(() => ({
  mutationFn: createTodo,
}))
</script>

<button onclick={() => mutation.mutate({ title: 'New' })}>
  Create Todo
</button>

{#if mutation.isPending}
  <div>Creating...</div>
{:else if mutation.isError}
  <div>Error: {mutation.error?.message}</div>
{:else if mutation.isSuccess}
  <div>Todo created successfully!</div>
{/if}

Notes

Svelte Query uses Svelte 5’s runes mode. The options parameter must be an accessor (function).
Use mutate for fire-and-forget operations and mutateAsync when you need to await the result or handle errors with try/catch.
Always invalidate or update relevant queries after a successful mutation to keep your UI in sync with the server state.

Build docs developers (and LLMs) love