TypeScript

Purpose and Scope

TanStack Query’s TypeScript story is built around inference from the functions that actually fetch or mutate server state. Rather than requiring users to annotate every call site, adapters infer result data, error values, variables, and selected data from queryFn, mutationFn, select, and option helper return types. This page explains those patterns for React-oriented readers while grounding the details in the framework TypeScript docs supplied for Angular, Lit, and Preact. The important transfer is that adapters expose framework-native APIs, but they share TanStack Query Core’s type model for query results, mutation results, keys, and helpers.

Sources: docs/framework/angular/typescript.md, docs/framework/lit/typescript.md, docs/framework/preact/typescript.md

The Preact TypeScript page is intentionally a framework rewrite of the React TypeScript page: its frontmatter points at docs/framework/react/typescript.md and replaces React package terminology with Preact terminology. The Angular page follows the same reference pattern while substituting useQuery with injectQuery, useMutation with injectMutation, and adapting status narrowing to signal methods such as isSuccess(). That means React readers can use the same conceptual model: keep fetch functions typed, let call sites infer, and reach for helpers when options move out of the hook call.

Sources: docs/framework/angular/typescript.md, docs/framework/preact/typescript.md

Relevant Source Files

  • docs/framework/angular/typescript.md - Shows inferred query data, select transformations, Angular signal narrowing, default error typing, module augmentation through Register, and typed option extraction with queryOptions.
  • docs/framework/lit/typescript.md - Provides the clearest standalone explanation that Lit Query reuses TanStack Query Core’s type system, demonstrates query and mutation inference, and names the queryOptions, infiniteQueryOptions, and mutationOptions helpers.
  • docs/framework/preact/typescript.md - Declares the Preact TypeScript page as a generated adaptation of the React TypeScript page, which supports using the same inference guidance for React-like hook APIs with package-name substitutions.

Inference From Query and Mutation Functions

The primary rule is simple: give your query and mutation functions precise return types. In the Lit example, fetchTodos returns Promise<Todo[]>, so the query controller result exposes data as Todo[] | undefined until success is known. The same pattern appears in the Angular examples where a query function returning Promise.resolve(5) produces number | undefined, and a select callback converting the number to a string changes the exposed data type to string | undefined. React and Preact hooks follow the same shape: the function return value drives result typing.

Sources: docs/framework/angular/typescript.md, docs/framework/lit/typescript.md

type Todo = {
  id: number
  title: string
}
 
async function fetchTodos(): Promise<Todo[]> {
  const response = await fetch('/api/todos')
  if (!response.ok) throw new Error('Failed to fetch todos')
  return response.json() as Promise<Todo[]>
}

This approach matters because TanStack Query is transport agnostic. A query function can use fetch, axios, Angular HttpClient, GraphQL clients, or any promise-returning backend access layer, but TypeScript can only infer useful result types when that layer exposes useful types. The Angular documentation demonstrates this with HttpClient.get<Group[]>('/groups') wrapped in lastValueFrom, producing Group[] | undefined for the query data. For React users, the equivalent lesson is to type the API client function, not to scatter generic parameters across every useQuery invocation.

Sources: docs/framework/angular/typescript.md

Narrowing Result State

Query results intentionally represent lifecycle uncertainty. Before a query succeeds, data can be absent; after success, it can be treated as present according to the adapter’s result type. Lit documents narrowing through isSuccess, isPending, isError, or status, and the example shows query.data narrowing to Todo[] inside an if (query.isSuccess) branch. Angular uses signal accessors, so narrowing is written as query.isSuccess() in templates or computed code. React and Preact users should read this as the familiar status-flag pattern on hook results.

Sources: docs/framework/angular/typescript.md, docs/framework/lit/typescript.md

Angular has one adapter-specific caveat that clarifies why the exact API shape matters. Its docs note that TypeScript currently does not support discriminated unions on object methods, so narrowing on signal fields works only on signals returning a boolean. The guidance is to prefer isSuccess() and related boolean status signals over comparing status() === 'success'. React hook results do not use Angular signals, but the design principle still applies: prefer the status helpers that the adapter’s result object exposes when you want TypeScript to narrow data, error, and related fields.

