Query Keys
Understanding query key structure and cache operations
Query Keys
Eden TanStack Query generates deterministic query keys from your route paths and input. These keys are used by TanStack Query for caching, deduplication, and invalidation.
Key Structure
Query keys follow this structure:
// queryKey() returns:
[['path', 'segments', 'method'], { input: { ... }, type: 'query' }]
// Examples:
eden.users.get.queryKey()
// => [['users', 'get'], { type: 'query' }]
eden.users.get.queryKey({ role: 'admin' })
// => [['users', 'get'], { input: { role: 'admin' }, type: 'query' }]
eden.users({ id: '1' }).get.queryKey()
// => [['users', 'get'], {
// pathParams: [{ pathIndex: 0, entries: [['id', '1']] }],
// type: 'query'
// }]The type field differentiates regular queries from infinite queries:
eden.posts.get.infiniteQueryKey({ limit: 10 })
// => [['posts', 'get'], {
// input: { limit: 10 },
// type: 'infinite',
// infinite: { initialPageParam: null }
// }]Cache Identity
Path parameters
Path parameters are stored separately from request input and retain their path position and application order. A query field cannot overwrite path identity, and sequential parameters with the same name remain distinct.
Request headers
Per-request headers are included in the input part of the query key. Different tenant, authorization, locale, or other response-varying headers therefore create separate cache entries.
Header values are visible in TanStack Query Devtools and can be serialized by dehydration or persistence. Treat raw secrets in per-request headers as an explicit cache-identity trade-off, and only use them when that exposure is understood and controlled.
skipToken
skipToken is supported by queryOptions() and infiniteQueryOptions() to create disabled queries. It is not valid input for key-only helpers: queryKey(skipToken) and infiniteQueryKey(skipToken) throw an error.
Available Methods
queryKey()
Returns the query key for a specific route. Optionally accepts input for more specific targeting:
eden.users.get.queryKey() // all users queries
eden.users.get.queryKey({ role: 'admin' }) // specific input
eden.users({ id: '1' }).get.queryKey() // specific path paramsqueryFilter()
Returns a filter object (including queryKey) for use with TanStack Query's filter-based methods:
eden.users.get.queryFilter()
eden.users.get.queryFilter({ role: 'admin' })
eden.users.get.queryFilter(undefined, { exact: true, stale: true })queryKey() generates keys with type: "query", which only matches regular queries. queryFilter() uses type: "any", which matches both regular and infinite queries — making it better suited for broad cache invalidation.
infiniteQueryKey()
Returns the query key for infinite queries:
eden.posts.get.infiniteQueryKey()
eden.posts.get.infiniteQueryKey({ limit: 10 })
eden.posts.get.infiniteQueryKey({ limit: 10 }, { initialCursor: 'start' })The initial cursor is part of an exact infinite-query key. The helper defaults
it to null for optional cursor routes, matching infiniteQueryOptions().
Required cursor routes must pass the same explicit non-null initialCursor to
both helpers.
infiniteQueryFilter()
Returns a filter object for infinite queries:
eden.posts.get.infiniteQueryFilter()
eden.posts.get.infiniteQueryFilter({ category: 'tech' })
eden.posts.get.infiniteQueryFilter(
{ category: 'tech' },
{ initialCursor: 'start' }
)Infinite-query filters match all initial-cursor variants by default. Pass
initialCursor in the filter options to target one variant. With the default
hash, cursor values are compared as complete values rather than partial object
or array prefixes, while route input keeps TanStack Query's normal partial
matching. A custom queryKeyHashFn defines cursor identity just as it does for
cache entries. On optional cursor routes, exact: true uses the default null
cursor when initialCursor is omitted. Required cursor routes allow an omitted
cursor only for broad filters with exact absent or false; exact and
cursor-specific filters require an explicit non-null cursor.
mutationKey()
Returns the mutation key for a mutation route:
eden.users.post.mutationKey()
// => [['users', 'post']]Cache Operations
invalidateQueries
Mark queries as stale and trigger a refetch:
const queryClient = useQueryClient()
const eden = useEden()
// Invalidate all users queries (partial matching)
queryClient.invalidateQueries({
queryKey: eden.users.get.queryKey()
})
// Invalidate with queryFilter
queryClient.invalidateQueries(
eden.users.get.queryFilter()
)
// Invalidate a specific user
queryClient.invalidateQueries({
queryKey: eden.users({ id: '1' }).get.queryKey()
})cancelQueries
Cancel in-flight queries (useful for optimistic updates):
await queryClient.cancelQueries({
queryKey: eden.todos.get.queryKey()
})getQueryData / setQueryData
Read and write the cache directly:
// Read cached data
const user = queryClient.getQueryData(
eden.users({ id: '1' }).get.queryKey()
)
// Write to cache
queryClient.setQueryData(
eden.users({ id: '1' }).get.queryKey(),
{ id: '1', name: 'Updated Name', email: 'updated@example.com' }
)removeQueries
Remove queries from the cache entirely:
queryClient.removeQueries({
queryKey: eden.users({ id: '1' }).get.queryKey()
})Partial Matching
TanStack Query uses partial matching for query keys. A key without input matches all queries for that route:
// Invalidates ALL users queries, regardless of input
queryClient.invalidateQueries({
queryKey: eden.users.get.queryKey()
})
// Only invalidates the specific query with role='admin'
queryClient.invalidateQueries({
queryKey: eden.users.get.queryKey({ role: 'admin' })
})This is useful for broad invalidation after mutations:
const createUser = useMutation({
...eden.users.post.mutationOptions(),
onSuccess: () => {
// Invalidates all users queries (list, filtered, by id, etc.)
queryClient.invalidateQueries({
queryKey: eden.users.get.queryKey()
})
},
})For more details on how partial matching works, see the TanStack Query docs on query invalidation.