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
- Use anonymous selection for single values: Simpler and cleaner
- Name selections for multiple values: Makes code more readable
- Combine with sub-patterns: Validate and extract in one step
- 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);
}
);