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:
The package is written in TypeScript, so every method is fully typed and offers editor auto-completion and type checks;
Tedious tasks like API rate limits retry, asyncronous jobs management, pagination and creation of new assets are either automatically managed for you, or greatly simplified with simple methods that hide the inner complexities.
Depending on the environment you're working (browser or NodeJS), you can install one of these two packages:
npm install @datocms/cma-client-browsernpm install @datocms/cma-client-node
They both offer the same functionality. The only difference between the two, is in the methods available to upload files and create assets in your project, which are optimized for the different environment.
You can use the buildClient
function to initialize a new client.
import { buildClient } from '@datocms/cma-client-node';const client = buildClient({ apiToken: '<YOUR_TOKEN>' });
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: '<YOUR_TOKEN>',environment: 'my-sandbox-environment',});
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: '<YOUR_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).
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.
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 requestconsole.log(e.request.url);console.log(e.request.method);console.log(e.request.headers);console.log(e.request.body);// Information about the responseconsole.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' });
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 });