Skip to Content
V3 To V4

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 symbol
  • P.bigint - matches any bigint

Migration Checklist

  • Replace all __ imports with P
  • Update __.string, __.number, etc. to P.string, P.number
  • Change __ wildcard to P._ or P.any
  • Move select(), not(), when() to P.select(), P.not(), P.when()
  • Update Pattern type to P.Pattern
  • Replace [subpattern] array syntax with P.array(subpattern)
  • Change __.NaN to NaN
  • 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' **/*.ts

Note: Always review automated changes and test thoroughly!