Content Management API

Using the JavaScript client

If you're familiar with Javascript/TypeScript, you can make use of our official client to perform requests to the Content Management API.

It offers a number of benefits over making raw requests yourself:

How to install the client

DatoCMS provides three JavaScript client packages, each optimized for different runtime environments:

Terminal window
npm install @datocms/cma-client # Generic/agnostic (recommended for most cases)
npm install @datocms/cma-client-node # Node.js with filesystem access
npm install @datocms/cma-client-browser # Browser-optimized

@datocms/cma-client (generic/agnostic) — The safest and most portable choice. Use it for:

  • Edge functions (Cloudflare Workers, Vercel Edge Functions, etc.)

  • Serverless functions (AWS Lambda, Netlify Functions, etc.)

  • Any environment where maximum compatibility is needed

@datocms/cma-client-node — Use this if you're in a Node.js environment to get the best experience. It provides specialized helper methods for uploading files: createFromLocalFile() for local filesystem files and createFromUrl() for remote URLs. This gives you the most convenient API when working with Node.js and filesystem access.

@datocms/cma-client-browser — Use this if you're in a browser environment to get the best experience. It provides the createFromFileOrBlob() helper method for handling File and Blob objects from <input type="file" /> elements, giving you the most convenient API when working with user file uploads.

If you don't need these specialized upload helpers, the generic @datocms/cma-client package is the best choice — it works in more environments and avoids potential compatibility issues.

Initializing the client

You can use the buildClient function to initialize a new client.

import { buildClient } from '@datocms/cma-client-node';
const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
Specifying a sandbox environment

By default, every API request you perform will point to the current primary environment, but if you want to make changes to a specific sandbox environment, you can pass it in the initialization options:

import { buildClient } from '@datocms/cma-client-node';
const client = buildClient({
apiToken: process.env.DATOCMS_API_TOKEN,
environment: 'my-sandbox-environment',
});
Logging request/responses

The client can output logs of the API request and responses it performs to help you debug issues with your code. You can choose different level of logging, depending on how much information you need:

import { buildClient, LogLevel } from '@datocms/cma-client-node';
const client = buildClient({
apiToken: process.env.DATOCMS_API_TOKEN,
logLevel: LogLevel.BASIC,
});

The different levels of logging available are:

  • LogLevel.NONE (the default level): No output is generated;

  • LogLevel.BASIC: Logs HTTP requests (method, URL) and responses (status);

  • LogLevel.BODY: Logs HTTP requests (method, URL, body) and responses (status, body);

  • LogLevel.BODY_AND_HEADERS: Logs HTTP requests (method, URL, headers, body) and responses (status, headers, body).

Entity collections and pagination

Take a look at the Pagination section to understand how pagination works, and which methods the JavaScript client provides to make your task easier.

Raw vs Simplified endpoint methods

The client is organized by type of resource. For every resource, it offers a number of async methods to perform a CRUD request to a specific endpoint of the API:

// Example: Item Type (Model)
await client.itemType.rawList(...);
await client.itemType.rawFind(...);
await client.itemType.rawCreate(...);
await client.itemType.rawUpdate(...);
await client.itemType.rawDestroy(...);

"Why the raw prefix on all the methods?" you might ask. Well, let's take a closer look at one specific method call — in this case, the update of an existing model.

As already covered in previous sections, the API follows the JSON:API convention, which requires a specific format for the payloads. Every request/response has a data attribute, which contains a number of Resource Objects, which in turn contain different of top-level members (id, type, attributes, relationships, meta, etc), each with their own semantic:

const response = await client.itemTypes.rawUpdate('34532432', {
data: {
id: '34532432',
type: 'item_type',
attributes: {
name: 'Article',
api_key: 'article',
},
relationships: {
title_field: { data: { id: '451235', type: 'field' } },
},
}
});
console.log(`Created model ${response.data.attributes.name}!`);

As you can see from the example above, it can become very verbose to write even simple code using this format! That's why the client also offers a "simplified" method for every endpoint — without the raw prefix — which greatly reduces the amount of boilerplate code required:

const itemType = await client.itemTypes.update('34532432', {
name: 'Article',
api_key: 'article',
title_field: { id: '451235', type: 'field' },
});
console.log(`Created model ${itemType.name}!`);

So the complete set of methods available for the Model resource is:

