> ## Documentation Index
> Fetch the complete documentation index at: https://buttercms.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Astro

> Build content-driven Astro sites with ButterCMS. Covers setup, fetching pages, collections, components, and blog content with Astro's static-first approach.

## 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](../../core-concepts/content-types/pages), [Collections](../../core-concepts/content-types/collections), and [Blog Posts](../../core-concepts/content-types/blog-engine).

<Tip>
  In order for the snippets to work, you'll need to [setup your dashboard content schemas inside of ButterCMS](../buttercms-setup) first.
</Tip>

### 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.

<Card title="Astro Starter Project" icon="rocket" href="../starter-projects/astro">
  Hit the ground running with a pre-configured Astro + ButterCMS setup.
</Card>

## Installation

<Tabs>
  <Tab title="npm">
    ```bash theme={null}
    npm install buttercms
    ```
  </Tab>

  <Tab title="yarn">
    ```bash theme={null}
    yarn add buttercms
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash theme={null}
    pnpm add buttercms
    ```
  </Tab>
</Tabs>

Add your API token to `.env`:

```bash theme={null}
BUTTER_CMS_API_KEY=your_api_token
```

## Initialize the client

Create a reusable client instance:

```typescript theme={null}
// src/lib/buttercms.ts
import Butter from 'buttercms';

const butter = Butter(import.meta.env.BUTTER_CMS_API_KEY);

export default butter;
```

<Info>
  For complete SDK documentation including all available methods and configuration options, see the [JavaScript SDK Reference](../sdks/javascript-sdk).
</Info>

## Pages

<Tabs>
  <Tab title="Static (SSG)">
    ```astro theme={null}
    ---
    // 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>
    ```
  </Tab>

  <Tab title="Server (SSR)">
    Enable SSR in `astro.config.mjs`:

    ```javascript theme={null}
    // astro.config.mjs
    import { defineConfig } from 'astro/config';

    export default defineConfig({
      output: 'server',
    });
    ```

    ```astro theme={null}
    ---
    // 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>
    ```
  </Tab>
</Tabs>

## Collections

```astro theme={null}
---
// 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

```astro theme={null}
---
// 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

```astro theme={null}
---
// 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

<Info>
  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.
</Info>

```astro theme={null}
---
// 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

<Tabs>
  <Tab title="Blog Post List">
    ```astro theme={null}
    ---
    // 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>
    ```
  </Tab>

  <Tab title="Single Blog Post">
    ```astro theme={null}
    ---
    // 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>
    ```
  </Tab>
</Tabs>

## Preview Mode

Enable draft content preview with SSR:

```astro theme={null}
---
// 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

```astro theme={null}
---
// 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>
```

Layout with SEO:

```astro theme={null}
---
// 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

<CardGroup cols={2}>
  <Card title="Astro Starter" icon="rocket" href="../starter-projects/astro">
    Pre-configured starter project
  </Card>

  <Card title="JavaScript SDK" icon="js" href="../sdks/javascript-sdk">
    Complete SDK reference
  </Card>

  <Card title="GitHub Repository" icon="github" href="https://github.com/ButterCMS/astro-starter-buttercms">
    View source code
  </Card>

  <Card title="Content API" icon="database" href="../../api-reference/pages/get-multiple-pages">
    REST API documentation
  </Card>
</CardGroup>
