---
title: "SvelteKit Starter"
url: "https://adaptocms.com/docs/sveltekit-starter/"
source: adaptocms.com
---

# SvelteKit Starter

This guide covers integrating every Adapto CMS content type into a SvelteKit 2 + Svelte 5 project. For SDK method signatures and type definitions, see the [SDK Reference](/docs/sdk-reference).

## Prerequisites

-   An Adapto CMS account with an API key (found in the backoffice under Developer Tools → API Keys)
-   Node.js 18+

## Quick Start

```
npx create-adapto-app --template sveltekit
```

The installer will prompt you for the remaining details — including your Adapto API key — then scaffold the project, install dependencies, and set up your `.env` file. When it finishes, `cd` into your new project and run `npm run dev`.

## Configuration

Environment variables are loaded from SvelteKit's `$env/dynamic/private` and re-exported from `src/config.ts`.

```
// src/config.ts
import { env } from '$env/dynamic/private';

export const API_URL = env.ADAPTO_API_URL ?? '';
export const API_KEY = env.ADAPTO_API_KEY ?? '';
export const IS_DEV = env.DEV === 'true';
export const DEFAULT_LANGUAGE = 'en-US';
export const PAGE_SIZE = 10;
```

* * *

## Articles

### Listing articles

The `summary` field is auto-generated from the article's first paragraph — see the [SDK Reference](/docs/sdk-reference) for the full `IArticle` type.

```
// src/routes/[lang]/articles/+page.server.ts
import { adapto } from '$lib/adapto-sdk';
import { PAGE_SIZE } from '../../../config';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
  const { lang } = params;
  const { items: articles, total, pages: totalPages } = await adapto.articles.list({
    language: lang,
    status: 'published',
    page: 1,
    limit: PAGE_SIZE,
  });
  return { articles, total, totalPages, currentPage: 1 };
};
```

```
<!-- src/routes/[lang]/articles/+page.svelte -->
<script lang="ts">
  let { data } = $props();
</script>

<p>{data.total} articles</p>

{#each data.articles.items as article (article.id)}
  <article>
    <a href="/{data.lang}/articles/{article.slug}">{article.title}</a>
    {#if article.summary}<p>{article.summary}</p>{/if}
    {#if article.published_at}
      <time>{new Date(article.published_at).toLocaleDateString()}</time>
    {/if}
  </article>
{/each}
```

### Article detail

`article.content` is an HTML string. For articles with embedded media, pass it through `hydrateMediaPlacements` before rendering — see the [SDK Reference](/docs/sdk-reference).

```
// src/routes/[lang]/articles/[slug]/+page.server.ts
import { adapto } from '$lib/adapto-sdk';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
  const { slug } = params;
  try {
    const article = await adapto.articles.getBySlug(slug);
    return { article };
  } catch {
    error(404, 'Article not found');
  }
};
```

```
<!-- src/routes/[lang]/articles/[slug]/+page.svelte -->
<script lang="ts">
  let { data } = $props();
</script>

<svelte:head><title>{data.article.title}</title></svelte:head>

<article>
  <h1>{data.article.title}</h1>
  <p>By {data.article.author}</p>
  <div>{@html data.article.content}</div>
</article>
```

### Pagination

Page 1 lives at `/[lang]/articles`. Pages 2+ live at `/[lang]/articles/page/[pageNum]`.

```
// src/routes/[lang]/articles/page/[pageNum]/+page.server.ts
import { adapto } from '$lib/adapto-sdk';
import { error } from '@sveltejs/kit';
import { PAGE_SIZE } from '../../../../../config';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
  const { lang, pageNum } = params;
  const page = parseInt(pageNum, 10);
  if (isNaN(page) || page < 2) error(404, 'Invalid page number');

  const { items: articles, pages: totalPages } = await adapto.articles.list({
    language: lang, status: 'published', page, limit: PAGE_SIZE,
  });
  if (page > totalPages) error(404, 'Page not found');

  return { articles, currentPage: page, totalPages };
};
```

* * *

## Categories

See the [SDK Reference](/docs/sdk-reference) for the full `ICategory` type, including `parent_id` and `description`.

### Listing categories

```
// src/routes/[lang]/articles/categories/+page.server.ts
export const load: PageServerLoad = async ({ params }) => {
  const { lang } = params;
  const { items: categories } = await adapto.categories.list({ language: lang });
  return { categories: categories.filter((c) => c.parent_id === null) };
};
```

### Category detail with filtered articles

Articles store category membership as an array of category IDs in their `categories` field. Pass the category's `id` to the `category` filter when listing articles.

```
// src/routes/[lang]/articles/categories/[slug]/+page.server.ts
import { adapto } from '$lib/adapto-sdk';
import { error } from '@sveltejs/kit';
import { PAGE_SIZE } from '../../../../../config';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
  const { lang, slug } = params;
  const category = await adapto.categories.getBySlug(slug).catch(() => null);
  if (!category) error(404, 'Category not found');

  const { items: articles, pages: totalPages } = await adapto.articles.list({
    language: lang,
    status: 'published',
    category: category.id,
    page: 1,
    limit: PAGE_SIZE,
  });
  return { category, articles, totalPages, currentPage: 1 };
};
```

