> ## 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.

# Query Keys

> Understanding query key structure and serialization in TanStack Query

Query keys are the foundation of TanStack Query's caching system. They uniquely identify queries and are used to manage cache data, invalidation, and refetching.

## Query Key Structure

Query keys must be arrays. The simplest form is an array with a single string:

```tsx theme={null}
// Simple key
useQuery({ queryKey: ['posts'], queryFn: fetchPosts })
```

Query keys can include multiple elements for hierarchical organization:

```tsx theme={null}
// Hierarchical keys
useQuery({ queryKey: ['posts', 'list'], queryFn: fetchPosts })
useQuery({ queryKey: ['posts', 'detail', postId], queryFn: () => fetchPost(postId) })
```

## Type Definition

From `types.ts:53-61`, query keys are defined as:

```typescript theme={null}
export type QueryKey = Register extends {
  queryKey: infer TQueryKey
}
  ? TQueryKey extends ReadonlyArray<unknown>
    ? TQueryKey
    : TQueryKey extends Array<unknown>
      ? TQueryKey
      : ReadonlyArray<unknown>
  : ReadonlyArray<unknown>
```

This means query keys are always arrays and can contain any serializable values.

<Note>
  TanStack Query v4+ requires query keys to be arrays. If you're migrating from v3, change string keys like `'posts'` to `['posts']`.
</Note>

## Query Key Best Practices

### 1. Hierarchical Structure

Organize keys from generic to specific:

```tsx theme={null}
// Good: hierarchical structure
useQuery({ queryKey: ['users'], queryFn: fetchUsers })
useQuery({ queryKey: ['users', userId], queryFn: () => fetchUser(userId) })
useQuery({ queryKey: ['users', userId, 'posts'], queryFn: () => fetchUserPosts(userId) })

// This allows partial invalidation:
queryClient.invalidateQueries({ queryKey: ['users'] }) // Invalidates all user-related queries
queryClient.invalidateQueries({ queryKey: ['users', userId] }) // Invalidates specific user
```

### 2. Include All Dependencies

Include all variables that the query function depends on:

```tsx theme={null}
function usePosts(filters: { status: string; page: number }) {
  return useQuery({
    queryKey: ['posts', filters], // Include filters in the key
    queryFn: () => fetchPosts(filters),
  })
}

// Different filters = different queries
usePosts({ status: 'published', page: 1 })
usePosts({ status: 'draft', page: 1 })
```

### 3. Consistent Ordering

Maintain consistent ordering of key elements:

```tsx theme={null}
// Good: consistent structure
useQuery({ queryKey: ['posts', { status, sort }], ... })
useQuery({ queryKey: ['posts', { status, sort }], ... })

// Bad: inconsistent structure
useQuery({ queryKey: ['posts', { status, sort }], ... })
useQuery({ queryKey: ['posts', { sort, status }], ... }) // Different order
```

<Warning>
  Object keys in query keys are **not** automatically sorted. Use a consistent order or the queries will be treated as different.
</Warning>

## Query Key Serialization

Query keys are serialized to a query hash using the `hashQueryKeyByOptions` function. This hash is used internally for cache lookups.

From `queryCache.ts:113-116`:

```typescript theme={null}
const queryKey = options.queryKey
const queryHash =
  options.queryHash ?? hashQueryKeyByOptions(queryKey, options)
let query = this.get<TQueryFnData, TError, TData, TQueryKey>(queryHash)
```

The hash function serializes the key deterministically:

```tsx theme={null}
// These create the same hash
queryKey: ['posts', { id: 1, status: 'active' }]
queryKey: ['posts', { id: 1, status: 'active' }]

// These create different hashes
queryKey: ['posts', { id: 1, status: 'active' }]
queryKey: ['posts', { status: 'active', id: 1 }] // Different object key order
```

## Array-Based Keys

### Simple Values

Arrays can contain strings, numbers, booleans, etc.:

```tsx theme={null}
useQuery({ queryKey: ['posts'], ... })
useQuery({ queryKey: ['post', 123], ... })
useQuery({ queryKey: ['user', userId, 'active'], ... })
```

### Objects in Keys

Objects are useful for complex filters:

```tsx theme={null}
const filters = {
  status: 'published',
  author: 'john',
  tags: ['react', 'typescript'],
}

useQuery({
  queryKey: ['posts', filters],
  queryFn: () => fetchPosts(filters),
})
```

### Nested Objects

Nested structures are supported:

```tsx theme={null}
useQuery({
  queryKey: ['posts', {
    filters: { status: 'published' },
    pagination: { page: 1, pageSize: 10 },
    sort: { field: 'createdAt', order: 'desc' },
  }],
  queryFn: () => fetchPosts(...),
})
```

## Query Key Factories

Create factory functions for consistent key generation:

