What it is
neverthrow provides a Result type for TypeScript, making failure part of a function's return type instead of an invisible exception.
Functions return Ok or Err rather than throwing. The compiler then forces callers to acknowledge the failure case before reaching the value.
Installation
npm install neverthrowGetting started
The smallest useful thing you can do with it, and what each part means.
typescript
import { ok, err, Result } from 'neverthrow';
type ParseError = { kind: 'empty' } | { kind: 'not_a_number'; input: string };
function parseAge(input: string): Result<number, ParseError> {
if (!input.trim()) return err({ kind: 'empty' });
const value = Number(input);
if (Number.isNaN(value)) return err({ kind: 'not_a_number', input });
return ok(value);
}
const result = parseAge(raw);
// You cannot reach the value without handling the error case.
if (result.isErr()) {
return respond(400, result.error);
}
console.log(result.value);typescript
const outcome = parseAge(raw)
.map((age) => age + 1) // runs only on success
.andThen((age) => age >= 18 ? ok(age) : err({ kind: 'too_young' } as const))
.mapErr((e) => ({ ...e, field: 'age' })); // enrich the error
outcome.match(
(age) => respond(200, { age }),
(error) => respond(400, error),
);Advanced usage
Where the library earns its place over a simpler alternative.
typescript
import { ResultAsync, fromThrowable } from 'neverthrow';
// Convert a throwing function into one that returns Result.
const safeJsonParse = fromThrowable(JSON.parse, (e) => ({
kind: 'invalid_json' as const,
cause: e,
}));
// Convert a promise into a ResultAsync.
const fetchUser = (id: string) =>
ResultAsync.fromPromise(
fetch(`/api/users/${id}`).then((r) => r.json()),
(e) => ({ kind: 'network' as const, cause: e }),
);
const user = await fetchUser('42')
.andThen((raw) => safeJsonParse(raw))
.unwrapOr(null);Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- Property value does not exist on Result
- Narrow first with isOk()/isErr(), or use match/unwrapOr. Direct access is deliberately blocked.
- Mixing Result and thrown errors
- Pick one convention per layer. A half-converted codebase is worse than either approach alone.
Best practices
- Use Result for expected failures — validation, not-found, parse errors — and keep exceptions for genuine bugs.
- Wrap throwing libraries at the boundary with fromThrowable rather than scattering try/catch.
- Give errors a discriminated union type so callers can match on the kind.
- Avoid _unsafeUnwrap outside tests; it defeats the purpose.
Background
Why it exists, and what it was reacting to.
Inspired by Rust's Result, neverthrow addresses a real weakness in TypeScript: a function's signature says nothing about what it can throw, so error handling is invisible to both the compiler and the reader.
