How to Use ButterCMS with Astro: Complete Guide
Step-by-step guide to integrating ButterCMS with your Astro website.
ButterCMS is an API-first content management system designed to be developer-friendly while keeping things simple. It comes with a built-in blog engine, pages, and reusable components, so you do not have to model everything from scratch. If you have ever set up a headless CMS and spent more time configuring content types than actually building features, ButterCMS aims to skip that phase entirely.
For Astro developers, ButterCMS provides a clean REST API with an isomorphic JavaScript SDK that ships built-in TypeScript definitions. You fetch content at build time, generate static pages, and get the performance benefits of Astro with the content flexibility of a hosted CMS.
This guide was checked against the official docs on 2026-05-29. Astro is now on v6 (6.4.2 at the time of writing), and the ButterCMS SDK is on v3 (3.0.3). The two facts that trip people up most are version-related, so they are worth stating up front. Astro 6 requires Node.js 22.12.0 or newer, and the ButterCMS SDK requires Node.js 20 or greater for server-side use. Astro also defaults to fully static output, which is exactly what you want for a build-time CMS like Butter, so no adapter is needed for a standard blog.
Prerequisites
- Node.js 22.12.0 or newer (required by Astro 6; the ButterCMS SDK itself needs Node 20+)
- An Astro project (
npm create astro@latest) - A ButterCMS account (free tier available, plus a 14-day trial on paid plans; the cheapest paid plan is Basic at $71/mo)
Installation
Install the official ButterCMS JavaScript client (v3.0.3 as of 2026-05-29):
npm install buttercms
The package name buttercms is current and the SDK is published as ESM-first with a CommonJS fallback, so it imports cleanly inside Astro's Vite-based build.
Configuration
Create a ButterCMS client helper. This matches the pattern in the official Astro CMS guide, which uses the env var name BUTTER_TOKEN:
// src/lib/buttercms.ts
import Butter from "buttercms";
export const butter = Butter(import.meta.env.BUTTER_TOKEN);
export default butter;
Add your API token to .env:
BUTTER_TOKEN=your_read_api_token_here
You can find your Read API token in the ButterCMS dashboard under Settings > API Token. Because you fetch at build time inside Astro's static output, the token never ships to the browser, so it stays server-side by default. Astro only exposes env vars to client code when they are prefixed with PUBLIC_, so naming it BUTTER_TOKEN (no PUBLIC_ prefix) keeps it out of the client bundle. Even so, treat the Read token as low-sensitivity rather than secret, since the ButterCMS Read API is meant for read-only content delivery.
ButterCMS comes with a pre-built blog engine. Once you sign up, you already have blog post, category, tag, and author models ready to use. No custom type configuration needed for a standard blog.
Basic Usage
Fetch blog posts and render them on your Astro site:
---
// src/pages/blog/index.astro
import butter from "../../lib/buttercms";
import BaseLayout from "../../layouts/BaseLayout.astro";
const response = await butter.post.list({
page: 1,
page_size: 50,
});
const posts = response.data.data;
---
<BaseLayout title="Blog">
<h1>Blog</h1>
{posts.map((post) => (
<article>
<a href={`/blog/${post.slug}`}>
{post.featured_image && (
<img src={post.featured_image} alt={post.title} loading="lazy" />
)}
<h2>{post.title}</h2>
<p>{post.summary}</p>
<time>{new Date(post.published).toLocaleDateString()}</time>
</a>
</article>
))}
</BaseLayout>
Individual post pages with dynamic routes:
---
// src/pages/blog/[slug].astro
import butter from "../../lib/buttercms";
import BaseLayout from "../../layouts/BaseLayout.astro";
export async function getStaticPaths() {
const response = await butter.post.list({ page: 1, page_size: 100 });
const posts = response.data.data;
return posts.map((post) => ({
params: { slug: post.slug },
}));
}
const { slug } = Astro.params;
const response = await butter.post.retrieve(slug);
const post = response.data.data;
---
<BaseLayout title={post.title}>
<article>
<h1>{post.title}</h1>
<div class="meta">
<span>By {post.author.first_name} {post.author.last_name}</span>
<time>{new Date(post.published).toLocaleDateString()}</time>
</div>
{post.featured_image && (
<img src={post.featured_image} alt={post.title} />
)}
<div class="content" set:html={post.body} />
<div class="tags">
{post.tags.map((tag) => (
<a href={`/tag/${tag.slug}`}>{tag.name}</a>
))}
</div>
</article>
</BaseLayout>
ButterCMS also supports custom page types for things like landing pages. The SDK signatures are butter.page.list(pageType, options) and butter.page.retrieve(pageType, pageSlug, options), so the page type comes first and the slug second:
// Fetch all pages of a type: butter.page.list(pageType, options)
const response = await butter.page.list("landing_page", {
page: 1,
page_size: 10,
});
// Fetch a specific page: butter.page.retrieve(pageType, pageSlug, options)
const page = await butter.page.retrieve("landing_page", "homepage");
const fields = page.data.data.fields;
A note on rendering mode
Everything above runs at build time and produces static HTML, which is Astro's default output: 'static' mode. You do not need an adapter for a build-time CMS blog. If you later want a route that fetches from Butter on each request (for example a live preview page), opt that single route into on-demand rendering with export const prerender = false and install the adapter for your host. The current official adapter packages are @astrojs/netlify (7.0.11), @astrojs/vercel (10.0.8), @astrojs/cloudflare (13.6.0), and @astrojs/node (10.1.2). Note that output: 'hybrid' was removed in Astro 5, so do not reach for it. Static is the default and per-route prerender = false is how you mix in on-demand rendering.
Production Tips
Use ButterCMS categories for content organization. Categories are built in and support nesting. Build category archive pages in Astro to improve SEO with topical content hubs that search engines value.
Set up webhooks for automatic deploys. ButterCMS supports webhooks that fire when content changes. Point these at your Vercel, Netlify, or Cloudflare Pages build hook URL so your site rebuilds whenever editors publish updates.
Paginate large collections. The API returns paginated results. For sites with many posts, loop through pages during
getStaticPathsto fetch all content. Usepage_size: 100to minimize API calls.Leverage the components API. ButterCMS has a "Components" feature for reusable content blocks like hero sections, CTAs, and testimonials. Define components in the dashboard, then map them to Astro components for a page-builder experience.
Cache API responses in CI. If your build pipeline runs frequently, cache ButterCMS responses to avoid hitting rate limits. Store the JSON responses locally during builds and only re-fetch when content actually changes.
Alternatives to Consider
- Contentful if you need more advanced content modeling, localization, and a larger ecosystem of third-party integrations.
- Ghost if you want a free self-hosted option with a similar built-in blog engine and a polished writing editor.
- DatoCMS if you want a similar developer experience with built-in image optimization and better GraphQL support.
Common Errors and Fixes
Node version error on npm create astro or npm run build. Astro 6 declares engines.node >= 22.12.0. On an older runtime the install warns or the build fails outright. Run node -v, and if you are below 22.12.0, upgrade Node before anything else. The ButterCMS SDK separately requires Node 20+, so 22.12.0 satisfies both.
Reaching for output: 'hybrid'. Older tutorials tell you to set output: 'hybrid' to mix static and dynamic routes. That value was removed in Astro 5 and does not exist in Astro 6. Static is now the default, and you opt individual routes into on-demand rendering with export const prerender = false. If you copied a config with output: 'hybrid', delete that line.
Token is undefined at build time. import.meta.env.BUTTER_TOKEN returns undefined if the variable is not in .env, if the file is not at the project root, or if the dev server was started before the var was added. Restart the dev server after editing .env. Confirm the name matches exactly (BUTTER_TOKEN). Do not prefix it with PUBLIC_ unless you intend to expose it to the browser, because Astro only ships PUBLIC_-prefixed env vars to client code.
Empty page on a dynamic route. A [slug].astro page in Astro's default static mode must export getStaticPaths(). If you forget it, the route is never generated and you get a 404. Make sure getStaticPaths returns the array of { params: { slug } } objects, and that the slugs match the post.slug values returned by butter.post.list. If you switch that route to export const prerender = false, then drop getStaticPaths and read Astro.params directly instead.
Only the first page of posts shows up. The ButterCMS list endpoints are paginated. butter.post.list defaults to a small page size, so a blog with more than page_size posts silently drops the rest. Pass page_size explicitly and loop through pages inside getStaticPaths until you have fetched everything.
set:html not rendering rich text. ButterCMS returns post bodies as HTML strings. In Astro you must use the set:html directive (as shown above) rather than interpolating the string, which would escape the markup. Sanitize untrusted HTML if your editors are not fully trusted.
Official Docs and Examples
- ButterCMS JavaScript API client guide (install, client init, method signatures): https://buttercms.com/docs/api-client/javascript/
- ButterCMS official SDK source (buttercms-js on GitHub): https://github.com/ButterCMS/buttercms-js
- ButterCMS package on npm (v3.0.3): https://www.npmjs.com/package/buttercms
- Astro official ButterCMS integration guide (uses the
BUTTER_TOKENenv var): https://docs.astro.build/en/guides/cms/buttercms/ - Official Astro + ButterCMS starter project: https://buttercms.com/docs/frameworks/starter-projects/astro
- Astro on-demand rendering and adapters reference: https://docs.astro.build/en/guides/on-demand-rendering/
Wrapping Up
ButterCMS removes the content modeling overhead that comes with most headless CMS platforms. The pre-built blog engine, pages, and components system mean you spend less time in the CMS dashboard and more time building your Astro site. The API is clean, the SDK is simple, and the free tier lets you experiment without commitment. For teams that want a headless CMS that works out of the box without extensive configuration, ButterCMS delivers.
Sources
All versions and facts below were checked on 2026-05-29.
- ButterCMS npm package, version 3.0.3 and current package name: https://registry.npmjs.org/buttercms/latest
- ButterCMS JavaScript API client guide (install command,
Butter(token)init,post.list,post.retrieve,page.list,page.retrievesignatures, Node 20+ requirement, isomorphic SDK with TypeScript types): https://buttercms.com/docs/api-client/javascript/ - ButterCMS official SDK repository: https://github.com/ButterCMS/buttercms-js
- ButterCMS package on npm: https://www.npmjs.com/package/buttercms
- ButterCMS pricing (free tier, 14-day trial on paid plans, Basic plan from $71/mo): https://buttercms.com/pricing/
- Astro npm package, version 6.4.2 and
engines.node >= 22.12.0: https://registry.npmjs.org/astro/latest - Astro on-demand rendering guide (default
output: 'static', per-routeexport const prerender = false, adapter package names): https://docs.astro.build/en/guides/on-demand-rendering/ - Astro official ButterCMS integration guide (
BUTTER_TOKENenv var, client helper pattern): https://docs.astro.build/en/guides/cms/buttercms/ - Official Astro + ButterCMS starter project: https://buttercms.com/docs/frameworks/starter-projects/astro
- @astrojs/netlify version 7.0.11: https://registry.npmjs.org/@astrojs/netlify/latest
- @astrojs/vercel version 10.0.8: https://registry.npmjs.org/@astrojs/vercel/latest
- @astrojs/cloudflare version 13.6.0: https://registry.npmjs.org/@astrojs/cloudflare/latest
- @astrojs/node version 10.1.2: https://registry.npmjs.org/@astrojs/node/latest
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.