Skip to main content

Overview

This integration guide shows you how to how to update your existing project to:
  1. install the ButterCMS package
  2. instantiate ButterCMS
  3. 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 install buttercms
Add your API token to .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

---
// src/pages/[slug].astro
import butter from '../lib/buttercms';

export async function getStaticPaths() {
  const response = await butter.page.list('landing-page');
  return response.data.data.map((page) => ({
    params: { slug: page.slug },
    props: { page },
  }));
}

interface Props {
  page: {
    fields: {
      headline: string;
      subheadline: string;
      hero_image: string;
      body: string;
    };
  };
}

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>

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']);
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';

interface Props {
  components: Array<{
    type: string;
    fields: Record<string, any>;
  }>;
}

const { components } = Astro.props;

const componentMap = {
  hero: Hero,
  features: Features,
  testimonials: Testimonials,
  cta: CTA,
};
---

{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

---
// src/pages/landing/[slug].astro
import butter from '../../lib/buttercms';
import ComponentRenderer from '../../components/ComponentRenderer.astro';

export async function getStaticPaths() {
  const response = await butter.page.list('landing-page');
  return response.data.data.map((page) => ({
    params: { slug: page.slug },
    props: { page },
  }));
}

const { page } = Astro.props;
---

<main>
  <ComponentRenderer components={page.fields.body} />
</main>

Blog

---
// 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 });
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>

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

const { slug } = Astro.params;
const [pageType, pageSlug] = slug.split('/');

let page;
try {
  const response = await butter.page.retrieve(pageType, pageSlug);
  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';

export async function getStaticPaths() {
  const response = await butter.page.list('landing-page');
  return response.data.data.map((page) => ({
    params: { slug: page.slug },
    props: { page },
  }));
}

const { page } = Astro.props;
const seo = 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>
Layout with SEO:
---
// 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