Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tanstack/query/llms.txt
Use this file to discover all available pages before exploring further.
This guide will walk you through creating your first Svelte Query application, covering queries, mutations, and common patterns.
Prerequisites
Before starting, make sure you have:
- Svelte 5.25.0 or higher installed
- A Svelte or SvelteKit project set up
@tanstack/svelte-query installed
If you haven’t installed Svelte Query yet, see the Installation Guide.
Your First Query
Set up the QueryClient
First, create a QueryClient and wrap your app with QueryClientProvider in your root layout:<script lang="ts">
import { QueryClient, QueryClientProvider } from '@tanstack/svelte-query'
const queryClient = new QueryClient()
const { children } = $props()
</script>
<QueryClientProvider client={queryClient}>
{@render children()}
</QueryClientProvider>
Create your first query
Now create a component that fetches data using createQuery:<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'
interface Post {
id: number
title: string
body: string
}
async function fetchPosts(): Promise<Post[]> {
const response = await fetch('https://jsonplaceholder.typicode.com/posts')
if (!response.ok) throw new Error('Failed to fetch posts')
return response.json()
}
const postsQuery = createQuery(() => ({
queryKey: ['posts'],
queryFn: fetchPosts,
}))
</script>
<div>
<h1>Posts</h1>
{#if postsQuery.isPending}
<p>Loading posts...</p>
{:else if postsQuery.isError}
<p>Error: {postsQuery.error.message}</p>
{:else}
<ul>
{#each postsQuery.data as post}
<li>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
{/each}
</ul>
{/if}
</div>
The createQuery function takes an accessor function () => options that returns the query configuration. This allows the query to react to changes in dependencies.
Understanding query states
Svelte Query provides several state properties to handle different scenarios:{#if postsQuery.isPending}
<!-- Query is loading for the first time -->
<Spinner />
{:else if postsQuery.isError}
<!-- Query encountered an error -->
<ErrorMessage error={postsQuery.error} />
{:else if postsQuery.isSuccess}
<!-- Query succeeded and data is available -->
<DataView data={postsQuery.data} />
{/if}
<!-- Background refetch indicator -->
{#if postsQuery.isFetching}
<div class="refetch-indicator">Updating...</div>
{/if}
Key states:
isPending - Query has no data yet (initial load)
isError - Query failed
isSuccess - Query succeeded
isFetching - Query is fetching (includes background refetches)
data - The actual query data
error - The error object if query failed
Dynamic Queries
Queries can depend on reactive variables. The query automatically refetches when dependencies change:
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'
let postId = $state(1)
const postQuery = createQuery(() => ({
queryKey: ['post', postId],
queryFn: async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${postId}`
)
return response.json()
},
}))
</script>
<div>
<button onclick={() => postId--}>Previous</button>
<button onclick={() => postId++}>Next</button>
{#if postQuery.data}
<h1>{postQuery.data.title}</h1>
<p>{postQuery.data.body}</p>
{/if}
</div>
When postId changes, the query key ['post', postId] changes, triggering an automatic refetch with the new ID.
Mutations
Use createMutation to create, update, or delete data:
<script lang="ts">
import { createMutation, useQueryClient } from '@tanstack/svelte-query'
const queryClient = useQueryClient()
interface NewPost {
title: string
body: string
}
const createPostMutation = createMutation(() => ({
mutationFn: async (newPost: NewPost) => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost),
})
return response.json()
},
onSuccess: () => {
// Invalidate and refetch posts query
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
}))
let title = $state('')
let body = $state('')
function handleSubmit() {
createPostMutation.mutate({ title, body })
title = ''
body = ''
}
</script>
<form onsubmit={handleSubmit}>
<input bind:value={title} placeholder="Title" />
<textarea bind:value={body} placeholder="Body" />
<button
type="submit"
disabled={createPostMutation.isPending}
>
{createPostMutation.isPending ? 'Creating...' : 'Create Post'}
</button>
{#if createPostMutation.isError}
<p class="error">{createPostMutation.error.message}</p>
{/if}
</form>
Mutation States
Mutations provide similar state properties:
isPending - Mutation is in progress
isError - Mutation failed
isSuccess - Mutation succeeded
data - The mutation result data
error - The error object if mutation failed
mutate() - Function to trigger the mutation
mutateAsync() - Promise-based mutation function
Query Options
Customize query behavior with various options:
const query = createQuery(() => ({
queryKey: ['posts', { status: 'published' }],
queryFn: fetchPublishedPosts,
// Refetch interval (ms)
refetchInterval: 10000,
// Only refetch on window focus if data is stale
refetchOnWindowFocus: 'always',
// How long data stays fresh
staleTime: 5 * 60 * 1000, // 5 minutes
// Cache time
gcTime: 10 * 60 * 1000, // 10 minutes
// Retry failed requests
retry: 3,
// Enable/disable the query
enabled: true,
}))
All time values are in milliseconds. Use staleTime to control when data is considered “stale” and needs refetching.
Infinite Queries
For paginated or infinite scroll data, use createInfiniteQuery:
<script lang="ts">
import { createInfiniteQuery } from '@tanstack/svelte-query'
interface PostsResponse {
posts: Array<{ id: number; title: string }>
nextCursor: number | null
}
const query = createInfiniteQuery(() => ({
queryKey: ['posts', 'infinite'],
queryFn: async ({ pageParam = 1 }) => {
const response = await fetch(`/api/posts?page=${pageParam}`)
return response.json() as Promise<PostsResponse>
},
initialPageParam: 1,
getNextPageParam: (lastPage) => lastPage.nextCursor,
}))
</script>
<div>
{#if query.data}
{#each query.data.pages as page}
{#each page.posts as post}
<article>
<h2>{post.title}</h2>
</article>
{/each}
{/each}
{/if}
<button
onclick={() => query.fetchNextPage()}
disabled={!query.hasNextPage || query.isFetchingNextPage}
>
{#if query.isFetchingNextPage}
Loading more...
{:else if query.hasNextPage}
Load More
{:else}
No more posts
{/if}
</button>
</div>
Infinite Query Properties
data.pages - Array of all fetched pages
data.pageParams - Array of all page parameters
hasNextPage - Whether more pages are available
hasPreviousPage - Whether previous pages are available
fetchNextPage() - Load the next page
fetchPreviousPage() - Load the previous page
isFetchingNextPage - Next page is loading
isFetchingPreviousPage - Previous page is loading
Query Invalidation
Invalidate queries to force them to refetch:
<script lang="ts">
import { useQueryClient } from '@tanstack/svelte-query'
const queryClient = useQueryClient()
// Invalidate all queries
queryClient.invalidateQueries()
// Invalidate specific query
queryClient.invalidateQueries({ queryKey: ['posts'] })
// Invalidate queries matching a pattern
queryClient.invalidateQueries({ queryKey: ['posts', { status: 'draft' }] })
</script>
Using queryOptions Helper
For better type safety and reusability, use the queryOptions helper:
import { queryOptions } from '@tanstack/svelte-query'
export const postsQueryOptions = queryOptions({
queryKey: ['posts'],
queryFn: async () => {
const response = await fetch('/api/posts')
return response.json()
},
staleTime: 5 * 60 * 1000,
})
Then use it in your components:
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'
import { postsQueryOptions } from './queries'
const postsQuery = createQuery(() => postsQueryOptions)
</script>
The queryOptions helper provides better type inference and makes it easier to share query configurations across components.
Best Practices
Use meaningful query keys
Query keys should describe the data uniquely:// Good
['posts', { status: 'published', author: userId }]
['user', userId]
['todos', { filter: 'completed' }]
// Bad
['data']
['fetch']
['query1']
Handle loading and error states
Always provide feedback for pending and error states:{#if query.isPending}
<LoadingSpinner />
{:else if query.isError}
<ErrorMessage error={query.error} />
{:else}
<DataDisplay data={query.data} />
{/if}
Configure staleTime appropriately
Set staleTime based on how often your data changes:// User profile (rarely changes)
staleTime: 10 * 60 * 1000 // 10 minutes
// Real-time data (changes frequently)
staleTime: 0
// Dashboard stats (moderate updates)
staleTime: 60 * 1000 // 1 minute
Invalidate queries after mutations
Keep your UI in sync by invalidating related queries:onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
queryClient.invalidateQueries({ queryKey: ['user', userId] })
}
Common Patterns
Dependent Queries
Execute a query only after another query succeeds:
<script lang="ts">
let userId = $state(1)
const userQuery = createQuery(() => ({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
}))
const projectsQuery = createQuery(() => ({
queryKey: ['projects', userQuery.data?.id],
queryFn: () => fetchUserProjects(userQuery.data!.id),
enabled: !!userQuery.data,
}))
</script>
Optimistic Updates
Update UI immediately before server confirmation:
const mutation = createMutation(() => ({
mutationFn: updatePost,
onMutate: async (newPost) => {
await queryClient.cancelQueries({ queryKey: ['posts'] })
const previousPosts = queryClient.getQueryData(['posts'])
queryClient.setQueryData(['posts'], (old) => [...old, newPost])
return { previousPosts }
},
onError: (err, newPost, context) => {
queryClient.setQueryData(['posts'], context.previousPosts)
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
}))
Prefetching
Prefetch data before it’s needed:
<script lang="ts">
import { useQueryClient } from '@tanstack/svelte-query'
const queryClient = useQueryClient()
function handleMouseEnter(postId: number) {
queryClient.prefetchQuery({
queryKey: ['post', postId],
queryFn: () => fetchPost(postId),
})
}
</script>
<a href="/post/{post.id}" onmouseenter={() => handleMouseEnter(post.id)}>
{post.title}
</a>
Next Steps
Advanced Guides
Explore advanced patterns like SSR, persisting, and more.Guides →