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.

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';
import { BUTTER_CMS_API_KEY } from '$env/static/private';

const butter = Butter(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/routes/[slug]/+page.server.ts
import butter from '$lib/buttercms';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

interface SEOFields {
  title?: string;
  description?: string;
  og_title?: string;
  og_description?: string;
  og_image?: string;
}

interface PageFields {
  headline: string;
  subheadline: string;
  hero_image?: string;
  body: string | null;
  seo?: SEOFields;
}

export const load: PageServerLoad = async ({ params }) => {
  try {
    const response = await butter.page.retrieve<PageFields>('landing_page', params.slug);
    if (!response.data?.data) {
      error(404, 'Page not found');
    }
    return { page: response.data.data };
  } catch {
    error(404, 'Page not found');
  }
};
<!-- src/routes/[slug]/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();
</script>

<main>
  <h1>{data.page.fields.headline}</h1>
  <p>{data.page.fields.subheadline}</p>
  {#if data.page.fields.hero_image}
    <img src={data.page.fields.hero_image} alt={data.page.fields.headline} />
  {/if}
  {@html data.page.fields.body ?? ''}
</main>

Collections

// src/routes/brands/+page.server.ts
import butter from '$lib/buttercms';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async () => {
  const response = await butter.content.retrieve(['brands']);
  if (!response.data?.data) {
    error(500, 'Failed to load brands');
  }
  return { brands: response.data.data.brands };
};
<!-- src/routes/brands/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();
</script>

<main>
  <h1>Our Brands</h1>
  <ul>
    {#each data.brands as brand}
      <li>
        <img src={brand.logo} alt={brand.name} />
        <h2>{brand.name}</h2>
        {@html brand.description}
      </li>
    {/each}
  </ul>
</main>

Dynamic components

Component Renderer

<!-- src/lib/components/ComponentRenderer.svelte -->
<script module lang="ts">
  interface HeroFields {
    headline: string;
    subheadline: string;
    image: string;
    button_label: string;
    button_url: string;
  }

  interface FeaturesFields {
    headline: string;
    items: { icon?: string; title: string; description: string }[];
  }

  interface TestimonialsFields {
    headline: string;
    items: { quote: string; author_name: string; author_title?: string }[];
  }

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

<script lang="ts">
  import Hero from './Hero.svelte';
  import Features from './Features.svelte';
  import Testimonials from './Testimonials.svelte';
  import CTA from './CTA.svelte';
  import type { Component } from 'svelte';

  let { components }: { components: ButterComponent[] } = $props();

  const componentFor = (component: ButterComponent): Component | null => {
    switch (component.type) {
      case 'hero':
        return Hero;
      case 'features':
        return Features;
      case 'testimonials':
        return Testimonials;
      case 'cta':
        return CTA;
      default:
        return null;
    }
  };
</script>

{#each components as component}
  {@const Cmp = componentFor(component)}
  {#if Cmp}<Cmp {...component.fields} />{/if}
{/each}

Example Component

<!-- src/lib/components/Hero.svelte -->
<script lang="ts">
  let {
    headline,
    subheadline,
    image,
    button_label,
    button_url,
  }: {
    headline: string;
    subheadline: string;
    image: string;
    button_label: string;
    button_url: string;
  } = $props();
</script>

<section class="hero">
  <h1>{headline}</h1>
  <p>{subheadline}</p>
  {#if button_label}
    <a href={button_url}>{button_label}</a>
  {/if}
  {#if image}
    <img src={image} alt={headline} />
  {/if}
</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/routes/landing/[slug]/+page.server.ts
import butter from '$lib/buttercms';
import { error } from '@sveltejs/kit';
import type { ButterComponent } from '$lib/components/ComponentRenderer.svelte';
import type { PageServerLoad } from './$types';

interface PageFields {
  body: ButterComponent[];
}

export const load: PageServerLoad = async ({ params }) => {
  const response = await butter.page.retrieve<PageFields>('component_page', params.slug);
  if (!response.data?.data) {
    error(404, 'Page not found');
  }
  return { page: response.data.data };
};
<!-- src/routes/landing/[slug]/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';
  import ComponentRenderer from '$lib/components/ComponentRenderer.svelte';

  let { data }: { data: PageData } = $props();
</script>

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

Blog

// src/routes/blog/+page.server.ts
import butter from '$lib/buttercms';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ url }) => {
  const page = parseInt(url.searchParams.get('page') || '1');
  const response = await butter.post.list({ page, page_size: 10 });
  if (!response.data?.data || !response.data.meta) {
    error(500, 'Failed to load posts');
  }
  return {
    posts: response.data.data,
    meta: response.data.meta,
  };
};
<!-- src/routes/blog/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();
</script>

<main>
  <h1>Blog</h1>
  <ul>
    {#each data.posts as post}
      <li>
        <h2><a href="https://buttercms.com/blog/{post.slug}">{post.title}</a></h2>
        {@html post.summary ?? ''}
        {#if post.author}
          <span>By {post.author.first_name} {post.author.last_name}</span>
        {/if}
      </li>
    {/each}
  </ul>
  {#if data.meta.next_page}
    <a href="/blog?page={data.meta.next_page}">Next Page</a>
  {/if}
</main>

Preview Mode

Enable draft content preview:
// src/routes/preview/[...path]/+page.server.ts
import Butter from 'buttercms';
import { BUTTER_CMS_API_KEY, PREVIEW_SECRET } from '$env/static/private';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

interface PageFields {
  headline: string;
  body: string | null;
}

export const load: PageServerLoad = async ({ params, url }) => {
  const secret = url.searchParams.get('secret');

  if (secret !== PREVIEW_SECRET) {
    error(401, 'Invalid preview token');
  }

  const butter = Butter(BUTTER_CMS_API_KEY, true); // Enable preview mode

  const [pageType, slug] = params.path.split('/');
  if (!pageType || !slug) {
    error(404, 'Page not found');
  }

  try {
    const response = await butter.page.retrieve<PageFields>(pageType, slug);
    if (!response.data?.data) {
      error(404, 'Page not found');
    }
    return { page: response.data.data, isPreview: true };
  } catch {
    error(404, 'Page not found');
  }
};

SEO

<!-- src/routes/[slug]/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();

  const seo = $derived(data.page.fields.seo ?? {});
</script>

<svelte:head>
  <title>{seo.title || data.page.fields.headline}</title>
  {#if seo.description}
    <meta name="description" content={seo.description} />
  {/if}
  <meta property="og:title" content={seo.og_title || seo.title || data.page.fields.headline} />
  {#if seo.og_description || seo.description}
    <meta property="og:description" content={seo.og_description || seo.description} />
  {/if}
  {#if seo.og_image}
    <meta property="og:image" content={seo.og_image} />
  {/if}
</svelte:head>

<main>
  <h1>{data.page.fields.headline}</h1>
  {@html data.page.fields.body ?? ''}
</main>

Resources

JavaScript SDK

Complete SDK reference

Content API

REST API documentation