Type System
Understand How Field Selection Drives Return Types
The SDK generates TypeScript types for each collection. When you specify fields, the return type includes only those fields. Omitting fields selects all primitive fields.
By default, strictNull: true adds | null to every field — see Null Strictness for details.
const articles = await client.Articles.readMany({
fields: ['*'],
});
// Inferred type:
// {
// id: string | null;
// title: string | null;
// status: string | null;
// content: string | null;
// author_id: number | null;
// created_at: string | null;
// }[]
Select individual fields to narrow the return type:
const articles = await client.Articles.readMany({
fields: ['id', 'title', 'status'],
});
// Inferred type:
// {
// id: string | null;
// title: string | null;
// status: string | null;
// }[]
A single item works the same way:
const article = await client.Articles.readOne({
key: 1,
fields: ['id', 'title'],
});
// Inferred type:
// {
// id: string | null;
// title: string | null;
// }
TypeScript checks field access against the requested data shape. Removing a field from fields makes references to it a compile-time error. Adding a relation to include adds its nested type to the result.
Include Related Data
Request related data with include, alongside fields. Each relation has its own query, and the inferred type reflects whether it is to-one or to-many.
To-one relations return T | null. To-many relations return { data: T[] } — a data envelope wrapping the array.
const articles = await client.Articles.readMany({
fields: ['id', 'title'],
include: {
author: { fields: ['name', 'email'] },
},
});
// Inferred type:
// {
// id: string | null;
// title: string | null;
// author: {
// name: string | null;
// email: string | null;
// } | null;
// }[]
Include a to-many relation to return related items in a data envelope:
const authors = await client.Authors.readMany({
fields: ['id', 'name'],
include: {
articles: { fields: ['title', 'status'] },
},
});
// Inferred type:
// {
// id: string | null;
// name: string | null;
// articles: {
// data: {
// title: string | null;
// status: string | null;
// }[];
// } | null;
// }[]
When you omit fields from an included relation, the SDK defaults to ['*'], selecting every primitive field on that relation:
const articles = await client.Articles.readMany({
fields: ['id', 'title'],
include: {
author: {},
},
});
This default applies only to the SDK. The same request sent directly to the REST API returns HTTP 422 because every included relation must have a selection.
To-one relations return T | null because the foreign key can be null or removed by permissions. Under strictNull: true, to-many relations return { data: T[] } | null, so the envelope itself is nullable.
With strictNull: false, the to-many envelope is non-nullable, and to-one nullability follows the schema.
Filter, Sort, and Paginate Included Relations
Set query parameters alongside fields for each relation in include. These parameters apply to the related items, independently of the parent query.
To-many relations accept filter, sort, limit, and offset:
const authors = await client.Authors.readMany({
fields: ['id', 'name'],
include: {
articles: {
fields: ['title', 'status'],
filter: { status: { _eq: 'published' } },
sort: [{ created_at: { direction: 'desc' } }],
limit: 5,
},
},
});
// Inferred type:
// {
// id: string | null;
// name: string | null;
// articles: {
// data: {
// title: string | null;
// status: string | null;
// }[];
// } | null;
// }[]
curl -g "https://example.monospace.io/api/blog/items/Authors?fields=id,name&include[articles][fields]=title,status&include[articles][filter][status][_eq]=published&include[articles][sort][0][created_at][direction]=desc&include[articles][limit]=5" \
-H "Authorization: Bearer YOUR_API_KEY"
const params = new URLSearchParams({
fields: 'id,name',
'include[articles][fields]': 'title,status',
'include[articles][filter][status][_eq]': 'published',
'include[articles][sort][0][created_at][direction]': 'desc',
'include[articles][limit]': '5',
});
const response = await fetch(
`https://example.monospace.io/api/blog/items/Authors?${params}`,
{
headers: {
Authorization: 'Bearer YOUR_API_KEY',
},
},
);
const { data } = await response.json();
Both nullable and required to-one relations accept fields and a nested include. Nullable to-one relations also accept filter. Only to-many relations accept sort, limit, and offset.
The nullable to-one relation author accepts a filter:
const articles = await client.Articles.readMany({
fields: ['id', 'title'],
include: {
author: {
fields: ['name'],
filter: { active: { _eq: true } },
},
},
});
// Inferred type:
// {
// id: string | null;
// title: string | null;
// author: {
// name: string | null;
// } | null;
// }[]
curl -g "https://example.monospace.io/api/blog/items/Articles?fields=id,title&include[author][fields]=name&include[author][filter][active][_eq]=true" \
-H "Authorization: Bearer YOUR_API_KEY"
const params = new URLSearchParams({
fields: 'id,title',
'include[author][fields]': 'name',
'include[author][filter][active][_eq]': 'true',
});
const response = await fetch(
`https://example.monospace.io/api/blog/items/Articles?${params}`,
{
headers: {
Authorization: 'Bearer YOUR_API_KEY',
},
},
);
const { data } = await response.json();
include into the same REST parameter documented in Field Selection.Define Reusable Queries with satisfies
Store query parameters in a variable to reuse them across calls. Use as const satisfies to check the query at compile time and preserve literal types for return type inference.
import type { ArticlesReadManyParameters } from './generated/monospace';
const publishedArticles = {
fields: ['id', 'title', 'status'],
include: {
author: {
fields: ['name'],
filter: { active: { _eq: true } },
},
},
filter: { status: { _eq: 'published' } },
sort: [{ created_at: { direction: 'desc' } }],
limit: 50,
} as const satisfies ArticlesReadManyParameters;
const articles = await client.Articles.readMany(publishedArticles);
// Inferred type:
// {
// id: string | null;
// title: string | null;
// status: string | null;
// author: {
// name: string | null;
// } | null;
// }[]
as const satisfies instead of a : Type annotation — it preserves literal types for precise return type inference. A : ArticlesReadManyParameters annotation widens fields to string[], losing track of which fields were selected.Type Function Arguments
Use generated parameter types for function arguments so TypeScript checks each query and infers its return type.
import type { ArticlesReadManyParameters } from './generated/monospace';
async function fetchArticles<const P extends ArticlesReadManyParameters>(params: P) {
return client.Articles.readMany(params);
}
const articles = await fetchArticles({
fields: ['id', 'title'],
filter: { status: { _eq: 'published' } },
});
// Inferred type:
// {
// id: string | null;
// title: string | null;
// }[]
Every operation has a matching parameter type: ArticlesReadOneParameters, ArticlesCreateOneParameters, ArticlesUpdateOneParameters, and so on.
Understand Parameters vs Args Types
The SDK generates two types for each collection operation:
{Collection}{Op}Parameters— query string parameters:fields,include,filter,sort,limit,offset. Use these to define reusable query parameters to spread into different calls.{Collection}{Op}Args— the full argument object accepted by SDK methods, includingdatafor writes andkeyfor single-item operations.
Use Args for complete method arguments. Use Parameters for query parameters shared across calls, as in this example:
import type { AuthorsReadOneParameters, AuthorsReadOneResultItem } from './generated/monospace';
const queryParams = {
fields: ['id', 'name'],
} satisfies AuthorsReadOneParameters;
type Author = AuthorsReadOneResultItem<typeof queryParams>;
function readAuthor(key: string): Promise<Author> {
return client.Authors.readOne({ key, ...queryParams });
}
Extract Result Types for Component Props
Use the generated result types to type component props, function return values, or any variable that holds query results.
{Collection}{Op}Result describes the full result -- an array for readMany, a single object for readOne. {Collection}{Op}ResultItem describes a single item, for use in component props:
import type { ArticlesReadManyResult, ArticlesReadManyResultItem } from './generated/monospace';
// Full result type (array for readMany)
type ArticleList = ArticlesReadManyResult<{
fields: ['id', 'title', 'status'];
}>;
// = { id: string | null; title: string | null; status: string | null }[]
// Single item type (useful for component props)
type ArticleCard = ArticlesReadManyResultItem<{
fields: ['id', 'title', 'status'];
}>;
// = { id: string | null; title: string | null; status: string | null }
ArticlesReadOneResult, ArticlesReadOneResultItem, ArticlesCreateOneResult, and so on.Type Create and Update Payloads
The SDK generates input types for create and update operations. Create inputs require all non-nullable fields. Update inputs make every field optional.
import type { ArticlesCreateOneInput } from './generated/monospace';
const newArticle: ArticlesCreateOneInput = {
title: 'Getting Started', // required — non-nullable field
status: 'draft', // optional
author: { _connect: { key: { id: 1 } } }, // relation — set through a nested write, not `author_id`
};
await client.Articles.createOne({
data: newArticle,
fields: ['id', 'title'],
});
For updates, include only the fields you want to change:
import type { ArticlesUpdateOneInput } from './generated/monospace';
const changes: ArticlesUpdateOneInput = {
status: 'published', // only include fields you want to change
};
await client.Articles.updateOne({
key: 1,
data: changes,
fields: ['id', 'status'],
});
For relational writes using nested create and update syntax, see Relational Data.
Type Primary Key Parameters
Each collection exports a key type that matches its primary key field. Use it to type variables and function parameters that hold item IDs.
import type { ArticlesKey, AuthorsKey } from './generated/monospace';
// ArticlesKey = string (uuid primary key)
// AuthorsKey = number (auto-increment primary key)
async function getArticle(id: ArticlesKey) {
return client.Articles.readOne({
key: id,
fields: ['id', 'title'],
});
}
The key type is string or number depending on your schema's primary key field type.
See Also
- Field Selection — REST
fieldsandincludeparameter reference - Client Setup — SDK installation and
strictNullconfiguration - Relational Data — nested create and update input types
- Advanced — null strictness, per-query overrides, and more