Directus SDK Types
The Directus SDK provides TypeScript types used for auto-completion and generating output item types, but these can be complex to work with. This guide will cover some approaches to more easily work with these types.
This guide assumes you're working with the Directus SDK and using TypeScript 5 or later.
Setting Up a Schema
A schema contains a root schema type, custom fields on core collections, and a defined type for each available collection and field.
The root Schema type
The root schema type is the type provided to the SDK client. It should contain all available collections, including junction collections for many-to-many and many-to-any relations. This type is used by the SDK as a lookup table to determine what relations exist.
If a collection if defined as an array, it is considered a regular collection with multiple items. If not defined as an array, but instead as a single type, the collection is considered to be a singleton.
interface MySchema {
// regular collections are array types
collection_a: CollectionA[];
// singleton collections are singular types
collection_c: CollectionC;
}
interface MySchema {
// regular collections are array types
collection_a: CollectionA[];
// singleton collections are singular types
collection_c: CollectionC;
}
Improving Results
For the most reliable results, the root schema types should be kept as pure as possible. This means avoiding unions (CollectionA | null
), optional types (optional_collection?: CollectionA[]
), and preferably inline relational types (types nested directly on the root schema) except when adding custom fields to core collections.
Custom Fields on Core Collections
Core collections are provided and required in every Directus project, and are prefixed with directus_
. Directus projects can add additional fields to these collections, but should also be included in the schema when initializing the Directus SDK.
To define custom fields on the core collections, add a type containing only your custom fields as a singular type.
interface MySchema {
// regular collections are array types
collection_a: CollectionA[];
// singleton collections are singular types
collection_c: CollectionC;
// extend the provided DirectusUser type
directus_users: CustomUser;
}
interface CustomUser {
custom_field: string;
}
interface MySchema {
// regular collections are array types
collection_a: CollectionA[];
// singleton collections are singular types
collection_c: CollectionC;
// extend the provided DirectusUser type
directus_users: CustomUser;
}
interface CustomUser {
custom_field: string;
}
Collection Field Types
Most Directus field types will map to one of the TypeScript primitive types (string
, number
, boolean
). There are some exceptions to this where literal types are used to distinguish between primitives in order to add extra functionality.
interface CollectionA {
id: number;
status: string;
toggle: boolean;
}
interface CollectionA {
id: number;
status: string;
toggle: boolean;
}
There are currently 3 literal types that can be applied. The first 2 are both used to apply the count(field)
array function in the filter
/field
auto-complete suggestions, these are the 'json'
and 'csv'
string literal types. The 'datetime'
string literal type which is used to apply all datetime functions in the filter
/field
auto-complete suggestions.
interface CollectionA {
id: number;
status: string;
toggle: boolean;
tags: 'csv';
json_field: 'json';
date_created: 'datetime';
}
interface CollectionA {
id: number;
status: string;
toggle: boolean;
tags: 'csv';
json_field: 'json';
date_created: 'datetime';
}
In the output types these string literals will get resolved to their appropriate types:
'csv'
resolves tostring[]
'datetime'
resolves tostring
'json'
resolves toJsonValue
Types to Avoid
Some types should be avoided in the Schema as they may not play well with the type logic: any
or any[]
, empty type {}
, never
or void
.
Adding Relational Fields
For regular relations without junction collections, define a relation using a union of the primary key type and the related object.
interface MySchema {
// regular collections are array types
collection_a: CollectionA[];
collection_b: CollectionB[];
// singleton collections are singular types
collection_c: CollectionC;
// extend the provided DirectusUser type
directus_users: CustomUser;
}
interface CollectionB {
id: string;
}
interface MySchema {
// regular collections are array types
collection_a: CollectionA[];
collection_b: CollectionB[];
// singleton collections are singular types
collection_c: CollectionC;
// extend the provided DirectusUser type
directus_users: CustomUser;
}
interface CollectionB {
id: string;
}
Many to One
interface CollectionB {
id: string;
m2o: number | CollectionA;
}
interface CollectionB {
id: string;
m2o: number | CollectionA;
}
One to Many
interface CollectionB {
id: string;
m2o: number | CollectionA;
o2m: number[] | CollectionA[];
}
interface CollectionB {
id: string;
m2o: number | CollectionA;
o2m: number[] | CollectionA[];
}
Working with Junction Collections
For relations that rely on a junction collection, define the junction collection on the root schema and refer to this new type similar to the one to many relation above.
Many to Many
interface MySchema {
// regular collections are array types
collection_a: CollectionA[];
collection_b: CollectionB[];
// singleton collections are singular types
collection_c: CollectionC;
// many-to-many junction collection
collection_b_a_m2m: CollectionBA_Many[];
// extend the provided DirectusUser type
directus_users: CustomUser;
}
// many-to-many junction table
interface CollectionBA_Many {
id: number;
collection_b_id: string | CollectionB;
collection_a_id: number | CollectionA;
}
interface CollectionB {
id: string;
m2o: number | CollectionA;
o2m: number[] | CollectionA[];
m2m: number[] | CollectionBA_Many[];
}
interface MySchema {
// regular collections are array types
collection_a: CollectionA[];
collection_b: CollectionB[];
// singleton collections are singular types
collection_c: CollectionC;
// many-to-many junction collection
collection_b_a_m2m: CollectionBA_Many[];
// extend the provided DirectusUser type
directus_users: CustomUser;
}
// many-to-many junction table
interface CollectionBA_Many {
id: number;
collection_b_id: string | CollectionB;
collection_a_id: number | CollectionA;
}
interface CollectionB {
id: string;
m2o: number | CollectionA;
o2m: number[] | CollectionA[];
m2m: number[] | CollectionBA_Many[];
}
Many to Any
interface MySchema {
// regular collections are array types
collection_a: CollectionA[];
collection_b: CollectionB[];
// singleton collections are singular types
collection_c: CollectionC;
// many-to-many junction collection
collection_b_a_m2m: CollectionBA_Many[];
// many-to-any junction collection
collection_b_a_m2a: CollectionBA_Any[];
// extend the provided DirectusUser type
directus_users: CustomUser;
}
// many-to-any junction table
interface CollectionBA_Any {
id: number;
collection_b_id: string | CollectionB;
collection: 'collection_a' | 'collection_c';
item: string | CollectionA | CollectionC;
}
interface CollectionB {
id: string;
m2o: number | CollectionA;
o2m: number[] | CollectionA[];
m2m: number[] | CollectionBA_Many[];
m2m: number[] | CollectionBA_Any[];
}
interface MySchema {
// regular collections are array types
collection_a: CollectionA[];
collection_b: CollectionB[];
// singleton collections are singular types
collection_c: CollectionC;
// many-to-many junction collection
collection_b_a_m2m: CollectionBA_Many[];
// many-to-any junction collection
collection_b_a_m2a: CollectionBA_Any[];
// extend the provided DirectusUser type
directus_users: CustomUser;
}
// many-to-any junction table
interface CollectionBA_Any {
id: number;
collection_b_id: string | CollectionB;
collection: 'collection_a' | 'collection_c';
item: string | CollectionA | CollectionC;
}
interface CollectionB {
id: string;
m2o: number | CollectionA;
o2m: number[] | CollectionA[];
m2m: number[] | CollectionBA_Many[];
m2m: number[] | CollectionBA_Any[];
}
Working with Generated Output
async function getCollectionA() {
return await client.request(
readItems('collection_a', {
fields: ['id']
})
)
}
// generated type that can be used in the component
// resolves to { "id": number } in this example
type GeneratedType = Awaited<ReturnType<typeof getCollectionA>>;
async function getCollectionA() {
return await client.request(
readItems('collection_a', {
fields: ['id']
})
)
}
// generated type that can be used in the component
// resolves to { "id": number } in this example
type GeneratedType = Awaited<ReturnType<typeof getCollectionA>>;
Working with Input Query Types
For the output types to work properly, the fields
list needs to be static so the types can read the fields that were selected in the query.
// this does not work and resolves to string[], losing all information about the fields themselves
const fields = ["id", "status"];
// correctly resolves to readonly ["id", "status"]
const fields = ["id", "status"] as const;
// this does not work and resolves to string[], losing all information about the fields themselves
const fields = ["id", "status"];
// correctly resolves to readonly ["id", "status"]
const fields = ["id", "status"] as const;
Complete example:
const query: Query<MySchema, CollectionA> = {
limit: 20,
offset: 0,
};
let search = 'test';
if (search) {
query.search = search;
}
// create a second query for literal/readonly type inference
const query2 = {
...query,
fields: [
"id", "status"
],
} satisfies Query<MySchema, CollectionA>;
const results = await directusClient.request(readItems("collection_a", query2));
// or build the query directly inline
const results2 = await directusClient.request(readItems("collection_a", {
...query,
search,
fields: [
"id", "status"
],
}));
const query: Query<MySchema, CollectionA> = {
limit: 20,
offset: 0,
};
let search = 'test';
if (search) {
query.search = search;
}
// create a second query for literal/readonly type inference
const query2 = {
...query,
fields: [
"id", "status"
],
} satisfies Query<MySchema, CollectionA>;
const results = await directusClient.request(readItems("collection_a", query2));
// or build the query directly inline
const results2 = await directusClient.request(readItems("collection_a", {
...query,
search,
fields: [
"id", "status"
],
}));
Alias Unsupported
At this time, alias
has not been typed yet for use in other query parameters like deep
.