Skip to main content

Manipulating documents and entries with a TypeScript-based project

Page summary:

Safely manipulate documents and entries in Strapi v5 TypeScript projects using the UID and Data namespaces for type safety, enabling both generic and known entity type operations with code completion.

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.

Prerequisites
  • 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 application
  • UID.Component represents a union of every component identifier in the application
  • UID.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 object
  • Data.Component represents a Strapi component object
  • Data.Entity represents either a document or a component
Tip

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;
// ...
// }
}
Warning

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::account.account', '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'>
Was this page helpful?