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:
$ 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
TypedDocumentNodeso 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
npm install @datocms/cda-clientThe 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 typedresult.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.