Skip to content

Retries

Retries are off by default. Opt in by passing retry:

const api = createClient({
retry: { attempts: 3, backoff: "exponential" },
});
  • "exponential" (default) - 100ms, 200ms, 400ms, 800ms… capped at 30 seconds
  • "linear" - 100ms, 200ms, 300ms… capped at 30 seconds
  • (attempt) => ms - custom function for full control

The default predicate retries transient failures only:

ErrorRetried?
NetworkErroryes
5xx HTTPyes
408 Request Timeoutyes
429 Too Manyyes
Other 4xxno
AbortErrorno
ValidationErrorno

The reasoning: 4xx errors are deterministic - retrying without changing the request gives the same answer. Aborts and validation errors are explicit and never retried.

For finer control, pass a retryOn function:

retry: {
attempts: 5,
backoff: "exponential",
retryOn: (err, attempt) => {
if (err instanceof HttpError && err.status === 503) return true;
return false;
},
}

The predicate gets the 1-indexed attempt number, so you can give up after a certain count even when the error type would normally retry.

Override the client default for one call:

await api.get("/x", {
retry: { attempts: 0 }, // disable retries for this request
});

fetch-kit covers retries, response caching, and in-flight request deduplication out of the box. If you need richer server-state management - optimistic updates with rollback, normalized caches, or background refetching - that’s TanStack Query territory. fetch-kit deliberately stops short of that.