Skip to Content
Migration GuidesV4 To V5 Migration Guide

Last Updated: 3/18/2026


TS-Pattern v4 to v5 Migration Guide

This guide covers all breaking changes and new features when migrating from version 4 to version 5 of TS-Pattern.

Breaking Changes

TypeScript v5 Required

TS-Pattern v5 requires TypeScript v5+ because it relies on const type parameters .

Eager Evaluation of .with()

This is the most significant breaking change.

v4 Behavior (Lazy)

In v4, no code executed until you called .exhaustive() or .otherwise():

type Input = { type: 'ok'; value: number } | { type: 'error'; error: Error }; function someFunction(input: Input) { match(input) .with({ type: 'ok' }, ({ value }) => { console.log(value); // This does NOT run }) .with({ type: 'error' }, ({ error }) => { throw error; // This does NOT run either }); // ⚠️ Nothing happens because we didn't call .exhaustive() or .otherwise() } someFunction({ type: 'ok', value: 42 }); // nothing happens

v5 Behavior (Eager)

In v5, the library executes the matching handler as soon as it finds it:

function someFunction(input: Input) { match(input) .with({ type: 'ok' }, ({ value }) => { console.log(value); // ✅ This RUNS immediately when matched }) .with({ type: 'error' }, ({ error }) => { throw error; // ✅ This RUNS immediately when matched }); } someFunction({ type: 'ok', value: 42 }); // logs "42" to the console!

Impact: In practice, this shouldn’t change anything as long as you always finish your pattern matching expressions with either .exhaustive() or .otherwise().

Matching on Map and Set

Using new Set(...) and new Map(...) directly in patterns is no longer supported. Use P.set() and P.map() instead.

Before (v4)

import { match } from 'ts-pattern'; match(value) .with(new Set([P.number]), (set) => 'a set of numbers') .with(new Map([['key', P.number]]), (map) => 'map with numeric values') .otherwise(() => null);

After (v5)

import { match, P } from 'ts-pattern'; match(value) .with(P.set(P.number), (set) => 'a set of numbers') .with(P.map('key', P.number), (map) => 'map with numeric values') .otherwise(() => null);

Pattern Semantics:

  • P.set(subpattern): The subpattern must match all values in the Set
  • P.map(keyPattern, valuePattern): Patterns must match all keys and values in the Map

New Features

Chainable Methods

The biggest addition in v5 is the ability to chain methods to narrow down matched values.

P.number Methods

const example = (position: { x: number; y: number }) => match(position) .with({ x: P.number.gte(100) }, () => 'x >= 100') .with({ x: P.number.between(0, 100) }, () => '0 <= x <= 100') .with( { x: P.number.positive().int(), y: P.number.positive().int(), }, () => 'positive integers' ) .otherwise(() => 'negative coordinates');

Available number methods:

  • P.number.between(min, max) - inclusive range
  • P.number.lt(max) - less than
  • P.number.gt(min) - greater than
  • P.number.lte(max) - less than or equal
  • P.number.gte(min) - greater than or equal
  • P.number.int() - integers only
  • P.number.finite() - excludes Infinity and -Infinity
  • P.number.positive() - positive numbers
  • P.number.negative() - negative numbers

P.string Methods

const example = (query: string) => match(query) .with(P.string.startsWith('SELECT'), () => 'selection query') .with(P.string.endsWith('FROM user'), () => 'user query') .with(P.string.includes('*'), () => 'wildcard query') // Methods can be chained! .with(P.string.startsWith('SET').includes('*'), () => 'SET with wildcard') .exhaustive();

Available string methods:

  • P.string.startsWith(str) - starts with substring
  • P.string.endsWith(str) - ends with substring
  • P.string.minLength(min) - at least min characters
  • P.string.maxLength(max) - at most max characters
  • P.string.length(len) - exactly len characters
  • P.string.includes(str) - contains substring
  • P.string.regex(RegExp) - matches regular expression

Global Methods

Available on all primitive type patterns:

match(value) .with( { username: P.string, displayName: P.string.optional(), // ✨ optional property }, () => 'user with optional display name' ) .with( { title: P.string, author: { username: P.string.select() }, // ✨ select method }, (username) => `author: ${username}` ) .with( P.instanceOf(Error).and({ source: P.string }), // ✨ intersection () => 'Error with source' ) .with( P.string.or(P.number), // ✨ union () => 'string or number' ) .otherwise(() => null);

Global methods:

  • .optional() - matches even if property is missing
  • .select() - injects matched value into handler
  • .and(pattern) - intersection (both patterns must match)
  • .or(pattern) - union (either pattern must match)

Variadic Tuple Patterns

Create array patterns with variable length using ...P.array():

// Non-empty list of strings match(value) .with( [P.string, ...P.array(P.string)], (value) => { // value: [string, ...string[]] } ) .otherwise(() => null);

Multiple Fixed Elements

match(value) .with( [P.string, P.string, P.string, ...P.array(P.string)], (value) => { // value: [string, string, string, ...string[]] } ) .with([], (value) => { // value: [] }) .otherwise(() => null);

Patterns After Variadic

match(value) // Fixed patterns AFTER the variadic .with( [...P.array(P.number), P.string, P.number], (value) => { // value: [...number[], string, number] } ) // Fixed patterns on BOTH sides .with( [P.boolean, ...P.array(P.string), P.number, P.symbol], (value) => { // value: [boolean, ...string[], number, symbol] } ) .otherwise(() => null);

Optional Sub-pattern

The argument to P.array() is now optional and defaults to P._:

match(value) .with( [P.string, ...P.array()], // matches [string, ...unknown[]] (value) => 'starts with string' ) .otherwise(() => null);

.returnType<Type>()

Explicitly set the return type without setting the input type.

v4 Approach

match< { isAdmin: boolean; plan: 'free' | 'paid' }, // ⚠️ Must specify input type number // return type >({ isAdmin, plan }) .with({ isAdmin: true }, () => 123) .with({ plan: 'free' }, () => 'Oops!'); // ❌ not a number

v5 Approach

match({ isAdmin, plan }) .returnType<number>() // ✅ Only specify return type .with({ isAdmin: true }, () => 123) .with({ plan: 'free' }, () => 'Oops!'); // ❌ not a number

Migration Checklist

  • Ensure TypeScript version is 5.0 or higher
  • Verify all match expressions end with .exhaustive() or .otherwise()
  • Replace new Set([pattern]) with P.set(pattern)
  • Replace new Map([...]) with P.map(keyPattern, valuePattern)
  • Consider using chainable methods for cleaner string/number validation
  • Update explicit type parameters to use .returnType<T>() instead
  • Test variadic tuple patterns if using array matching