Library / SDK
gvergnaud/ts-pattern avatar
gvergnaud/ts-pattern

ts-pattern: exhaustive pattern matching for TypeScript

🎨 The exhaustive Pattern Matching library for TypeScript, with smart type inference.

15,168 stars172 forksTypeScriptMIT

At a glance

What is it?
ts-pattern adds match expressions and exhaustiveness checking to TypeScript in about 2 kB. Here is what the API does, how to install it, and where it stops helping.
Who is it for?
Adopt ts-pattern if your TypeScript code branches on discriminated unions or on the shape of untyped API responses, and you want the compiler to fail when a case is missing. Skip it if your branching is a couple of boolean checks, or if you need to match values your types cannot describe.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 19 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What ts-pattern replaces, and who feels the pain

TypeScript's `switch` narrows a discriminated union correctly, but nothing forces you to handle every member. Add a new variant to `Result` and every switch that ignored it still compiles, so the failure surfaces at runtime instead of at build time. The README frames pattern matching as "a code-branching technique coming from functional programming languages" that is "more powerful and often less verbose than imperative alternatives (if/else/switch statements), especially for complex conditions."

The people who feel this most are those writing state reducers, parsers for third-party JSON, and anything that models a workflow as a union of tagged objects. If your branches are two booleans deep, ts-pattern is overhead. If you have a `Result` type with nested payloads and you keep forgetting the error arm, it is aimed squarely at you.

How match, with and exhaustive work together

The core is a builder. `match(value)` starts a chain, `.with(pattern, handler)` adds a branch, and the terminal call decides what happens when nothing matched. The README's opening example matches a `Result` union whose `ok` arm carries a `Data` union of text and image variants, and closes with `.exhaustive()`.

Exhaustiveness is the mechanism worth understanding. `.exhaustive()` type-checks the set of patterns you supplied against the input type, and the README states it enforces "that you are matching every possible case." `.otherwise(fn)` is the escape hatch when you want a default instead of a compile error. `.when(predicate, handler)` adds a guard, `.returnType<T>()` pins the result type, and `.narrow()` narrows the input type without producing a value.

Patterns are not plain values. They are descriptions: nested object literals, tuple and array patterns, `P.array`, `P.record`, `P.set` and `P.map`, plus wildcards `P._`, `P.string`, `P.number`. Combinators cover the awkward cases: `P.union`, `P.intersection`, `P.not`, `P.optional`, `P.instanceOf`, `P.when` and `P.select(name?)`, which extracts a sub-value for the handler. `isMatching(pattern, value)` reuses the same pattern language as a validator and narrows the value in a type guard. Support for Sets, Maps and BigInt is a genuine differentiator, since the compiler narrows those poorly on its own.

Installing ts-pattern and matching a first union

The README lists npm first, then the equivalent commands for other package managers, including a JSR route. Pick one; they install the same package.

bash
npm install ts-pattern
bash
pnpm add ts-pattern
# OR
yarn add ts-pattern
# OR
bun add ts-pattern
# OR
npx jsr add @gabriel/ts-pattern

Then import `match` and `P` and describe your data. This mirrors the README example: a `Result` union, two branches, and `.exhaustive()` at the end.

ts
import { match, P } from 'ts-pattern';

type Result =
  | { type: 'ok'; data: string }
  | { type: 'error'; error: Error };

const html = match(result)
  .with({ type: 'error' }, () => '<p>Oups! An error occured</p>')
  .with({ type: 'ok' }, (res) => `<p>${res.data}</p>`)
  .exhaustive();

What you should see: the handler for the `ok` branch receives a value narrowed to that variant, so `res.data` type-checks. Delete the `error` branch and `.exhaustive()` should fail to compile. That error is the point of the library, so verify it fires before you trust the chain. `P.select()` is the next step: name the value you want out of a nested pattern instead of reaching through the handler argument.

Where ts-pattern stops helping