Sources: docs/framework/angular/typescript.md

Typing Errors and Global Register Types

By default, the Angular examples show query.error() inferred as Error | null when the query function is typed normally. If a call site supplies an explicit error type parameter, the same result can become string | null, but that is usually less ergonomic because it can force additional generic arguments. The docs also show narrowing an Error | null value with axios.isAxiosError, which keeps the default broad error type while allowing library-specific handling at the point where an error is actually inspected.

Sources: docs/framework/angular/typescript.md

For applications that want a different global default, TanStack Query exposes a Register interface through module augmentation. The Angular docs augment @tanstack/angular-query-experimental with defaultError: unknown, causing query errors to become unknown | null and requiring every call site to narrow explicitly. The Lit docs use the same Register idea for global key shapes, augmenting @tanstack/lit-query with queryKey and mutationKey. In React or Preact, the module name changes to the adapter package, but the purpose is the same: encode app-wide conventions once.

Sources: docs/framework/angular/typescript.md, docs/framework/lit/typescript.md

import '@tanstack/lit-query'
 
type AppQueryKey = ['todos' | 'projects', ...ReadonlyArray<unknown>]
 
declare module '@tanstack/lit-query' {
  interface Register {
    queryKey: AppQueryKey
    mutationKey: AppQueryKey
  }
}

Extracting Typed Options

Inline options infer well because the adapter can see queryKey, queryFn, and select at the call site. The tradeoff appears when teams extract options into reusable functions for prefetching, cache reads, routers, services, or shared components. The Angular docs describe losing inference when options are moved away from injectQuery, and recommend the queryOptions helper to recover it. The Lit docs make the same recommendation for controllers and QueryClient calls, showing one todosOptions() function used by both createQueryController and queryClient.prefetchQuery.

Sources: docs/framework/angular/typescript.md, docs/framework/lit/typescript.md

import { QueryClient, queryOptions } from '@tanstack/lit-query'
 
function todosOptions() {
  return queryOptions({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    staleTime: 5_000,
  })
}
 
const queryClient = new QueryClient()
void queryClient.prefetchQuery(todosOptions())

The key detail is that option helpers preserve the relationship between the query key and the data returned by the query function. Lit’s docs call out that the branded queryKey returned from queryOptions helps APIs such as queryClient.getQueryData understand the data type. This is especially useful in React applications that centralize options next to route loaders, feature modules, or API clients. Instead of manually typing cache reads later, define a helper once and pass the helper result into hooks, prefetching, and cache access APIs.

Sources: docs/framework/lit/typescript.md

Compact Reference

ConcernPublic names shown in sourceBehavior
Query inferenceinjectQuery, createQueryControllerInfers data from queryFn and transforms it through select.
Mutation inferenceinjectMutation, createMutationControllerInfers variables and result data from mutationFn.
Option extractionqueryOptions, infiniteQueryOptions, mutationOptionsPreserves typed options when configuration is shared outside the hook or controller call.
Global typingRegister, defaultError, queryKey, mutationKeyUses module augmentation against the adapter package to define app-wide defaults and key shapes.
Result narrowingisSuccess, isPending, isError, status, isSuccess()Narrows lifecycle-dependent fields such as data and error when checked through adapter-supported status flags.

Next Steps

When adding TypeScript to a TanStack Query feature, start by typing the API client functions and mutation functions. Then keep query options inline until reuse is needed; once reuse appears, move the options into queryOptions, infiniteQueryOptions, or mutationOptions helpers so prefetching, hooks, controllers, and cache reads stay connected. If the application has strict key conventions or wants unknown errors by default, add module augmentation through the adapter package’s Register interface. After that, use status helpers in rendering code to narrow data before accessing success-only fields.

Sources: docs/framework/angular/typescript.md, docs/framework/lit/typescript.md, docs/framework/preact/typescript.md