Last Updated: 3/6/2026
TS-Pattern v3 to v4 Migration Guide
This guide will help you migrate your codebase from TS-Pattern v3 to v4.
Breaking Changes
1. Import Changes
Type-specific wildcard patterns have moved from __. to a new Pattern module (also exported as P).
Before (v3):
import { match, __ } from 'ts-pattern';
match(value)
.with(__.string, (v) => v)
.with(__.number, (v) => `${v}`)
.exhaustive();After (v4):
import { match, P } from 'ts-pattern';
match(value)
.with(P.string, (v) => v)
.with(P.number, (v) => `${v}`)
.exhaustive();2. Top-Level Wildcard
The top-level __ export moved to P._ and P.any.
Before:
import { match, __ } from 'ts-pattern';
match(value)
.with(__, (v) => `${v}`)
.exhaustive();After:
import { match, P } from 'ts-pattern';
match(value)
.with(P._, (v) => `${v}`)
// OR
.with(P.any, (v) => `${v}`)
.exhaustive();3. Pattern Creation Functions
Functions like select(), not(), and when() moved to the P module.
Before:
import { match, select, not, when } from 'ts-pattern';
match(value)
.with({ prop: select() }, (v) => `${v}`)
.with({ prop: not(10) }, (v) => `${v}`)
.with({ prop: when((x) => x < 5) }, (v) => `${v}`)
.exhaustive();After:
import { match, P } from 'ts-pattern';
match(value)
.with({ prop: P.select() }, (v) => `${v}`)
.with({ prop: P.not(10) }, (v) => `${v}`)
.with({ prop: P.when((x) => x < 5) }, (v) => `${v}`)
.exhaustive();4. Pattern Type
The Pattern type is now accessible at P.Pattern.
Before:
import { Pattern } from 'ts-pattern';
const pattern: Pattern = /* ... */;After:
import { P } from 'ts-pattern';
const pattern: P.Pattern = /* ... */;5. Array Patterns
The syntax for matching arrays with unknown length changed from [subpattern] to P.array(subpattern).
Before:
import { match, __ } from 'ts-pattern';
match(value)
.with({ data: [{ name: __.string }] }, (users) => users)
.exhaustive();After:
import { match, P } from 'ts-pattern';
match(value)
.with({ data: P.array({ name: P.string }) }, (users) => users)
.exhaustive();Now [subpattern] matches arrays with exactly 1 element, which is more consistent with native JavaScript destructuring.
6. NaN Pattern
The __.NaN pattern was replaced with the NaN value directly.
Before:
match(value)
.with(__.NaN, () => 'not a number')
.exhaustive();After:
match(value)
.with(NaN, () => 'not a number')
.exhaustive();New Features in v4
P.optional()
Match optional object properties:
match(user)
.with(
{
type: 'user',
detail: {
bio: P.optional(P.string),
socialLinks: P.optional({ twitter: P.select() }),
},
},
(twitterLink) => {
// twitterLink: string | undefined
}
)
.otherwise(() => {});P.union() and P.intersection()
Combine patterns:
// P.union - matches if ANY pattern matches
match(input)
.with({ type: P.union('a', 'b') }, (x) => 'a or b')
.otherwise(() => 'other');
// P.intersection - matches if ALL patterns match
match(input)
.with(
{ prop: P.intersection(P.instanceOf(A), { foo: 'bar' }) },
(x) => 'matched'
)
.otherwise(() => 'other');P.select() with Sub-Patterns
P.select() now accepts a sub-pattern:
match(input)
.with(
{ author: P.select({ type: 'user' }) },
(user) => {
// user: { type: 'user', ... }
}
)
.otherwise(() => {});P.infer
Infer types from patterns:
const postPattern = {
title: P.string,
description: P.optional(P.string),
content: P.string,
} as const;
type Post = P.infer<typeof postPattern>;
// Post: { title: string, description?: string, content: string }New Wildcards
P.symbol- matches any symbolP.bigint- matches any bigint
Migration Checklist
- Replace all
__imports withP - Update
__.string,__.number, etc. toP.string,P.number - Change
__wildcard toP._orP.any - Move
select(),not(),when()toP.select(),P.not(),P.when() - Update
Patterntype toP.Pattern - Replace
[subpattern]array syntax withP.array(subpattern) - Change
__.NaNtoNaN - Test your application thoroughly
Automated Migration
You can use find-and-replace with regex to automate most of these changes:
# Replace __ imports
sed -i 's/import { match, __ }/import { match, P }/g' **/*.ts
# Replace __.string, __.number, etc.
sed -i 's/\b__\.string\b/P.string/g' **/*.ts
sed -i 's/\b__\.number\b/P.number/g' **/*.ts
sed -i 's/\b__\.boolean\b/P.boolean/g' **/*.ts
# Replace standalone __
sed -i 's/\.with(__, /\.with(P._, /g' **/*.tsNote: Always review automated changes and test thoroughly!