DatoCMS Content Management API Utilities
Take a look at the full API documentation for examples!
Field Types
This library provides comprehensive TypeScript type definitions and utilities for all DatoCMS field types. Each field type includes type guards, validation functions, localization support, and editor appearance configurations.
What's available
Every field type follows a consistent pattern providing:
- Field value types: TypeScript definitions for the field's data structure
- Type guards: Functions to validate field values at runtime
- Localization support: Utilities for handling localized field variants
- Validation types: Supported validators for the field type
- Appearance configuration: Editor types and their configuration options
Example: lat_lon Field Type
View example
import { isLatLonFieldValue, isLocalizedLatLonFieldValue } from '@datocms/cma-client';
import type { LatLonFieldValue, LatLonFieldValidators, LatLonFieldAppearance } from '@datocms/cma-client';
const value: LatLonFieldValue = { latitude: 45.4642, longitude: 9.1900 };
if (isLatLonFieldValue(someValue)) {
}
if (isLocalizedLatLonFieldValue(localizedValue)) {
}
type Validators = LatLonFieldValidators;
type Appearance = LatLonFieldAppearance;
Context-Dependent field types
Some field types have different value formats depending on the API context (request vs response) or query parameters:
Request vs Response variations
File and Gallery fields have different type requirements for API requests versus responses:
View example
import {
FileFieldValue,
FileFieldValueInRequest,
GalleryFieldValue,
GalleryFieldValueInRequest,
isFileFieldValue,
isFileFieldValueInRequest,
isGalleryFieldValue,
isGalleryFieldValueInRequest
} from '@datocms/cma-client';
const fileResponse: FileFieldValue = {
upload_id: "12345",
alt: null,
title: null,
custom_data: {},
focal_point: null
};
const fileRequest: FileFieldValueInRequest = {
upload_id: "12345"
};
if (isFileFieldValueInRequest(someFileValue)) {
}
if (isGalleryFieldValue(someGalleryValue)) {
}
"Nested Mode" Response variations
Block-containing fields (structured_text, single_block, rich_text) support different block representations for regular responses, for "Nested Mode" responses, and for requests:
View example
import {
StructuredTextFieldValue,
StructuredTextFieldValueInRequest,
StructuredTextFieldValueInNestedResponse,
isStructuredTextFieldValue,
isStructuredTextFieldValueInRequest,
isStructuredTextFieldValueInNestedResponse
} from '@datocms/cma-client';
const standard: StructuredTextFieldValue = {
document: {
type: "root",
children: [
{
type: "block",
item: "IdMLV2GJTXyQ0Bfns7R4IQ"
}
]
}
};
const nested: StructuredTextFieldValueInNestedResponse = {
document: {
type: "root",
children: [
{
type: "block",
item: {
id: "IdMLV2GJTXyQ0Bfns7R4IQ",
type: "item",
attributes: { },
relationships: { }
}
}
]
}
};
const request: StructuredTextFieldValueInRequest = {
document: {
type: "root",
children: [
{
type: "block",
item: "FicV5CxCSQ6yOrgfwRoiKA"
},
{
type: "block",
item: {
type: "item",
attributes: { },
relationships: { }
}
}
]
}
};
if (isStructuredTextFieldValueInNestedResponse(someStructuredText)) {
}
if (isStructuredTextFieldValueInRequest(requestData)) {
}
These variants ensure type safety across different API contexts while maintaining the same conceptual data structure. All localized variants also have corresponding type guards (e.g., isLocalizedStructuredTextFieldValueInRequest, isLocalizedStructuredTextFieldValueInNestedResponse, etc.).
TypeScript Generics Support: For maximum type safety, all field value types and type guards for block-containing fields accept ItemTypeDefinition generics to provide precise typing for your specific schema:
View example
import type { MyArticle, MyArticleSection } from './schema';
const content: StructuredTextFieldValueInRequest<MyArticleSection> = {
document: {
type: "root",
children: []
}
};
if (isStructuredTextFieldValueInNestedResponse<MyArticleSection>(value)) {
}
Block Processing Utilities
Inspecting Records and Blocks
The inspectItem() function provides a visual, tree-structured representation of DatoCMS records in the console, making it easier to debug and understand complex content structures.
inspectItem()
Formats a DatoCMS item (record or block) as a visual tree structure, showing all fields with proper formatting for each field type. Particularly useful for debugging nested structures like modular content and structured text.
View details
TypeScript Signature:
function inspectItem(
item: Item,
options?: InspectItemOptions
): string
type InspectItemOptions = {
maxWidth?: number;
}
Parameters:
item: Any DatoCMS item, including records, blocks, or items in create/update format
options: Optional configuration object
maxWidth: Maximum characters to display for text fields before truncating with "..."
Returns: A formatted string representation of the item as a tree structure
Usage Example:
import { inspectItem } from '@datocms/cma-client';
const record = await client.items.find('MgCNaAI0RxSG8CA9sDXCHg');
console.log(inspectItem(record));
Creating and Duplicating Blocks
buildBlockRecord()
Converts a block data object into the proper format for API requests.
View details
TypeScript Signature:
function buildBlockRecord<D extends ItemTypeDefinition>(
body: ItemUpdateSchema<ToItemDefinitionInRequest<D>>
): NewBlockInRequest<ToItemDefinitionInRequest<D>>
Parameters:
body: Block data in update schema format
Returns: Formatted block record ready for API requests
duplicateBlockRecord()
Creates a deep copy of a block record, including all nested blocks, removing IDs to create new instances.
View details
TypeScript Signature:
async function duplicateBlockRecord<D extends ItemTypeDefinition>(
existingBlock: ItemWithOptionalIdAndMeta<ToItemDefinitionInNestedResponse<D>>,
schemaRepository: SchemaRepository
): Promise<NewBlockInRequest<ToItemDefinitionInRequest<D>>>
Parameters:
existingBlock: The block to duplicate
schemaRepository: Repository for schema lookups
Returns: New block record without IDs, ready to be created
Narrowing Block Types
isBlockOfType()
Builds a type guard that narrows a union of block shapes to the one matching a given model. Meant for Array#filter / Array#find over block-bearing fields — either nested-response arrays (from client.items.find(..., { nested: true })) or request-payload arrays you're inspecting before sending.
TypeScript doesn't auto-narrow on discriminators buried in nested properties, so the natural-looking check block.relationships.item_type.data.id === SOME_ID won't narrow the block's type. This guard does the walk and returns a proper type-guard predicate.
View details
TypeScript Signature:
function isBlockOfType<Id extends string>(
itemTypeId: Id,
): <T>(block: T) => block is NarrowBlockByItemType<T, Id>
function isBlockOfType<T, Id extends string>(
itemTypeId: Id,
block: T,
): block is NarrowBlockByItemType<T, Id>
type NarrowBlockByItemType<T, Id extends string> = Extract<
T,
{ relationships: { item_type: { data: { type: 'item_type'; id: Id } } } }
>
Parameters:
itemTypeId: The item-type ID literal. For narrowing to work, the argument must be typed as a literal — use as const on pre-set ID constants. No ItemTypeDefinition type parameter is needed: Extract walks the input union using just the ID.
block (direct form only): The block to check.
Returns:
- Curried form: a predicate
(block) => block is D-typed-block.
- Direct form: a
boolean that also acts as a type guard on block.
In both cases the guard:
- Narrows blocks carrying
relationships.item_type.data.id — that covers BlockInNestedResponse<D> and the object variants of BlockInRequest<D> (UpdatedBlockInRequest, NewBlockInRequest).
- Returns
false for plain string IDs (unchanged-reference form in request payloads) and for any non-block input.
The default (non-nested) response shape, where block fields are arrays of plain string IDs, is deliberately not supported — there's no way to recover the type from an ID alone.
Usage Example:
import { isBlockOfType } from '@datocms/cma-client';
const IMAGE_BLOCK_ID = 'FJM79jjKRMSVg-fR6k6X2A' as const;
const article = await client.items.find<Schema.Article>(articleId, { nested: true });
const images = article.content.filter(
(b) => b.relationships.item_type.data.id === IMAGE_BLOCK_ID,
);
images[0].attributes.upload_id;
const images = article.content.filter(isBlockOfType(IMAGE_BLOCK_ID));
images[0].attributes.upload_id;
const first = article.content[0];
if (isBlockOfType(IMAGE_BLOCK_ID, first)) {
first.attributes.upload_id;
}
Use the curried form when you need a predicate for .filter / .find; use the direct form for one-off if checks. The __itemTypeId discriminator is also available for inline switch narrowing on a single value.
Recursive Block Operations
DatoCMS supports three field types that can contain blocks: Modular Content (arrays of blocks), Single Block fields, and Structured Text (rich-text with embedded blocks). These functions abstract away the differences between field types and can traverse blocks recursively, processing nested blocks within blocks. They require a SchemaRepository instance to look up field definitions for nested blocks.
visitBlocksInNonLocalizedFieldValue()
Visit every block in a non-localized field value recursively, including blocks nested within other blocks.
View details
TypeScript Signature:
async function visitBlocksInNonLocalizedFieldValue(
nonLocalizedFieldValue: unknown,
fieldType: string,
schemaRepository: SchemaRepository,
visitor: (item: BlockInRequest, path: TreePath) => void | Promise<void>,
): Promise<void>
Parameters:
nonLocalizedFieldValue: The non-localized field value
fieldType: The type of DatoCMS field (ie. string, rich_text, etc.)
schemaRepository: Repository for caching schema lookups
visitor: Function called for each block (including nested)
mapBlocksInNonLocalizedFieldValue()
Transform all blocks in a non-localized field value recursively, including nested blocks.
View details
TypeScript Signature:
async function mapBlocksInNonLocalizedFieldValue(
nonLocalizedFieldValue: unknown,
fieldType: string,
schemaRepository: SchemaRepository,
mapper: (item: BlockInRequest, path: TreePath) => BlockInRequest | Promise<BlockInRequest>,
): Promise<unknown>
Parameters:
nonLocalizedFieldValue: The non-localized field value
fieldType: The type of DatoCMS field (ie. string, rich_text, etc.)
schemaRepository: Repository for caching schema lookups
mapper: Function that transforms each block
Returns: New field value
filterBlocksInNonLocalizedFieldValue()
Filter blocks recursively, removing blocks at any nesting level that don't match the predicate.
View details
TypeScript Signature:
async function filterBlocksInNonLocalizedFieldValue(
nonLocalizedFieldValue: unknown,
fieldType: string,
schemaRepository: SchemaRepository,
predicate: (item: BlockInRequest, path: TreePath) => boolean | Promise<boolean>,
): Promise<unknown>
Parameters:
nonLocalizedFieldValue: The non-localized field value to filter
fieldType: The type of DatoCMS field (ie. string, rich_text, etc.)
schemaRepository: Repository for caching schema lookups
predicate: Function that tests each block
Returns: New field value with filtered blocks
Usage Example:
const noVideos = await filterBlocksInNonLocalizedFieldValue(
schemaRepository,
field,
fieldValue,
(block) => block.relationships.item_type.data.id !== 'video_block'
);
findAllBlocksInNonLocalizedFieldValue()
Find all blocks that match the predicate, searching recursively through nested blocks.
View details
TypeScript Signature:
async function findAllBlocksInNonLocalizedFieldValue(
nonLocalizedFieldValue: unknown,
fieldType: string,
schemaRepository: SchemaRepository,
predicate: (item: BlockInRequest, path: TreePath) => boolean | Promise<boolean>,
): Promise<Array<{ item: BlockInRequest; path: TreePath }>>
Parameters:
nonLocalizedFieldValue: The non-localized field value to search
fieldType: The type of DatoCMS field (ie. string, rich_text, etc.)
schemaRepository: Repository for caching schema lookups
predicate: Function that tests each block
Returns: Array of all matching blocks with their paths
reduceBlocksInNonLocalizedFieldValue()
Reduce all blocks recursively to a single value.
View details
TypeScript Signature:
async function reduceBlocksInNonLocalizedFieldValue<R>(
nonLocalizedFieldValue: unknown,
fieldType: string,
schemaRepository: SchemaRepository,
reducer: (accumulator: R, item: BlockInRequest, path: TreePath) => R | Promise<R>,
initialValue: R,
): Promise<R>
Parameters:
nonLocalizedFieldValue: The non-localized field value to reduce
fieldType: The type of DatoCMS field (ie. string, rich_text, etc.)
schemaRepository: Repository for caching schema lookups
reducer: Function that processes each block
initialValue: Initial accumulator value
Returns: The final accumulated value
someBlocksInNonLocalizedFieldValue()
Check if any block (including nested) matches the predicate.
View details
TypeScript Signature:
async function someBlocksInNonLocalizedFieldValue(
nonLocalizedFieldValue: unknown,
fieldType: string,
schemaRepository: SchemaRepository,
predicate: (item: BlockInRequest, path: TreePath) => boolean | Promise<boolean>,
): Promise<boolean>
Parameters:
nonLocalizedFieldValue: The non-localized field value to test
fieldType: The type of DatoCMS field (ie. string, rich_text, etc.)
schemaRepository: Repository for caching schema lookups
predicate: Function that tests each block
Returns: True if any block matches
everyBlockInNonLocalizedFieldValue()
Check if every block (including nested) matches the predicate.
View details
TypeScript Signature:
async function everyBlockInNonLocalizedFieldValue(
nonLocalizedFieldValue: unknown,
fieldType: string,
schemaRepository: SchemaRepository,
predicate: (item: BlockInRequest, path: TreePath) => boolean | Promise<boolean>,
): Promise<boolean>
Parameters:
nonLocalizedFieldValue: The non-localized field value to test
fieldType: The type of DatoCMS field (ie. string, rich_text, etc.)
schemaRepository: Repository for caching schema lookups
predicate: Function that tests each block
Returns: True if all blocks match
Unified Field Processing (Localized & Non-Localized)
These utilities provide a unified interface for working with DatoCMS field values that may or may not be localized. They eliminate the need for conditional logic when processing fields that could be either localized or non-localized.
mapNormalizedFieldValues() / mapNormalizedFieldValuesAsync()
Apply a transformation function to field values, handling both localized and non-localized fields uniformly.
View details
TypeScript Signatures:
function mapNormalizedFieldValues<TInput, TOutput>(
localizedOrNonLocalizedFieldValue: TInput | LocalizedFieldValue<TInput>,
field: Field,
mapFn: (locale: string | undefined, localeValue: TInput) => TOutput
): TOutput | LocalizedFieldValue<TOutput>
async function mapNormalizedFieldValuesAsync<TInput, TOutput>(
localizedOrNonLocalizedFieldValue: TInput | LocalizedFieldValue<TInput>,
field: Field,
mapFn: (locale: string | undefined, localeValue: TInput) => Promise<TOutput>
): Promise<TOutput | LocalizedFieldValue<TOutput>>
Parameters:
localizedOrNonLocalizedFieldValue: The field value (localized or non-localized)
field: The DatoCMS field definition
mapFn: Function to transform each value (receives locale for localized fields, undefined for non-localized)
Returns: Transformed value maintaining the same structure
filterNormalizedFieldValues() / filterNormalizedFieldValuesAsync()
Filter field values based on a predicate, handling both localized and non-localized fields.
View details
TypeScript Signatures:
function filterNormalizedFieldValues<T>(
localizedOrNonLocalizedFieldValue: T | LocalizedFieldValue<T>,
field: Field,
filterFn: (locale: string | undefined, localeValue: T) => boolean
): T | LocalizedFieldValue<T> | undefined
async function filterNormalizedFieldValuesAsync<T>(
localizedOrNonLocalizedFieldValue: T | LocalizedFieldValue<T>,
field: Field,
filterFn: (locale: string | undefined, localeValue: T) => Promise<boolean>
): Promise<T | LocalizedFieldValue<T> | undefined>
Parameters:
localizedOrNonLocalizedFieldValue: The field value to filter
field: The DatoCMS field definition
filterFn: Predicate function for filtering
Returns: Filtered value or undefined if all filtered out
visitNormalizedFieldValues() / visitNormalizedFieldValuesAsync()
Visit each value in a field, handling both localized and non-localized fields.
View details
TypeScript Signatures:
function visitNormalizedFieldValues<T>(
localizedOrNonLocalizedFieldValue: T | LocalizedFieldValue<T>,
field: Field,
visitFn: (locale: string | undefined, localeValue: T) => void
): void
async function visitNormalizedFieldValuesAsync<T>(
localizedOrNonLocalizedFieldValue: T | LocalizedFieldValue<T>,
field: Field,
visitFn: (locale: string | undefined, localeValue: T) => Promise<void>
): Promise<void>
Parameters:
localizedOrNonLocalizedFieldValue: The field value to visit
field: The DatoCMS field definition
visitFn: Function called for each value
someNormalizedFieldValues() / someNormalizedFieldValuesAsync()
Check if at least one field value passes the test.
View details
TypeScript Signatures:
function someNormalizedFieldValues<T>(
localizedOrNonLocalizedFieldValue: T | LocalizedFieldValue<T>,
field: Field,
testFn: (locale: string | undefined, localeValue: T) => boolean
): boolean
async function someNormalizedFieldValuesAsync<T>(
localizedOrNonLocalizedFieldValue: T | LocalizedFieldValue<T>,
field: Field,
testFn: (locale: string | undefined, localeValue: T) => Promise<boolean>
): Promise<boolean>
Parameters:
localizedOrNonLocalizedFieldValue: The field value to test
field: The DatoCMS field definition
testFn: Predicate function
Returns: True if any value passes the test
everyNormalizedFieldValue() / everyNormalizedFieldValueAsync()
Check if all field values pass the test.
View details
TypeScript Signatures:
function everyNormalizedFieldValue<T>(
localizedOrNonLocalizedFieldValue: T | LocalizedFieldValue<T>,
field: Field,
testFn: (locale: string | undefined, localeValue: T) => boolean
): boolean
async function everyNormalizedFieldValueAsync<T>(
localizedOrNonLocalizedFieldValue: T | LocalizedFieldValue<T>,
field: Field,
testFn: (locale: string | undefined, localeValue: T) => Promise<boolean>
): Promise<boolean>
Parameters:
localizedOrNonLocalizedFieldValue: The field value to test
field: The DatoCMS field definition
testFn: Predicate function
Returns: True if all values pass the test
toNormalizedFieldValueEntries() / fromNormalizedFieldValueEntries()
Convert field values to/from a normalized entry format for uniform processing.
View details
TypeScript Signatures:
function toNormalizedFieldValueEntries<T>(
localizedOrNonLocalizedFieldValue: T | LocalizedFieldValue<T>,
field: Field
): NormalizedFieldValueEntry<T>[]
function fromNormalizedFieldValueEntries<T>(
entries: NormalizedFieldValueEntry<T>[],
field: Field
): T | LocalizedFieldValue<T>
type NormalizedFieldValueEntry<T> = {
locale: string | undefined;
value: T;
}
Parameters:
localizedOrNonLocalizedFieldValue/entries: Value to convert from/to
field: The DatoCMS field definition
Returns: Normalized entries array or reconstructed field value
Usage Example:
const entries = toNormalizedFieldValueEntries(fieldValue, field);
const processed = entries.map(({ locale, value }) => ({
locale,
value: processValue(value)
}));
const result = fromNormalizedFieldValueEntries(processed, field);
SchemaRepository
The SchemaRepository class provides a lightweight, in-memory cache for DatoCMS schema entities (item types, fields, fieldsets, and plugins). It helps avoid redundant API calls when working across multiple functions or utilities that require schema lookups.
Why use it?
- Cache once, reuse everywhere: The first API call stores results in memory; all subsequent lookups are instant.
- Efficient schema access: Retrieve entities by ID, API key, or package name without re-fetching.
- Optimized for block processing: Essential for utilities like
mapBlocksInNonLocalizedFieldValue.
- Fewer API calls: Dramatically speeds up bulk operations and complex traversals.
Usage Example:
View example
const schemaRepository = new SchemaRepository(client);
const blogPost = await schemaRepository.getItemTypeByApiKey('blog_post');
const fields = await schemaRepository.getItemTypeFields(blogPost);
const sameBlogPost = await schemaRepository.getItemTypeByApiKey('blog_post');
const sameFields = await schemaRepository.getItemTypeFields(blogPost);
await mapBlocksInNonLocalizedFieldValue(
fieldValue,
fieldType,
schemaRepository,
async (block) => {
}
);
When to Use
- Traversing relationships that repeatedly query schema
- Bulk record processing scripts
- Block-processing utilities that need frequent lookups
- Any script where reducing API calls matters
When Not to Use
- Scripts that modify schema (models, fields, etc.)
- Long-running applications (cache never expires)
- Situations where the schema might change during execution
Class signature
class SchemaRepository {
constructor(client: GenericClient)
async getAllItemTypes(): Promise<ItemType[]>
async getAllModels(): Promise<ItemType[]>
async getAllBlockModels(): Promise<ItemType[]>
async getItemTypeByApiKey(apiKey: string): Promise<ItemType>
async getItemTypeById(id: string): Promise<ItemType>
async getItemTypeFields(itemType: ItemType): Promise<Field[]>
async getItemTypeFieldsets(itemType: ItemType): Promise<Fieldset[]>
async getModelsEmbeddingBlocks(blocks: ItemType[]): Promise<ItemType[]>
async getNestedBlocks(itemTypes: ItemType[]): Promise<ItemType[]>
async getNestedModels(itemTypes: ItemType[]): Promise<ItemType[]>
async getAllPlugins(): Promise<Plugin[]>
async getPluginById(id: string): Promise<Plugin>
async getPluginByPackageName(packageName: string): Promise<Plugin>
async getAllRawItemTypes(): Promise<RawItemType[]>
async getRawItemTypeByApiKey(apiKey: string): Promise<RawItemType>
async getRawNestedBlocks(itemTypes: Array<ItemType | RawItemType>): Promise<Array<RawItemType>>
async getRawNestedModels(itemTypes: Array<ItemType | RawItemType>): Promise<Array<RawItemType>>
}
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/datocms/js-rest-api-clients. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The package is available as open source under the terms of the MIT License.