Skip to Content
Select

Last Updated: 3/6/2026


P.select() - Extracting Values

P.select() lets you pick a piece of your input data structure and inject it into your handler function. This is especially useful when pattern matching on deep data structures.

Anonymous Selection

Without a name argument, P.select() creates an anonymous selection. You can have only one anonymous selection per pattern.

import { match, P } from 'ts-pattern'; type Input = | { type: 'post'; user: { name: string } } | { type: 'image'; src: string }; const input: Input = { type: 'post', user: { name: 'Gabriel' } }; const output = match(input) .with( { type: 'post', user: { name: P.select() } }, (username) => username // username: string ) .otherwise(() => 'anonymous'); console.log(output); // => 'Gabriel'

Accessing Full Input

The full input value (with narrowed type) is available as the second argument:

match(input) .with( { type: 'post', user: { name: P.select() } }, (username, fullInput) => { // username: string // fullInput: { type: 'post', user: { name: string } } return `${username} posted`; } ) .otherwise(() => '');

Named Selections

When you need to select multiple values, give each selection a name:

type Post = { type: 'post'; user: { name: string }; content: string; }; const input: Post = { type: 'post', user: { name: 'Gabriel' }, content: 'Hello!', }; match(input) .with( { type: 'post', user: { name: P.select('name') }, content: P.select('body'), }, ({ name, body }) => `${name} wrote "${body}"` ) .otherwise(() => ''); // Output: 'Gabriel wrote "Hello!"'

Selection with Sub-Patterns

You can pass a sub-pattern to P.select() to only select values matching that pattern:

type User = { age: number; name: string }; type Post = { body: string }; type Input = { author: User; content: Post }; declare const input: Input; match(input) // Select only if age > 18 .with( { author: P.select({ age: P.number.gt(18) }) }, (author) => { // author: User (with age > 18) return `Adult author: ${author.name}`; } ) // Named selection with sub-pattern .with( { author: P.select('author', { age: P.number.gt(18) }), content: P.select(), }, ({ author, content }) => { // author: User, content: Post return `${author.name}: ${content.body}`; } ) .otherwise(() => '');

Selecting from Arrays

When using P.select() with arrays or records, you can extract collections:

Array Elements

const data = { items: ['a', 'b', 'c'] }; match(data) .with( { items: P.array(P.string.select()) }, (items) => { // items: string[] return items.join(', '); } ) .otherwise(() => '');

Record Values

const data = { a: 1, b: 2, c: 3 }; match(data) .with( P.record(P.string, P.number.select()), (values) => { // values: number[] return values.reduce((a, b) => a + b, 0); } ) .otherwise(() => 0);

Record Keys

const data = { a: 1, b: 2, c: 3 }; match(data) .with( P.record(P.string.select(), P.number), (keys) => { // keys: string[] return keys.join(', '); } ) .otherwise(() => '');

Multiple Selections

Combine multiple named selections in complex patterns:

type Event = | { type: 'message'; user: { id: number; name: string }; text: string; timestamp: number; } | { type: 'reaction'; user: { id: number; name: string }; emoji: string; }; declare const event: Event; match(event) .with( { type: 'message', user: { name: P.select('userName') }, text: P.select('message'), timestamp: P.select('time'), }, ({ userName, message, time }) => { return `[${time}] ${userName}: ${message}`; } ) .with( { type: 'reaction', user: { name: P.select('userName') }, emoji: P.select('emoji'), }, ({ userName, emoji }) => { return `${userName} reacted with ${emoji}`; } ) .exhaustive();

Selecting Entire Branches

Select the entire matched object:

type Shape = | { kind: 'circle'; radius: number } | { kind: 'square'; size: number }; match(shape) .with({ kind: 'circle', radius: P.select() }, (radius) => { // radius: number return Math.PI * radius ** 2; }) .with({ kind: 'square' }, P.select(), (square) => { // This doesn't work - P.select() must be inside the pattern }) .exhaustive();

Real-World Examples

Extracting Error Details

type Result<T> = | { status: 'success'; data: T } | { status: 'error'; error: { code: number; message: string } }; const handleResult = <T>(result: Result<T>) => match(result) .with( { status: 'success', data: P.select() }, (data) => ({ success: true, data }) ) .with( { status: 'error', error: { code: P.select('code'), message: P.select('message'), }, }, ({ code, message }) => ({ success: false, error: { code, message }, }) ) .exhaustive();

Form Validation

type FormData = { email: string; password: string; confirmPassword: string; }; const validateForm = (data: FormData) => match(data) .with( { email: P.string.regex(/@/).select('email'), password: P.string.minLength(8).select('pwd'), confirmPassword: P.select('confirm'), }, ({ email, pwd, confirm }) => { if (pwd === confirm) { return { valid: true, email }; } return { valid: false, error: 'Passwords do not match' }; } ) .otherwise(() => ({ valid: false, error: 'Invalid input' }));

Best Practices

  1. Use anonymous selection for single values: Simpler and cleaner
  2. Name selections for multiple values: Makes code more readable
  3. Combine with sub-patterns: Validate and extract in one step
  4. Keep selections focused: Only extract what you need
// Good: Clear and focused match(user) .with({ email: P.select(), verified: true }, (email) => sendEmail(email)) .otherwise(() => {}); // Less ideal: Selecting unnecessary data match(user) .with( { id: P.select('id'), email: P.select('email'), name: P.select('name'), verified: P.select('verified'), }, (selections) => { // Only using email, but selected everything sendEmail(selections.email); } );