```tsx theme={null}
const postKeys = {
  all: ['posts'] as const,
  lists: () => [...postKeys.all, 'list'] as const,
  list: (filters: PostFilters) => [...postKeys.lists(), filters] as const,
  details: () => [...postKeys.all, 'detail'] as const,
  detail: (id: number) => [...postKeys.details(), id] as const,
}

// Usage
useQuery({ queryKey: postKeys.all, ... })
useQuery({ queryKey: postKeys.list({ status: 'published' }), ... })
useQuery({ queryKey: postKeys.detail(123), ... })

// Invalidation
queryClient.invalidateQueries({ queryKey: postKeys.all }) // All posts
queryClient.invalidateQueries({ queryKey: postKeys.lists() }) // All lists
queryClient.invalidateQueries({ queryKey: postKeys.detail(123) }) // Specific post
```

<Tip>
  Use `as const` assertions to ensure type safety and prevent accidental mutations of query keys.
</Tip>

## Query Key Matching

### Exact Matching

By default, queries match exactly:

```tsx theme={null}
// Only invalidates queries with exactly ['posts']
queryClient.invalidateQueries({ queryKey: ['posts'], exact: true })
```

### Prefix Matching

Without `exact: true`, keys match by prefix:

```tsx theme={null}
// Invalidates:
// ['posts']
// ['posts', 'list']
// ['posts', 'detail', 123]
queryClient.invalidateQueries({ queryKey: ['posts'] })
```

### Partial Matching

The `matchQuery` function (used internally) supports partial matching:

```tsx theme={null}
// From queryCache.ts:183-191
find<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData>(
  filters: WithRequired<QueryFilters, 'queryKey'>,
): Query<TQueryFnData, TError, TData> | undefined {
  const defaultedFilters = { exact: true, ...filters }

  return this.getAll().find((query) =>
    matchQuery(defaultedFilters, query),
  ) as Query<TQueryFnData, TError, TData> | undefined
}
```

## Query Key in Query Function

The query key is available in the query function context:

```tsx theme={null}
useQuery({
  queryKey: ['posts', { status: 'published', page: 1 }],
  queryFn: ({ queryKey }) => {
    const [_key, filters] = queryKey
    return fetchPosts(filters)
  },
})
```

From the source, the context includes the queryKey at `query.ts:450-461`:

```typescript theme={null}
const createQueryFnContext = (): QueryFunctionContext<TQueryKey> => {
  const queryFnContext: OmitKeyof<
    QueryFunctionContext<TQueryKey>,
    'signal'
  > = {
    client: this.#client,
    queryKey: this.queryKey,
    meta: this.meta,
  }
  addSignalProperty(queryFnContext)
  return queryFnContext as QueryFunctionContext<TQueryKey>
}
```

## Custom Query Hash Function

You can provide a custom hash function:

```tsx theme={null}
import { QueryClient } from '@tanstack/react-query'
import { hashKey } from '@tanstack/react-query'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      queryKeyHashFn: (queryKey) => {
        // Custom hashing logic
        return hashKey(queryKey)
      },
    },
  },
})
```

## Effective Key Organization

### By Resource

```tsx theme={null}
const keys = {
  users: {
    all: ['users'] as const,
    detail: (id: number) => ['users', id] as const,
  },
  posts: {
    all: ['posts'] as const,
    list: (filters: Filters) => ['posts', 'list', filters] as const,
    detail: (id: number) => ['posts', id] as const,
  },
}
```

### By Feature

```tsx theme={null}
const dashboardKeys = {
  all: ['dashboard'] as const,
  stats: (period: string) => ['dashboard', 'stats', period] as const,
  activity: (userId: number) => ['dashboard', 'activity', userId] as const,
}
```

## Type Safety with Query Keys

Use TypeScript to ensure type safety:

```tsx theme={null}
type PostFilters = {
  status: 'published' | 'draft'
  author?: string
}

const postKeys = {
  all: ['posts'] as const,
  list: (filters: PostFilters) => ['posts', 'list', filters] as const,
  detail: (id: number) => ['posts', 'detail', id] as const,
}

// Type-safe usage
const filters: PostFilters = { status: 'published' }
useQuery({ queryKey: postKeys.list(filters), ... })

// TypeScript error: invalid status
// postKeys.list({ status: 'invalid' })
```

## Query Key Examples from Source

From the test suite in `useQuery.test.tsx:46-53`:

```typescript theme={null}
function Page() {
  const { data = 'default' } = useQuery({
    queryKey: key,
    queryFn: () => sleep(10).then(() => 'test'),
  })

  return <div><h1>{data}</h1></div>
}
```

And from the basic example in `examples/react/basic/src/index.tsx:26-34`:

```typescript theme={null}
function usePosts() {
  return useQuery({
    queryKey: ['posts'],
    queryFn: async (): Promise<Array<Post>> => {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts')
      return await response.json()
    },
  })
}
```
