Skip to Content
Patterns GuideP Select Extract Values

Last Updated: 3/18/2026


P.select() - Extract Values

P.select() lets you pick pieces of your input data structure and inject them directly into your handler function. This is especially useful for deep data structures where destructuring would be tedious.

Anonymous Selection

Use P.select() without arguments to extract a single value:

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'

Rule: Only one anonymous selection per pattern.

Named Selections

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

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

Named selections are passed as an object to your handler function.

Select with Sub-patterns

You can add 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 }; match(input) .with( { author: P.select({ age: P.number.gt(18) }), }, (author) => { // author: User (only if age > 18) return `Adult author: ${author.name}`; } ) .otherwise(() => 'Minor or anonymous');

Named Selection with Sub-pattern

match(input) .with( { author: P.select('author', { age: P.number.gt(18) }), content: P.select('post'), }, ({ author, post }) => { // author: User (age > 18) // post: Post return `${author.name} wrote: ${post.body}`; } ) .otherwise(() => 'No match');

Accessing Full Input

Even when using selections, you can still access the full input value as the second argument to your handler:

match(input) .with( { type: 'error', error: P.select() }, (error, fullInput) => { // error: Error (selected value) // fullInput: { type: 'error', error: Error } (full input, narrowed) return `Error of type ${fullInput.type}: ${error.message}`; } ) .exhaustive();

Selection in Arrays

Select specific array elements:

type Input = [string, number, boolean]; match([name, age, active] as Input) .with( [P.select('name'), P.select('age'), P._], ({ name, age }) => `${name} is ${age} years old` ) .exhaustive();

Selection with P.union

When using P.union, selections work across all union branches:

match(input) .with( { type: P.union('user', 'admin'), name: P.select() }, (name) => `Name: ${name}` // Works for both user and admin ) .exhaustive();

Real-World Example: Deep Nested Data

type ApiResponse = { status: 'success' | 'error'; data: { user: { profile: { firstName: string; lastName: string; }; settings: { theme: 'light' | 'dark'; }; }; }; }; const response: ApiResponse = ...; const greeting = match(response) .with( { status: 'success', data: { user: { profile: { firstName: P.select('first'), lastName: P.select('last') }, settings: { theme: P.select('theme') } } } }, ({ first, last, theme }) => `Hello ${first} ${last}! Theme: ${theme}` ) .otherwise(() => 'Error loading user');

Instead of deeply destructuring the response, P.select() picks exactly what you need.

Common Patterns

Select from Array Element

match(users) .with( [{ name: P.select() }, ...P.array()], (firstName) => `First user: ${firstName}` ) .exhaustive();

Select Error Message

match(result) .with( { status: 'error', error: { message: P.select() } }, (msg) => `Error: ${msg}` ) .otherwise(() => 'Success');

Conditional Selection

match(data) .with( { score: P.select(P.number.gte(90)) }, (score) => `Excellent: ${score}` // Only if score >= 90 ) .otherwise(() => 'Below 90');