// Example: Item Type (Model)
await client.itemType.list(...);
await client.itemType.rawList(...);
await client.itemType.find(...);
await client.itemType.rawFind(...);
await client.itemType.create(...);
await client.itemType.rawCreate(...);
await client.itemType.update(...);
await client.itemType.rawUpdate(...);
await client.itemType.destroy(...);
await client.itemType.rawDestroy(...);

In the next sections, you'll find a real-world usage example of the client for every endpoint offered by the API.

Error management

In case an API call fails with HTTP status code outside of the 2xx range, an ApiError exception will be raised by the client, containing all the details of the request/response.

import { ApiError } from '@datocms/cma-client-node';
try {
await client.itemType.create({
name: 'Article',
api_key: 'article',
});
} catch(e) {
if (e instanceof ApiError) {
// Information about the failed request
console.log(e.request.url);
console.log(e.request.method);
console.log(e.request.headers);
console.log(e.request.body);
// Information about the response
console.log(e.response.status);
console.log(e.response.statusText);
console.log(e.response.headers);
console.log(e.response.body);
} else {
throw e;
}
}

The error object also includes a .findError() method that you can use to check if the response includes a particular error code:

// finds in the array of api_error entities an error with code 'INVALID_FIELD',
// that in its details has the key 'field' set to 'api_key':
const errorEntity = e.findError('INVALID_FIELD', { field: 'api_key' });

SchemaRepository utility for efficient schema access

When working with complex operations that require frequent access to schema information (models, fields, fieldsets, and plugins), the SchemaRepository utility provides an efficient caching layer to avoid redundant API calls.

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[]>
// Plugin methods
async getAllPlugins(): Promise<Plugin[]>
async getPluginById(id: string): Promise<Plugin>
async getPluginByPackageName(packageName: string): Promise<Plugin>
// Raw variants (return full JSON:API response format)
async getAllRawItemTypes(): Promise<RawItemType[]>
async getRawItemTypeByApiKey(apiKey: string): Promise<RawItemType>
// ... and more raw variants
}
Purpose

SchemaRepository is designed to solve performance problems when repeatedly fetching the same schema information during operations that traverse nested blocks, structured text, or modular content. It acts as an in-memory cache for schema entities.

Without SchemaRepository, a script processing fields containing nested blocks might make the same client.itemTypes.list() or client.fields.list() calls dozens of times: SchemaRepository ensures each unique schema request is made only once.

import { SchemaRepository, mapBlocksInNonLocalizedFieldValue } from '@datocms/cma-client';
const schemaRepository = new SchemaRepository(client);
// These calls will hit the API and cache the results
const models = await schemaRepository.getAllModels();
const blogPost = await schemaRepository.getItemTypeByApiKey('blog_post');
// These subsequent calls will return cached results (no API calls)
const sameModels = await schemaRepository.getAllModels();
const sameBlogPost = await schemaRepository.getItemTypeByApiKey('blog_post');
// Pass the repository to utilities that need schema information
await mapBlocksInNonLocalizedFieldValue(record.content, 'rich_text', schemaRepository, (block, path) => {
// The utility will use the cached schema data internally
});
What's it for
  • Caching schema entities: Automatically caches item types, fields, fieldsets, and plugins after the first API request

  • Complex traversal operations: Essential when using utilities like mapBlocksInNonLocalizedFieldValue() that need to repeatedly lookup block models and fields

  • Bulk operations: Ideal for scripts that process multiple records of different types

  • Read-heavy workflows: Perfect for scenarios where you need to repeatedly access the same schema information

When NOT to use it
  • Schema modification: Do NOT use if your script modifies models, fields, fieldsets, or plugins, as the cache will become stale!

  • Long-running applications: The cache has no expiration mechanism!

  • Concurrent schema changes: No protection against cache inconsistency!

Pro tip: Best practices

Create one instance per script execution, not per operation, and make sure to use SchemaRepository consistently throughout your script for maximum cache efficiency!

Ponyfilling fetch()

If your Javascript environment does not provide the Fetch API interface — for example, if you are using a version of Node lower than 18 — you will need to specify a ponyfill during client configuration:

import { buildClient } from '@datocms/cma-client-node';
import { fetch } from '@whatwg-node/fetch';
const client = buildClient({ apiToken: '<YOUR_TOKEN>', fetchFn: fetch });