Overview
This integration guide shows you how to how to update your existing project to:- install the ButterCMS package
- instantiate ButterCMS
- create components to fetch and display each of the three ButterCMS content types: Pages, Collections, and Blog Posts.
In order for the snippets to work, you’ll need to setup your dashboard content schemas inside of ButterCMS first.
Starter project
Or, you can jump directly to the starter project below, which will allow you to clone, install, run, and deploy a fully working starter project that’s integrated with content already inside of your ButterCMS account.Astro Starter Project
Hit the ground running with a pre-configured Astro + ButterCMS setup.
Installation
- npm
- yarn
- pnpm
npm install buttercms
yarn add buttercms
pnpm add buttercms
.env:
BUTTER_CMS_API_KEY=your_api_token
Initialize the client
Create a reusable client instance:// src/lib/buttercms.ts
import Butter from 'buttercms';
const butter = Butter(import.meta.env.BUTTER_CMS_API_KEY);
export default butter;
For complete SDK documentation including all available methods and configuration options, see the JavaScript SDK Reference.
Pages
- Static (SSG)
- Server (SSR)
---
// src/pages/[slug].astro
import butter from '../lib/buttercms';
interface PageFields {
headline: string;
subheadline: string;
hero_image: string;
body: string;
}
export async function getStaticPaths() {
const response = await butter.page.list<PageFields>('landing_page');
if (!response.data?.data) {
throw new Error('Failed to load landing pages from ButterCMS');
}
return response.data.data.map((page) => ({
params: { slug: page.slug },
props: { page },
}));
}
interface Props {
page: {
fields: PageFields;
};
}
const { page } = Astro.props;
---
<main>
<h1>{page.fields.headline}</h1>
<p>{page.fields.subheadline}</p>
{page.fields.hero_image && (
<img src={page.fields.hero_image} alt={page.fields.headline} />
)}
<div set:html={page.fields.body ?? ''} />
</main>
Enable SSR in
astro.config.mjs:// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
output: 'server',
});
---
// src/pages/[slug].astro
import butter from '../lib/buttercms';
interface PageFields {
headline: string;
subheadline: string;
hero_image: string;
body: string;
}
const { slug } = Astro.params;
let page: { fields: PageFields };
try {
const response = await butter.page.retrieve<PageFields>('landing_page', slug);
if (!response.data?.data) {
return Astro.redirect('/404');
}
page = response.data.data;
} catch {
return Astro.redirect('/404');
}
---
<main>
<h1>{page.fields.headline}</h1>
<p>{page.fields.subheadline}</p>
{page.fields.hero_image && (
<img src={page.fields.hero_image} alt={page.fields.headline} />
)}
<div set:html={page.fields.body ?? ''} />
</main>
Collections
---
// src/pages/brands.astro
import butter from '../lib/buttercms';
interface Brand {
name: string;
logo: string;
description: string;
}
const response = await butter.content.retrieve(['brands']);
if (!response.data?.data) {
throw new Error('Failed to load brands from ButterCMS');
}
const brands: Brand[] = response.data.data.brands;
---
<main>
<h1>Our Brands</h1>
<ul>
{brands.map((brand) => (
<li>
<img src={brand.logo} alt={brand.name} />
<h2>{brand.name}</h2>
<div set:html={brand.description} />
</li>
))}
</ul>
</main>
Dynamic components
Component Renderer
---
// src/components/ComponentRenderer.astro
import Hero from './Hero.astro';
import Features from './Features.astro';
import Testimonials from './Testimonials.astro';
import CTA from './CTA.astro';
export interface HeroFields {
headline: string;
subheadline: string;
image: string;
button_label: string;
button_url: string;
}
export interface FeaturesFields {
headline: string;
items: { icon?: string; title: string; description: string }[];
}
export interface TestimonialsFields {
headline: string;
items: { quote: string; author_name: string; author_title?: string }[];
}
export interface CtaFields {
headline: string;
subheadline: string;
button_label: string;
button_url: string;
}
export type ButterComponent =
| { type: 'hero'; fields: HeroFields }
| { type: 'features'; fields: FeaturesFields }
| { type: 'testimonials'; fields: TestimonialsFields }
| { type: 'cta'; fields: CtaFields };
const componentMap = {
hero: Hero,
features: Features,
testimonials: Testimonials,
cta: CTA,
};
interface Props {
components: ButterComponent[];
}
const { components } = Astro.props;
---
{components.map((component) => {
const Component = componentMap[component.type];
return Component ? <Component {...component.fields} /> : null;
})}
Example Component
---
// src/components/Hero.astro
interface Props {
headline: string;
subheadline: string;
image: string;
button_label: string;
button_url: string;
}
const { headline, subheadline, image, button_label, button_url } = Astro.props;
---
<section class="hero">
<h1>{headline}</h1>
<p>{subheadline}</p>
{button_label && <a href={button_url}>{button_label}</a>}
{image && <img src={image} alt={headline} />}
</section>
Using in Pages
This uses a distinct page type (
component_page) whose body is a Component Picker (Page Builder) field — an array of components — separate from the WYSIWYG landing_page in the Pages section (whose body is a string). A page’s body is one field type or the other, so the Page Builder example needs its own page type.---
// src/pages/landing/[slug].astro
import butter from '../../lib/buttercms';
import ComponentRenderer from '../../components/ComponentRenderer.astro';
import type { ButterComponent } from '../../components/ComponentRenderer.astro';
interface PageFields {
body: ButterComponent[];
}
export async function getStaticPaths() {
const response = await butter.page.list<PageFields>('component_page');
if (!response.data?.data) {
throw new Error('Failed to load landing pages from ButterCMS');
}
return response.data.data.map((page) => ({
params: { slug: page.slug },
props: { page },
}));
}
interface Props {
page: {
fields: PageFields;
};
}
const { page } = Astro.props;
---
<main>
<ComponentRenderer components={page.fields.body} />
</main>
Blog
- Blog Post List
- Single Blog Post
---
// src/pages/blog/index.astro
import butter from '../../lib/buttercms';
interface Post {
slug: string;
title: string;
summary: string;
author: { first_name: string; last_name: string };
}
const response = await butter.post.list({ page: 1, page_size: 10 });
if (!response.data?.data) {
throw new Error('Failed to load blog posts from ButterCMS');
}
const posts: Post[] = response.data.data;
const meta = response.data.meta;
---
<main>
<h1>Blog</h1>
<ul>
{posts.map((post) => (
<li>
<h2><a href={`/blog/${post.slug}`}>{post.title}</a></h2>
<p set:html={post.summary ?? ''} />
<span>By {post.author.first_name} {post.author.last_name}</span>
</li>
))}
</ul>
{meta?.next_page && (
<a href={`/blog/page/${meta.next_page}`}>Next Page</a>
)}
</main>
---
// src/pages/blog/[slug].astro
import butter from '../../lib/buttercms';
export async function getStaticPaths() {
const response = await butter.post.list({ page_size: 100 });
if (!response.data?.data) {
throw new Error('Failed to load blog posts from ButterCMS');
}
return response.data.data.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
interface Props {
post: {
title: string;
body?: string;
published: string | null;
featured_image: string | null;
author: { first_name: string; last_name: string };
};
}
const { post } = Astro.props;
---
<article>
<h1>{post.title}</h1>
<p>
By {post.author.first_name} {post.author.last_name}
{post.published && (
<span> on {new Date(post.published).toLocaleDateString()}</span>
)}
</p>
{post.featured_image && <img src={post.featured_image} alt={post.title} />}
<div set:html={post.body ?? ''} />
<a href="/blog">Back to Posts</a>
</article>
Preview Mode
Enable draft content preview with SSR:---
// src/pages/preview/[...slug].astro
import Butter from 'buttercms';
const butter = Butter(import.meta.env.BUTTER_CMS_API_KEY, true); // Enable preview mode
interface PageFields {
headline: string;
body: string;
}
const { slug } = Astro.params;
if (!slug) {
return Astro.redirect('/404');
}
const [pageType, pageSlug] = slug.split('/');
let page: { fields: PageFields };
try {
const response = await butter.page.retrieve<PageFields>(pageType, pageSlug);
if (!response.data?.data) {
return Astro.redirect('/404');
}
page = response.data.data;
} catch {
return Astro.redirect('/404');
}
---
<div class="preview-banner">Preview Mode</div>
<main>
<h1>{page.fields.headline}</h1>
<div set:html={page.fields.body ?? ''} />
</main>
SEO
---
// src/pages/[slug].astro
import butter from '../lib/buttercms';
import Layout from '../layouts/Layout.astro';
interface SeoFields {
title?: string;
description?: string;
og_title?: string;
og_description?: string;
og_image?: string;
}
interface PageFields {
headline: string;
body: string;
seo?: SeoFields;
}
export async function getStaticPaths() {
const response = await butter.page.list<PageFields>('landing_page');
if (!response.data?.data) {
throw new Error('Failed to load landing pages from ButterCMS');
}
return response.data.data.map((page) => ({
params: { slug: page.slug },
props: { page },
}));
}
interface Props {
page: {
fields: PageFields;
};
}
const { page } = Astro.props;
const seo: SeoFields = page.fields.seo ?? {};
---
<Layout
title={seo.title || page.fields.headline}
description={seo.description}
ogTitle={seo.og_title || seo.title}
ogDescription={seo.og_description || seo.description}
ogImage={seo.og_image}
>
<main>
<h1>{page.fields.headline}</h1>
<div set:html={page.fields.body ?? ''} />
</main>
</Layout>
---
// src/layouts/Layout.astro
interface Props {
title: string;
description?: string;
ogTitle?: string;
ogDescription?: string;
ogImage?: string;
}
const { title, description, ogTitle, ogDescription, ogImage } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
{description && <meta name="description" content={description} />}
{ogTitle && <meta property="og:title" content={ogTitle} />}
{ogDescription && <meta property="og:description" content={ogDescription} />}
{ogImage && <meta property="og:image" content={ogImage} />}
</head>
<body>
<slot />
</body>
</html>
Resources
Astro Starter
Pre-configured starter project
JavaScript SDK
Complete SDK reference
GitHub Repository
View source code
Content API
REST API documentation