```
<script lang="ts">
  let { data } = $props();
</script>

<h1>{data.category.name}</h1>
{#if data.category.description}
  <div>{@html data.category.description}</div>
{/if}
{#each data.articles as article (article.id)}
  <a href="/{data.lang}/articles/{article.slug}">{article.title}</a>
{/each}
```

### Subcategories

```
const subcategories = await adapto.categories.getSubcategories(category.id);
```

* * *

## Pages

CMS-managed pages (Home, About, Contact, etc.) are fetched by slug. See the [SDK Reference](/docs/sdk-reference) for the full `IPage` type including `menu_label`.

```
// src/routes/[lang]/[slug]/+page.server.ts
import { adapto } from '$lib/adapto-sdk';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
  const { slug } = params;
  try {
    const page = await adapto.pages.getBySlug(slug);
    return { page };
  } catch {
    error(404, 'Page not found');
  }
};
```

```
<script lang="ts">
  let { data } = $props();
</script>

<svelte:head><title>{data.page.title}</title></svelte:head>

<main>
  <h1>{data.page.title}</h1>
  <div>{@html data.page.content}</div>
</main>
```

* * *

## Custom Collections

A collection defines a schema via its `fields` array. Items store their values in a plain `data` object keyed by field name. See the [SDK Reference](/docs/sdk-reference) for all `ICollectionField` types.

### Collection list

```
// src/routes/[lang]/[collection_slug]/+page.server.ts
import { adapto } from '$lib/adapto-sdk';
import { error } from '@sveltejs/kit';
import { PAGE_SIZE } from '../../../config';

export const load = async ({ params }) => {
  const { lang, collection_slug } = params;
  const collection = await adapto.collections.getBySlug(collection_slug).catch(() => null);
  if (!collection) error(404, 'Collection not found');

  const { items, pages: totalPages } = await adapto.collections.listItems(
    collection.id,
    { language: lang, status: 'published', page: 1, limit: PAGE_SIZE },
  );
  return { collection, items, totalPages, currentPage: 1 };
};
```

### Collection item detail

```
// src/routes/[lang]/[collection_slug]/[item_slug]/+page.server.ts
import { adapto } from '$lib/adapto-sdk';
import { error } from '@sveltejs/kit';

export const load = async ({ params }) => {
  const { collection_slug, item_slug } = params;
  try {
    const collection = await adapto.collections.getBySlug(collection_slug);
    const item = await adapto.collections.getItemBySlug(collection.id, item_slug);
    return { collection, item };
  } catch {
    error(404, 'Item not found');
  }
};
```

```
<script lang="ts">
  let { data } = $props();
</script>

<h1>{data.item.title}</h1>

{#each Object.entries(data.item.data) as [fieldName, value] (fieldName)}
  {@const field = data.collection.fields.find((f) => f.name === fieldName)}
  {#if field && value !== null && value !== ''}
    <section>
      <h3>{field.label}</h3>
      {#if field.type === 'rich_text'}
        <div>{@html String(value)}</div>
      {:else if field.type === 'image'}
        <img src={String(value)} alt={field.label} />
      {:else if field.type === 'url' || field.type === 'email'}
        <a href={String(value)}>{String(value)}</a>
      {:else if field.type === 'boolean'}
        <span>{value ? 'Yes' : 'No'}</span>
      {:else if field.type === 'multi_select'}
        {#each value as option}<span>{option}</span>{/each}
      {:else}
        <p>{String(value)}</p>
      {/if}
    </section>
  {/if}
{/each}
```

* * *

## Micro Copy

Micro copy entries are short, language-scoped strings managed in the CMS. Load the full dictionary for a language in the `[lang]` layout so every child route can use it without extra fetches.

```
// src/routes/[lang]/+layout.server.ts
import { adapto } from '$lib/adapto-sdk';
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ params }) => {
  const { lang } = params;
  const [languages, copy] = await Promise.all([
    adapto.languages.list(),
    adapto.microCopy.getDictionary(lang),
  ]);
  return { lang, languages, copy };
};
```

```
<!-- Usage in any child +page.svelte -->
<script lang="ts">
  let { data } = $props();
</script>

<button>{data.copy['btn.submit']}</button>
```

* * *

## Multi-language

Every content type has a `language` field storing the full locale code (e.g. `"en-US"`). The `[lang]` URL segment uses the same value. The root route redirects to the first available language.

```
// src/routes/+page.server.ts
import { redirect } from '@sveltejs/kit';
import { adapto } from '$lib/adapto-sdk';

export const load = async () => {
  const languages = await adapto.languages.list();
  redirect(302, `/${languages[0] ?? 'en-US'}`);
};
```
