Search across all documentation pages
Type your useSWR calls with generics to get full type safety on data, error, keys, and fetcher functions. SWR's TypeScript support enables inference of return types from fetcher signatures.
"use client";
import useSWR from "swr";
interface User {
id: string;
name: string;
email: string;
}
const fetcher = (url: string): Promise<User> =>
fetch(url).then((r) => r.json());
function Profile() {
const { data, error } = useSWR<User, Error>("/api/me", fetcher);
// data: User | undefined
// error: Error | undefined
if (data) {
return <div>{data.name}</div>; // data is User here
}
}"use client";
import useSWR, { Fetcher, SWRConfiguration, Key } from "swr";
import useSWRMutation, { SWRMutationConfiguration } from "swr/mutation";
// ---- Type-safe fetcher factory ----
function createFetcher<T>(): Fetcher<T, string> {
return async (
useSWR<Data, Error, Key> accepts up to three generic parameters: the data type, the error type, and the key type.Data generic from the fetcher's return type.Fetcher<Data, Key> type from SWR enforces that the fetcher accepts the key type and returns Promise<Data>.useSWRMutation<Data, Error, Key, Arg> adds a fourth generic for the mutation argument type.SWRResponse, SWRConfiguration, Key, and Fetcher for advanced typing.Inferred types from fetcher:
// SWR infers Data from the fetcher return type
const fetchUser = (url: string): Promise<User> => fetch(url).then((r) => r.json());
const { data } = useSWR(
Array key typing:
type FetcherArgs = [url: string, token: string];
const fetcher: Fetcher<User, FetcherArgs> = ([url, token]) =>
fetch(url, { headers: { Authorization: `Bearer ${
Typed middleware:
import { Middleware, SWRHook } from "swr";
const loggerMiddleware: Middleware = (useSWRNext: SWRHook) => {
return (key, fetcher, config) => {
const result = useSWRNext
Typed SWR configuration:
import type { SWRConfiguration } from "swr";
const defaultConfig: SWRConfiguration<User, Error> = {
revalidateOnFocus: false,
errorRetryCount: 3,
onSuccess: (data) => {
// data is typed as User
as casts for type safety. useSWR<User> is better than data as User.null), the data type remains Data | undefined even with suspense.Key type accepts string, any[], null, undefined, or () => Key.undefined case for data even when you expect it to be loaded.res.json() returns Promise<any> by default. You must cast it: res.json() as Promise<User>. This is a trust boundary — runtime validation is not performed.useSWR generic, TypeScript may silently widen to any. Always verify your types align.error defaults to any if you do not specify the Error generic. Always specify it for type safety: useSWR<Data, Error>.useSWR in a custom hook, the return type is SWRResponse<Data, Error>. Import it for explicit typing.Middleware type export rather than writing the full signature manually.| Approach | Pros | Cons |
|---|---|---|
| Explicit generics on useSWR | Full control, clear types | Verbose, manual alignment |
| Inferred from fetcher | Less boilerplate | Easy to accidentally return any |
| Custom typed hooks | Encapsulated, reusable | Extra abstraction layer |
| Runtime validation (zod) | True runtime safety | Extra dependency, performance cost |
const { data, error } = useSWR<User, Error>("/api/me", fetcher);
// data: User | undefined
// error: Error | undefinedUp to three generics: useSWR<Data, Error, Key>.
Yes. If your fetcher has a typed return, SWR infers Data from it:
const fetchUser = (url: string): Promise<User> =>
fetch(url).then((r) => r.json());
const { data } =
res.json() always returns Promise<any> in TypeScript. You must cast it: res.json() as Promise<User>. This is a trust boundary -- TypeScript cannot validate the runtime shape. Consider using Zod for runtime validation.
import { Fetcher } from "swr";
const fetcher: Fetcher<User[], string> = (url) =>
fetch(url).then((r) => r.json());Fetcher<Data, Key> enforces that the fetcher accepts the key type and returns .
useSWRMutation<Data, Error, Key, Arg>(key, mutationFn);Data: return type of the mutationError: error typeKey: the cache key typeArg: the argument type passed to triggerTypeScript may silently widen to any instead of showing a compile error. Always verify that your fetcher's return type aligns with the Data generic you pass to useSWR.
type FetcherArgs = [url: string, token: string];
const fetcher: Fetcher<User, FetcherArgs> = ([url, token]) =>
fetch(url, {
When wrapping useSWR in a custom hook, the return type is SWRResponse<Data, Error>. Import it from swr for explicit typing of your custom hook's return value.
import { Middleware, SWRHook } from "swr";
const myMiddleware: Middleware = (useSWRNext: SWRHook) => {
return (key, fetcher, config) => {
return useSWRNext(key, fetcher, config);
Always prefer explicit generics (useSWR<User>) over as casts (data as User). Generics provide compile-time safety across the entire hook usage, while casts only silence the type checker at one point.
import type { SWRConfiguration } from "swr";
const config: SWRConfiguration<User, Error> = {
revalidateOnFocus: false,
onSuccess: (data) => {
console.log(data.name);
Promise<Data>Use the Middleware type export rather than writing the full signature manually.