Content Delivery API

Using the JavaScript CDA client

The DatoCMS Content Delivery API is a single GraphQL endpoint:

https://graphql.datocms.com/

The endpoint stays constant no matter what operation you perform, and it's read-only — that is, it does not offer any mutation operation. You can use our Content Management API for that.

Unlike REST, the HTTP verb does not vary by operation: every request — whether you’re querying or fetching with variables — is a POST to that URL, with a JSON-encoded body.

Making a raw HTTP request

You can talk to the API from anything that speaks HTTP. Here's the same query sent with curl and with the native Fetch API:

Terminal window
$ curl 'https://graphql.datocms.com/' \
-H 'Authorization: Bearer YOUR-API-TOKEN' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
--data-binary '{ "query": "{ allPosts { title } }" }'
const response = await fetch('https://graphql.datocms.com/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${process.env.DATOCMS_READONLY_TOKEN}`,
},
body: JSON.stringify({
query: '{ allPosts { title } }',
}),
});
const { data } = await response.json();
console.log(data);

This works, but you'll quickly need to layer extra concerns on top: rate-limit retries, draft/preview mode, environment targeting, cache tags, pagination beyond the 500-record limit, and TypeScript types. That's exactly what our official client takes care of.

Using the @datocms/cda-client package

@datocms/cda-client is a lightweight, TypeScript-first package that wraps the native Fetch API with helpers tailored for the Content Delivery API. It's the recommended way to query DatoCMS from any JavaScript or TypeScript runtime — Node.js, browsers, edge runtimes, Deno, Bun.

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

  • The package is written in TypeScript, with full support for TypedDocumentNode so that — paired with gql.tada or GraphQL Code Generator — you get end-to-end type inference on every query result and variable;

  • All the API headers that control environments, draft mode, strict mode, cache tags and Content Link are exposed as plain options on the client, no manual header juggling required;

  • API rate-limit retries are managed for you automatically, and a dedicated helper bypasses the API's 500-record per-query limit transparently.

Installation
Terminal window
npm install @datocms/cda-client

The package has zero runtime dependencies and works in any environment that ships a native Fetch API (Node.js ≥ 18, modern browsers, Cloudflare Workers, Vercel Edge Functions, Deno, Bun, etc.).

Executing a query

The main entry point is executeQuery. It accepts a GraphQL query (as a string, DocumentNode, or TypedDocumentNode) and an options object, and returns a Promise that resolves with the query result:

import { executeQuery } from '@datocms/cda-client';
const result = await executeQuery('{ allPosts { title } }', {
token: process.env.DATOCMS_READONLY_TOKEN,
});
console.log(result);

You can pass GraphQL variables under the variables option:

const result = await executeQuery(
`query PostsByCategory($category: String!) {
allPosts(filter: { category: { eq: $category } }) {
title
}
}`,
{
token: process.env.DATOCMS_READONLY_TOKEN,
variables: { category: 'news' },
},
);

TypeScript and end-to-end type safety

executeQuery supports TypedDocumentNode, so the type of the response is inferred directly from your query. Combined with a code generator, you get autocomplete and compile-time checks on every field:

import { executeQuery } from '@datocms/cda-client';
import { AllArticlesQuery } from './generated/graphql';
const result = await executeQuery(AllArticlesQuery, {
token: process.env.DATOCMS_READONLY_TOKEN,
variables: { limit: 10 },
});
// `result.allArticles` is fully typed
result.allArticles.forEach((article) => console.log(article.title));

Error management

If a request fails — either because of a non-2xx HTTP status, or because the GraphQL response contains an errors array — the client throws an ApiError exception containing all the details of the request and the response:

import { executeQuery, ApiError } from '@datocms/cda-client';
try {
const result = await executeQuery('{ allPosts { title } }', {
token: process.env.DATOCMS_READONLY_TOKEN,
});
console.log(result);
} catch (e) {
if (e instanceof ApiError) {
// Information about the failed request
console.log(e.query);
console.log(e.options);
// 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;
}
}

You can learn more about the package and explore its full API surface — including rawExecuteQuery, buildRequestHeaders and buildRequestInit for framework integrations — on the @datocms/cda-client README.

Last updated: