How to Use DatoCMS with Astro: Complete Guide
Step-by-step guide to integrating DatoCMS with your Astro website.
DatoCMS is a headless CMS built with an API-first approach. It stands out for its built-in image optimization pipeline and solid localization support. If you are building a content-driven Astro site and want a CMS that handles media intelligently without extra services, DatoCMS is worth a serious look.
The GraphQL API is well designed, the free tier is generous enough for personal projects, and the content modeling interface is one of the cleanest I have worked with. Let me walk you through the full setup.
Prerequisites
- Node.js 18+
- An Astro project (
npm create astro@latest) - A DatoCMS account (free tier includes 300 records and 10GB bandwidth)
Installation
Install the DatoCMS client library:
npm install datocms-client
For GraphQL queries (the recommended approach), you can also use a lightweight fetch wrapper. No additional package is strictly required since DatoCMS has a clean GraphQL endpoint.
Configuration
Grab your API token from DatoCMS under Settings > API tokens. The "Read-only API token" is what you need for the frontend.
Add it to your .env file:
DATOCMS_API_TOKEN=your_read_only_api_token
DATOCMS_ENVIRONMENT=main
Create a helper for GraphQL queries:
// src/lib/datocms.ts
const DATOCMS_API_TOKEN = import.meta.env.DATOCMS_API_TOKEN;
export async function datocmsRequest(query: string, variables?: Record<string, any>) {
const res = await fetch("https://graphql.datocms.com/", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${DATOCMS_API_TOKEN}`,
},
body: JSON.stringify({ query, variables }),
});
if (!res.ok) {
throw new Error(`DatoCMS error: ${res.status}`);
}
const json = await res.json();
if (json.errors) {
throw new Error(json.errors.map((e: any) => e.message).join(", "));
}
return json.data;
}
Basic Usage
With a "Blog Post" model in DatoCMS containing title, slug, content, coverImage, and publishDate fields, here is how to list posts:
---
// src/pages/blog/index.astro
import { datocmsRequest } from "../../lib/datocms";
import BaseLayout from "../../layouts/BaseLayout.astro";
const POSTS_QUERY = `
query {
allBlogPosts(orderBy: publishDate_DESC) {
id
title
slug
publishDate
coverImage {
responsiveImage(imgixParams: { w: 600, h: 400, fit: crop }) {
src
width
height
alt
}
}
}
}
`;
const data = await datocmsRequest(POSTS_QUERY);
const posts = data.allBlogPosts;
---
<BaseLayout title="Blog">
<h1>Blog</h1>
{posts.map((post: any) => (
<article>
<a href={`/blog/${post.slug}`}>
{post.coverImage?.responsiveImage && (
<img
src={post.coverImage.responsiveImage.src}
alt={post.coverImage.responsiveImage.alt}
width={post.coverImage.responsiveImage.width}
height={post.coverImage.responsiveImage.height}
loading="lazy"
/>
)}
<h2>{post.title}</h2>
</a>
</article>
))}
</BaseLayout>
For individual post pages:
---
// src/pages/blog/[slug].astro
import { datocmsRequest } from "../../lib/datocms";
import BaseLayout from "../../layouts/BaseLayout.astro";
export async function getStaticPaths() {
const data = await datocmsRequest(`
query { allBlogPosts { slug } }
`);
return data.allBlogPosts.map((post: any) => ({
params: { slug: post.slug },
}));
}
const { slug } = Astro.params;
const data = await datocmsRequest(`
query PostBySlug($slug: String!) {
blogPost(filter: { slug: { eq: $slug } }) {
title
content { value }
publishDate
coverImage {
responsiveImage(imgixParams: { w: 1200, h: 630, fit: crop }) {
src
alt
}
}
}
}
`, { slug });
const post = data.blogPost;
---
<BaseLayout title={post.title}>
<article>
<h1>{post.title}</h1>
<div set:html={post.content.value} />
</article>
</BaseLayout>
Production Tips
Use the Responsive Image API. DatoCMS runs images through imgix automatically. You get on-the-fly resizing, format conversion, and quality optimization through URL parameters. No need for Cloudinary or a separate image CDN.
Leverage Structured Text. DatoCMS's Structured Text field type gives you a portable JSON format instead of raw HTML. Use datocms-structured-text-to-html-string to render it, or build custom renderers for full control.
Set up build webhooks. In DatoCMS under Settings > Webhooks, configure a trigger for your hosting platform. This ensures content updates go live without manual builds.
Use preview mode for drafts. DatoCMS has a drafts system. Pass includeDrafts: true in your headers during development so editors can preview unpublished content before it goes live.
Batch your GraphQL queries. Instead of making separate requests for posts, authors, and categories, combine them into a single query. DatoCMS handles nested data well through its GraphQL API.
Alternatives to Consider
- Contentful if you need a larger ecosystem with more third-party integrations and community resources.
- Storyblok if your content team needs a visual drag-and-drop editor rather than a form-based interface.
- Sanity if you want more flexibility in schema design and real-time collaborative editing.
Wrapping Up
DatoCMS pairs naturally with Astro. The GraphQL API keeps your data fetching clean, the image pipeline eliminates the need for a separate optimization service, and the localization features are built right in. For small to medium content sites, the free tier covers more than enough, and the developer experience stays consistent as you scale up.
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.