JSPM

  • Created
  • Published
  • Downloads 50782
  • Score
    100M100P100Q162676F
  • License MIT

JS client for DatoCMS REST Content Management API

Package Exports

  • @datocms/cma-client
  • @datocms/cma-client/dist/cjs/index.js
  • @datocms/cma-client/dist/esm/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@datocms/cma-client) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

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';

// 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;

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,
  // 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
}

"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,
  // 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:

View example
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
}

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; // Maximum width for text fields before truncation (default: 80)
}

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));

// 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..."

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

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:

// Remove all video blocks at any nesting level
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:

// 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);

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);

// 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

  • 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)

  // 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
}

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.