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 happensv5 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 SetP.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 rangeP.number.lt(max)- less thanP.number.gt(min)- greater thanP.number.lte(max)- less than or equalP.number.gte(min)- greater than or equalP.number.int()- integers onlyP.number.finite()- excludesInfinityand-InfinityP.number.positive()- positive numbersP.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 substringP.string.endsWith(str)- ends with substringP.string.minLength(min)- at least min charactersP.string.maxLength(max)- at most max charactersP.string.length(len)- exactly len charactersP.string.includes(str)- contains substringP.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 numberv5 Approach
match({ isAdmin, plan })
.returnType<number>() // ✅ Only specify return type
.with({ isAdmin: true }, () => 123)
.with({ plan: 'free' }, () => 'Oops!'); // ❌ not a numberMigration Checklist
- Ensure TypeScript version is 5.0 or higher
- Verify all
matchexpressions end with.exhaustive()or.otherwise() - Replace
new Set([pattern])withP.set(pattern) - Replace
new Map([...])withP.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