Exhaustiveness is a property of your types, not of your data. If an API response is typed as `any` or as a loose interface, `.exhaustive()` will happily accept a chain that misses real runtime cases, because the compiler has nothing to check against. The README offers a sandbox example for handling untyped API responses, and that is exactly the scenario where you must validate the shape yourself, with `isMatching` or otherwise, before the types mean anything.

There is also a type-checking cost. The package.json ships a `perf` script that runs `tsc --project tests/tsconfig.json --noEmit --extendedDiagnostics`, and a `trace` script that generates a compiler trace for `@typescript/analyze-trace`. A library that needs tooling to measure its own type-checking overhead is telling you something: heavy chains over large unions can slow editor feedback. The README cites a bundle size of about 2 kB, but that is runtime weight, not compile time. If your build already sits near a type-checking budget, measure before adopting broadly.

ts-pattern compared with a schema validation library

The closest neighbour in a TypeScript codebase is a schema library such as Zod, and the two solve adjacent problems differently. A schema library validates unknown input and produces a parsed, typed value: you define the schema, it checks the data at runtime, and the type is derived from the schema. ts-pattern assumes you already have types and gives you a compact way to branch on them, with exhaustiveness enforced at compile time. Its `isMatching` narrows a value in a type guard, but it does not parse or transform unknown input into a new structure the way a schema does.

In practice they compose. Validate the boundary with a schema, then match on the resulting union with ts-pattern, where `.exhaustive()` catches the branch you forgot when the union grows. If you only need one of the two, ask whether your problem is untrusted input (schema) or branching over a type you already trust (ts-pattern).

Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-09-11. Recent releases are v5.9.0 (2025-10-26), v5.8.0 (2025-07-26) and v5.7.1 (2025-05-18), so the version line is moving at a steady cadence rather than sitting still.

The licence is MIT, which in practice means you can use the package in commercial and closed-source projects provided the copyright notice and permission notice are preserved. That is a summary of the licence identifier in the repository, not legal advice; read the LICENSE file before shipping if the distinction matters to your organisation.

Upgrade cost is mostly a typing question. Because `.exhaustive()` turns missing cases into compile errors, a change to a union type can break every match chain that touches it, and that breakage is intentional. The package ships both ESM and CommonJS entry points plus a `./types` subpath, so a major version bump that changes the exported types is the event to watch, not the runtime bundle. Pin the version, read the release notes for the major you are jumping to, and re-run your type check.

Editorial conclusion

Adopt ts-pattern if your TypeScript code branches on discriminated unions or on the shape of untyped API responses, and you want the compiler to fail when a case is missing. Skip it if your branching is a couple of boolean checks, or if you need to match values your types cannot describe. Before committing, run `npm install ts-pattern` in a scratch project, paste one real reducer from your codebase, and check what `.exhaustive()` reports when you delete a case. That error message is the whole product; if it reads clearly on your own data, the library earns its place.

Frequently asked questions

What is ts-pattern?

It is a TypeScript library for pattern matching, described in its README as "the exhaustive Pattern Matching library for TypeScript with smart type inference." It lets you express branching conditions as patterns over objects, arrays, tuples, Sets, Maps and primitives, and check that every case is handled.

How does ts-pattern compare with Zod?

They solve different problems. Zod-style schema validation parses unknown input into a typed value, while ts-pattern branches over types you already have and enforces exhaustiveness at compile time. The README documents `isMatching` as a way to validate the shape of your data with patterns, but the library does not parse input into a new structure.

What alternatives to ts-pattern exist?

The README points to languages where pattern matching is built in, such as Python, Rust, Swift, Elixir and Haskell, and to a stage 1 tc39 proposal to add pattern matching to EcmaScript. Within TypeScript, the practical alternative is hand-written switch statements, which narrow types but do not enforce exhaustiveness.

Official sources

  1. gvergnaud/ts-pattern on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/gvergnaud-ts-pattern.svg)](https://hysenlabs.com/projects/gvergnaud-ts-pattern)