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.
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';
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
- Server-Side (SSR)
- Static (SSG)
// 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>
Enable prerendering:
// src/routes/[slug]/+page.server.ts
import butter from '$lib/buttercms';
import { error } from '@sveltejs/kit';
import type { PageServerLoad, EntryGenerator } from './$types';
interface PageFields {
headline: string;
subheadline: string;
hero_image?: string;
body: string | null;
}
export const prerender = true;
export const entries: EntryGenerator = async () => {
const response = await butter.page.list<PageFields>('landing_page');
if (!response.data?.data) {
return [];
}
return response.data.data.map((page) => ({ slug: page.slug }));
};
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');
}
};
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
- Blog Post List
- Single Blog Post
// 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>
// src/routes/blog/[slug]/+page.server.ts
import butter from '$lib/buttercms';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
try {
const response = await butter.post.retrieve(params.slug);
if (!response.data?.data) {
error(404, 'Post not found');
}
return { post: response.data.data };
} catch {
error(404, 'Post not found');
}
};
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
const formattedDate = data.post.published
? new Date(data.post.published).toLocaleDateString()
: '';
</script>
<article>
<h1>{data.post.title}</h1>
{#if data.post.author}
<p>
By {data.post.author.first_name} {data.post.author.last_name} on {formattedDate}
</p>
{/if}
{#if data.post.featured_image}
<img src={data.post.featured_image} alt={data.post.title} />
{/if}
{@html data.post.body ?? ''}
<a href="/blog">Back to Posts</a>
</article>
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