Field Selection
View SourceField 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
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:
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
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:
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:
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:
const todo = await getTodo({
fields: [
"id",
{
relatedItem: ["article", { article: ["id", "title"] }]
}
],
input: { id: "todo-123" }
});Calculations with Arguments
Pass arguments to calculation fields:
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
const todo = await getTodo({
fields: [
"id",
{ settings: ["theme", "notifications", "timezone"] }
],
input: { id: "todo-123" }
});Embedded Arrays
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:
const todo = await getTodo({
fields: [
"id",
{
content: [
"text",
{
textContent: ["text", "formatting"],
imageContent: ["url", "caption"],
videoContent: ["url", "thumbnail"]
}
]
}
],
input: { id: "todo-123" }
});See Union Types for detailed union type handling.
Load Restrictions
Actions can restrict which relationships/calculations clients can load using allowed_loads or denied_loads:
// 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 for configuring load restrictions.
Common Patterns
List vs Detail Views
// 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
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
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 - Pagination, sorting, and filtering
- Typed Queries - Predefined field selections for SSR
- RPC Action Options - Load restrictions
- Union Types - Complex union type handling
- Embedded Resources - Embedded resource patterns