
Product
Socket for Jira Is Now Available
Socket for Jira lets teams turn alerts into Jira tickets with manual creation, automated ticketing rules, and two-way sync.
@datocms/cma-client
Advanced tools
Take a look at the full API documentation for examples!
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.
Every field type follows a consistent pattern providing:
Example: lat_lon Field Type
import { isLatLonFieldValue, isLocalizedLatLonFieldValue } from '@datocms/cma-client';
import type { LatLonFieldValue, LatLonFieldValidators, LatLonFieldAppearance } from '@datocms/cma-client';
// Field value type - object with latitude/longitude or null
const value: LatLonFieldValue = { latitude: 45.4642, longitude: 9.1900 };
// Type guard functions for validation
if (isLatLonFieldValue(someValue)) {
// someValue is guaranteed to be { latitude: number; longitude: number } | null
}
if (isLocalizedLatLonFieldValue(localizedValue)) {
// localizedValue is a localized lat/lon field
}
// Validator and appearance types available for type-safe configuration
type Validators = LatLonFieldValidators;
type Appearance = LatLonFieldAppearance;
Some field types have different value formats depending on the API context (request vs response) or query parameters:
File and Gallery fields have different type requirements for API requests versus responses:
import {
FileFieldValue,
FileFieldValueInRequest,
GalleryFieldValue,
GalleryFieldValueInRequest,
// Type guards for runtime validation
isFileFieldValue,
isFileFieldValueInRequest,
isGalleryFieldValue,
isGalleryFieldValueInRequest
} from '@datocms/cma-client';
// API Response format - all metadata fields present with defaults
const fileResponse: FileFieldValue = {
upload_id: "12345",
alt: null, // Always present (default: null)
title: null, // Always present (default: null)
custom_data: {}, // Always present (default: {})
focal_point: null // Always present (default: null)
};
// API Request format - metadata fields are optional
const fileRequest: FileFieldValueInRequest = {
upload_id: "12345"
// alt, title, custom_data, focal_point are optional
};
// Runtime validation for different contexts
if (isFileFieldValueInRequest(someFileValue)) {
// someFileValue has optional metadata fields
}
if (isGalleryFieldValue(someGalleryValue)) {
// someGalleryValue is array of files with all metadata present
}
Block-containing fields (structured_text, single_block, rich_text) support different block representations for regular responses, for "Nested Mode" responses, and for requests:
import {
StructuredTextFieldValue,
StructuredTextFieldValueInRequest,
StructuredTextFieldValueInNestedResponse,
// Type guards for all variations (also available for single_block and rich_text)
isStructuredTextFieldValue,
isStructuredTextFieldValueInRequest,
isStructuredTextFieldValueInNestedResponse
} from '@datocms/cma-client';
// Regular response - blocks as string IDs
const standard: StructuredTextFieldValue = {
document: {
type: "root",
children: [
{
type: "block",
// String ID reference
item: "IdMLV2GJTXyQ0Bfns7R4IQ"
}
]
}
};
// Nested Mode response (?nested=true) - blocks as full objects
const nested: StructuredTextFieldValueInNestedResponse = {
document: {
type: "root",
children: [
{
type: "block",
// Always full block object
item: {
id: "IdMLV2GJTXyQ0Bfns7R4IQ",
type: "item",
attributes: { /* ... */ },
relationships: { /* ... */ }
}
}
]
}
};
// Request format - flexible block representation
const request: StructuredTextFieldValueInRequest = {
document: {
type: "root",
children: [
{
type: "block",
// Can be string ID, to keep block unchanged...
item: "FicV5CxCSQ6yOrgfwRoiKA"
},
{
type: "block",
// ...or full block object (to create new blocks or update existing ones)
item: {
type: "item",
attributes: { /* ... */ },
relationships: { /* ... */ }
}
}
]
}
};
// Runtime validation for different contexts
if (isStructuredTextFieldValueInNestedResponse(someStructuredText)) {
// someStructuredText has blocks as full objects
}
if (isStructuredTextFieldValueInRequest(requestData)) {
// requestData allows flexible block representations
}
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:
import type { MyArticle, MyArticleSection } from './schema';
// Fully typed structured text with specific block types
const content: StructuredTextFieldValueInRequest<MyArticleSection> = {
document: {
type: "root",
children: [/* ... */]
}
};
// Type guard with generic for precise validation
if (isStructuredTextFieldValueInNestedResponse<MyArticleSection>(value)) {
// value is now typed with your specific block schema
}
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.
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.
TypeScript Signature:
function inspectItem(
item: Item,
options?: InspectItemOptions
): string
type InspectItemOptions = {
maxWidth?: number; // Maximum width for text fields before truncation (default: 80)
}
Parameters:
item: Any DatoCMS item, including records, blocks, or items in create/update formatoptions: 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));
// Output:
// Item "MgCNaAI0RxSG8CA9sDXCHg" (item_type: "bJse85JFR0GbA37ey6kA1w")
// ├─ title: "My Blog Post"
// ├─ slug: "my-blog-post"
// └─ content:
// ├─ en: "This is the English content..."
// └─ it: "Questo è il contenuto italiano..."
Converts a block data object into the proper format for API requests.
TypeScript Signature:
function buildBlockRecord<D extends ItemTypeDefinition>(
body: ItemUpdateSchema<ToItemDefinitionInRequest<D>>
): NewBlockInRequest<ToItemDefinitionInRequest<D>>
Parameters:
body: Block data in update schema formatReturns: Formatted block record ready for API requests
Creates a deep copy of a block record, including all nested blocks, removing IDs to create new instances.
TypeScript Signature:
async function duplicateBlockRecord<D extends ItemTypeDefinition>(
existingBlock: ItemWithOptionalIdAndMeta<ToItemDefinitionInNestedResponse<D>>,
schemaRepository: SchemaRepository
): Promise<NewBlockInRequest<ToItemDefinitionInRequest<D>>>
Parameters:
existingBlock: The block to duplicateschemaRepository: Repository for schema lookupsReturns: New block record without IDs, ready to be created
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.
Visit every block in a non-localized field value recursively, including blocks nested within other blocks.
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 valuefieldType: The type of DatoCMS field (ie. string, rich_text, etc.)schemaRepository: Repository for caching schema lookupsvisitor: Function called for each block (including nested)Transform all blocks in a non-localized field value recursively, including nested blocks.
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 valuefieldType: The type of DatoCMS field (ie. string, rich_text, etc.)schemaRepository: Repository for caching schema lookupsmapper: Function that transforms each blockReturns: New field value
Filter blocks recursively, removing blocks at any nesting level that don't match the predicate.
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 filterfieldType: The type of DatoCMS field (ie. string, rich_text, etc.)schemaRepository: Repository for caching schema lookupspredicate: Function that tests each blockReturns: New field value with filtered blocks
Usage Example:
// Remove all video blocks at any nesting level
const noVideos = await filterBlocksInNonLocalizedFieldValue(
schemaRepository,
field,
fieldValue,
(block) => block.relationships.item_type.data.id !== 'video_block'
);
Find all blocks that match the predicate, searching recursively through nested blocks.
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 searchfieldType: The type of DatoCMS field (ie. string, rich_text, etc.)schemaRepository: Repository for caching schema lookupspredicate: Function that tests each blockReturns: Array of all matching blocks with their paths
Reduce all blocks recursively to a single value.
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 reducefieldType: The type of DatoCMS field (ie. string, rich_text, etc.)schemaRepository: Repository for caching schema lookupsreducer: Function that processes each blockinitialValue: Initial accumulator valueReturns: The final accumulated value
Check if any block (including nested) matches the predicate.
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 testfieldType: The type of DatoCMS field (ie. string, rich_text, etc.)schemaRepository: Repository for caching schema lookupspredicate: Function that tests each blockReturns: True if any block matches
Check if every block (including nested) matches the predicate.
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 testfieldType: The type of DatoCMS field (ie. string, rich_text, etc.)schemaRepository: Repository for caching schema lookupspredicate: Function that tests each blockReturns: True if all blocks match
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.
Apply a transformation function to field values, handling both localized and non-localized fields uniformly.
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 definitionmapFn: Function to transform each value (receives locale for localized fields, undefined for non-localized)Returns: Transformed value maintaining the same structure
Filter field values based on a predicate, handling both localized and non-localized fields.
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 filterfield: The DatoCMS field definitionfilterFn: Predicate function for filteringReturns: Filtered value or undefined if all filtered out
Visit each value in a field, handling both localized and non-localized fields.
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 visitfield: The DatoCMS field definitionvisitFn: Function called for each valueCheck if at least one field value passes the test.
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 testfield: The DatoCMS field definitiontestFn: Predicate functionReturns: True if any value passes the test
Check if all field values pass the test.
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 testfield: The DatoCMS field definitiontestFn: Predicate functionReturns: True if all values pass the test
Convert field values to/from a normalized entry format for uniform processing.
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/tofield: The DatoCMS field definitionReturns: Normalized entries array or reconstructed field value
Usage Example:
// Convert to entries for processing
const entries = toNormalizedFieldValueEntries(fieldValue, field);
// Process entries uniformly
const processed = entries.map(({ locale, value }) => ({
locale,
value: processValue(value)
}));
// Convert back to field value format
const result = fromNormalizedFieldValueEntries(processed, field);
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?
mapBlocksInNonLocalizedFieldValue.Usage Example:
const schemaRepository = new SchemaRepository(client);
// First call: fetches from API and caches result
const blogPost = await schemaRepository.getItemTypeByApiKey('blog_post');
const fields = await schemaRepository.getItemTypeFields(blogPost);
// Next calls: resolved instantly from cache (no API calls)
const sameBlogPost = await schemaRepository.getItemTypeByApiKey('blog_post');
const sameFields = await schemaRepository.getItemTypeFields(blogPost);
// Works seamlessly with block-processing utilities
await mapBlocksInNonLocalizedFieldValue(
fieldValue,
fieldType,
schemaRepository, // share cached lookups
async (block) => {
// transform block here
}
);
When to Use
When Not to Use
class SchemaRepository {
constructor(client: GenericClient)
// Item Type methods
async getAllItemTypes(): Promise<ItemType[]>
async getAllModels(): Promise<ItemType[]>
async getAllBlockModels(): Promise<ItemType[]>
async getItemTypeByApiKey(apiKey: string): Promise<ItemType>
async getItemTypeById(id: string): Promise<ItemType>
// Field methods
async getItemTypeFields(itemType: ItemType): Promise<Field[]>
async getItemTypeFieldsets(itemType: ItemType): Promise<Fieldset[]>
// Higher-level utilities
async getModelsEmbeddingBlocks(blocks: ItemType[]): Promise<ItemType[]>
async getNestedBlocks(itemTypes: ItemType[]): Promise<ItemType[]>
async getNestedModels(itemTypes: ItemType[]): Promise<ItemType[]>
// Plugin methods
async getAllPlugins(): Promise<Plugin[]>
async getPluginById(id: string): Promise<Plugin>
async getPluginByPackageName(packageName: string): Promise<Plugin>
// Raw variants (return API response format)
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>>
// ... and more raw variants
}
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.
The package is available as open source under the terms of the MIT License.
FAQs
JS client for DatoCMS REST Content Management API
The npm package @datocms/cma-client receives a total of 60,047 weekly downloads. As such, @datocms/cma-client popularity was classified as popular.
We found that @datocms/cma-client demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 7 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket for Jira lets teams turn alerts into Jira tickets with manual creation, automated ticketing rules, and two-way sync.

Company News
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.

Security News
NIST will stop enriching most CVEs under a new risk-based model, narrowing the NVD's scope as vulnerability submissions continue to surge.