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

# SvelteKit

> Build content-driven SvelteKit applications with ButterCMS. Covers setup, fetching pages, collections, components, and blog 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](../../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>

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

const butter = Butter(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="Server-Side (SSR)">
    ```typescript theme={null}
    // 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');
      }
    };
    ```

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

  <Tab title="Static (SSG)">
    Enable prerendering:

    ```typescript theme={null}
    // 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');
      }
    };
    ```
  </Tab>
</Tabs>

## Collections

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

```svelte theme={null}
<!-- 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

```svelte theme={null}
<!-- 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

```svelte theme={null}
<!-- 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

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

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

```svelte theme={null}
<!-- 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

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

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

  <Tab title="Single Blog Post">
    ```typescript theme={null}
    // 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');
      }
    };
    ```

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

## Preview Mode

Enable draft content preview:

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

```svelte theme={null}
<!-- 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

<CardGroup cols={2}>
  <Card title="JavaScript SDK" icon="js" href="../sdks/javascript-sdk">
    Complete SDK reference
  </Card>

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