Error types
The ErrorType union, validation shape, and every boolean flag on UniversalError.
type
A small, closed set — deliberately coarse. Finer distinctions that don't map to a single HTTP
status range (CORS, DNS, SSL, offline) are exposed as boolean flags instead, so type stays a
stable switch/case target while the flags can grow without breaking it.
type | Typically means |
|---|---|
"Unauthorized" | 401 |
"Forbidden" | 403 |
"NotFound" | 404 |
"Timeout" | 408, or a network-level timeout with no status |
"Validation" | 400 or 422 |
"RateLimit" | 429 |
"Server" | 500+ |
"Network" | Request never got a response — offline, DNS failure, connection refused |
"Cancelled" | The request was aborted |
"Unknown" | Nothing matched |
When a status code is present, it's authoritative (statusToType). When there's no status at
all — the request never got a response — resolveErrorType falls back to Node error codes
(ECONNREFUSED, ETIMEDOUT, ...) and browser fetch's message family ("Failed to fetch",
Safari's "Load failed", Firefox's "NetworkError when attempting to fetch").
Boolean flags
error.isUnauthorized; // type === "Unauthorized" || status === 401
error.isForbidden; // type === "Forbidden" || status === 403
error.isNotFound; // type === "NotFound" || status === 404
error.isValidation; // type === "Validation", OR real field-level validation data is present
error.isRateLimited; // type === "RateLimit" || status === 429
error.isServer; // type === "Server" || status >= 500
error.isNetwork; // type === "Network", or offline/DNS/connection-refused signals
error.isOffline; // navigator-offline hint, or "offline"/"no internet" in the message
error.isTimeout; // ETIMEDOUT-family codes, or "timeout"/"timed out" in the message
error.isCancelled; // AbortController signal, ERR_CANCELED/ABORT_ERR, or "aborted" in the message
error.isDNS; // ENOTFOUND, EAI_AGAIN
error.isSSL; // certificate/TLS error codes or message
error.isCors; // "cors" or "cross-origin ... block" in the messageDNS/SSL/CORS/offline/cancelled detection is heuristic by nature — there's no HTTP status code
for "your DNS resolver failed" — so these lean on Node error codes and message-substring
matching. Adapters with a better signal (e.g. a real AbortSignal.aborted) pass that through as
a hint rather than relying purely on message sniffing.
isValidation is the one flag with two independent triggers. Most flags are pure functions of
type/status; isValidation is also true whenever real field-level validation data was
found in the body — even with no status code confirming it. That matters for a body with no HTTP
envelope at all, like a bare { message, errors: { email: [...] } } with no status anywhere:
type still resolves to "Unknown" (nothing there maps to a status range), but isValidation
is true because error.validation actually has field data. Try it in the
Playground with the "Laravel-style validation" preset.
This is deliberately narrower than "any errors field": a GraphQL/JSON:API-style body like
{ errors: [{ message: "..." }] } has no field name to attach the message to, so it normalizes
into { _general: [...] } — and that alone does not flip isValidation. Only a real field
key does. Try the "Generic errors array" preset in the Playground to see the difference.
retryable
Defaults to true for "Network", "Timeout", and "RateLimit", and for "Server" errors
except 501 Not Implemented and 505 HTTP Version Not Supported (neither succeeds on retry).
Everything else defaults to false. Override per-error via flagOverrides, or check
dynamically with error.shouldRetry(predicate).
validation
type ValidationErrors = Record<string, string[]>;Field name → list of messages, regardless of source. Produced by
normalizeValidation, which folds Laravel, Rails,
express-validator, and FastAPI's different shapes into this one. Use "_general" as the key for
errors that aren't tied to a specific field.
source
Where the error was produced from — "axios" and "fetch" are what the shipped adapters set
today; parseError() alone reports "unknown" when it can't tell. The type also reserves
values for adapters that don't exist yet ("graphql", "react-query", "node", "express",
"nestjs", ...) — see the roadmap for what's actually planned.