<!--
SPDX-FileCopyrightText: 2025 Torkild G. Kjevik
SPDX-FileCopyrightText: 2025 ash_typescript contributors <https://github.com/ash-project/ash_typescript/graphs/contributors>
SPDX-License-Identifier: MIT
-->
# Field Selection
Field selection is a core concept in AshTypescript that lets you precisely specify which data you need from your Ash resources.
## Why Field Selection?
- **Reduced payload size** - Only requested fields are returned
- **Better performance** - Ash only loads and processes requested data
- **Full type safety** - TypeScript infers exact return types based on selected fields
- **Explicit data requirements** - No over-fetching or under-fetching
## Basic Field Selection
### Simple Fields
```typescript
import { getTodo } from './ash_rpc';
const todo = await getTodo({
fields: ["id", "title", "completed", "priority"],
input: { id: "todo-123" }
});
if (todo.success) {
// TypeScript knows exact shape:
// { id: string, title: string, completed: boolean, priority: string }
console.log(todo.data.title);
}
```
**Note**: There is no "select all" option. This is intentional to prevent over-fetching and ensure explicit data requirements for full type-safety.
## Nested Field Selection
### Relationships
Select fields from related resources:
```typescript
const todo = await getTodo({
fields: [
"id",
"title",
{ user: ["name", "email", "avatarUrl"] }
],
input: { id: "todo-123" }
});
if (todo.success) {
console.log("Todo:", todo.data.title);
console.log("Created by:", todo.data.user.name);
}
```
### Multiple Relationships
```typescript
const todo = await getTodo({
fields: [
"id",
"title",
{
user: ["name", "email"],
assignee: ["name", "email"],
tags: ["name", "color"]
}
],
input: { id: "todo-123" }
});
```
### Deep Nesting
Select fields from nested relationships:
```typescript
const todo = await getTodo({
fields: [
"id",
"title",
{
comments: [
"id",
"text",
{
author: [
"name",
{ profile: ["bio", "avatarUrl"] }
]
}
]
}
],
input: { id: "todo-123" }
});
```
## Calculations
### Basic Calculations
Request calculated fields computed by your Ash resource:
```typescript
const todo = await getTodo({
fields: [
"id",
"title",
"completionPercentage", // Calculated field
"timeRemaining" // Calculated field
],
input: { id: "todo-123" }
});
```
### Calculations Returning Complex Types
For calculations returning complex types (unions, embedded resources) without arguments, use nested syntax:
```typescript
const todo = await getTodo({
fields: [
"id",
{
relatedItem: ["article", { article: ["id", "title"] }]
}
],
input: { id: "todo-123" }
});
```
### Calculations with Arguments
Pass arguments to calculation fields:
```typescript
const todo = await getTodo({
fields: [
"id",
{
priorityScore: {
args: { multiplier: 2.5, includeSubtasks: true },
fields: ["score", "rank", "category"]
}
}
],
input: { id: "todo-123" }
});
```
## Embedded Resources
### Basic Embedded Resources
```typescript
const todo = await getTodo({
fields: [
"id",
{ settings: ["theme", "notifications", "timezone"] }
],
input: { id: "todo-123" }
});
```
### Embedded Arrays
```typescript
const todo = await getTodo({
fields: [
"id",
{ attachments: ["filename", "size", "url"] }
],
input: { id: "todo-123" }
});
if (todo.success) {
todo.data.attachments.forEach(attachment => {
console.log(`${attachment.filename} (${attachment.size} bytes)`);
});
}
```
## Union Types
For union type fields, select fields from specific union members:
```typescript
const todo = await getTodo({
fields: [
"id",
{
content: [
"text",
{
textContent: ["text", "formatting"],
imageContent: ["url", "caption"],
videoContent: ["url", "thumbnail"]
}
]
}
],
input: { id: "todo-123" }
});
```
See [Union Types](../advanced/union-types.md) for detailed union type handling.
## Load Restrictions
Actions can restrict which relationships/calculations clients can load using `allowed_loads` or `denied_loads`:
```typescript
// If action has: allowed_loads: [:user]
const result = await listTodosLimited({
fields: ["id", "title", { user: ["name"] }] // OK
// { comments: ["text"] } // Would fail - not in allowed_loads
});
```
See [RPC Action Options](../features/rpc-action-options.md) for configuring load restrictions.
## Common Patterns
### List vs Detail Views
```typescript
// List view: minimal fields
async function fetchTodoList() {
return await listTodos({
fields: ["id", "title", "completed", "priority"]
});
}
// Detail view: full fields with relationships
async function fetchTodoDetail(id: string) {
return await getTodo({
fields: [
"id", "title", "description", "completed", "priority",
"dueDate", "createdAt", "updatedAt",
{
user: ["name", "email", "avatarUrl"],
comments: ["id", "text", { author: ["name"] }],
tags: ["name", "color"]
}
],
input: { id }
});
}
```
### Reusable Field Definitions
```typescript
const TodoFields = {
basic: ["id", "title", "completed"] as const,
withUser: [
"id", "title", "completed",
{ user: ["name", "email"] }
] as const,
full: [
"id", "title", "description", "completed", "priority",
{
user: ["name", "email", "avatarUrl"],
tags: ["name", "color"]
}
] as const
};
// Usage
const todos = await listTodos({ fields: TodoFields.withUser });
```
### Conditional Field Selection
```typescript
type ViewMode = "list" | "grid" | "detail";
function getTodoFields(mode: ViewMode) {
const baseFields = ["id", "title", "completed"];
switch (mode) {
case "list":
return [...baseFields, { user: ["name"] }];
case "grid":
return [...baseFields, "priority", { tags: ["color"] }];
case "detail":
return [
...baseFields,
"description", "priority", "dueDate",
{
user: ["name", "email"],
comments: ["id", "text", { author: ["name"] }]
}
];
}
}
```
## Next Steps
- [Querying Data](querying-data.md) - Pagination, sorting, and filtering
- [Typed Queries](typed-queries.md) - Predefined field selections for SSR
- [RPC Action Options](../features/rpc-action-options.md) - Load restrictions
- [Union Types](../advanced/union-types.md) - Complex union type handling
- [Embedded Resources](../advanced/embedded-resources.md) - Embedded resource patterns