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');