Manipulating documents and entries
This guide will explore TypeScript patterns for manipulating documents and entries in a Strapi v5 application, including how to leverage Strapi's UID
and Data
namespaces to interact with both generic and known entity types safely. If you're working on a TypeScript-based Strapi project, mastering these approaches will help you take full advantage of type safety and code completion, ensuring robust, error-free interactions with your application’s content and components.
- Strapi Application: A Strapi v5 application. If you don't have one, follow the documentation to get started.
- TypeScript: Ensure TypeScript is set up in your Strapi project. You can follow Strapi's official guide on configuring TypeScript.
- Generated Types: Application types have been generated and are accessible.
Type Imports
The UID
namespace contains literal unions representing the available resources in the application.
import type { UID } from '@strapi/strapi';
UID.ContentType
represents a union of every content-type identifier in the applicationUID.Component
represents a union of every component identifier in the applicationUID.Schema
represents a union of every schema (content-type or component) identifier in the application- And others...
Strapi provides a Data
namespace containing several built-in types for entity representation.
import type { Data } from '@strapi/strapi';
Data.ContentType
represents a Strapi document objectData.Component
represents a Strapi component objectData.Entity
represents either a document or a component
Both the entities' type definitions and UIDs are based on the generated schema types for your application.
In case of a mismatch or error, you can always regenerate the types.
Usage
Generic entities
When dealing with generic data, it is recommended to use non-parametrized forms of the Data
types.
Generic documents
async function save(name: string, document: Data.ContentType) {
await writeCSV(name, document);
// ^ {
// id: Data.ID;
// documentId: string;
// createdAt?: DateTimeValue;
// updatedAt?: DateTimeValue;
// publishedAt?: DateTimeValue;
// ...
// }
}
In the preceding example, the resolved properties for document
are those common to every content-type.
Other properties have to be checked manually using type guards.
if ('my_prop' in document) {
return document.my_prop;
}
Generic components
function renderComponent(parent: Node, component: Data.Component) {
const elements: Element[] = [];
const properties = Object.entries(component);
for (const [name, value] of properties) {
// ^ ^
// string any
const paragraph = document.createElement('p');
paragraph.textContent = `Key: ${name}, Value: ${value}`;
elements.push(paragraph);
}
parent.append(...elements);
}
Known entities
When manipulating known entities, it is possible to parametrize Data
types for better type safety and code completion.
Known documents
const ALL_CATEGORIES = ['food', 'tech', 'travel'];
function validateArticle(article: Data.ContentType<'api::article.article'>) {
const { title, category } = article;
// ^? ^?
// string Data.ContentType<'api::category.category'>
if (title.length < 5) {
throw new Error('Title too short');
}
if (!ALL_CATEGORIES.includes(category.name)) {
throw new Error(`Unknown category ${category.name}`);
}
}
Known components
function processUsageMetrics(
id: string,
metrics: Data.Component<'app.metrics'>
) {
telemetry.send(id, { clicks: metrics.clicks, views: metrics.views });
}
Advanced use cases
Entities subsets
Using the types' second parameter (TKeys
), it is possible to obtain a subset of an entity.
type Credentials = Data.ContentType<'api::acount.acount', 'email' | 'password'>;
// ^? { email: string; password: string }
type UsageMetrics = Data.Component<'app.metrics', 'clicks' | 'views'>;
// ^? { clicks: number; views: number }
Type argument inference
It is possible to bind and restrict an entity type based on other function parameters.
In the following example, the uid
type is inferred upon usage as T
and used as a type parameter for the document
.
import type { UID } from '@strapi/strapi';
function display<T extends UID.ContentType>(
uid: T,
document: Data.ContentType<T>
) {
switch (uid) {
case 'api::article.article': {
return document.title;
// ^? string
// ^? Data.ContentType<'api::article.article'>
}
case 'api::category.category': {
return document.name;
// ^? string
// ^? Data.ContentType<'api::category.category'>
}
case 'api::account.account': {
return document.email;
// ^? string
// ^? Data.ContentType<'api::account.account'>
}
default: {
throw new Error(`unknown content-type uid: "${uid}"`);
}
}
}
When calling the function, the document
type needs to match the given uid
.
declare const article: Data.Document<'api::article.article'>;
declare const category: Data.Document<'api::category.category'>;
declare const account: Data.Document<'api::account.account'>;
display('api::article.article', article);
display('api::category.category', category);
display('api::account.account', account);
// ^ ✅
display('api::article.article', category);
// ^ Error: "category" is not assignable to parameter of type ContentType<'api::article.article'>