# Uniform SDK for Svelte and SvelteKit

> Comprehensive guide to integrating Uniform with Svelte and SvelteKit, including Canvas composition rendering, Context personalization, visual editing, edge personalization, and ISR.

Source: https://docs.uniform.app/docs/sdk/svelte

> **Warning:**
>
> **Preview release.** The Svelte / SvelteKit SDK is currently in preview. It is fully functional and suitable for evaluation and development, but APIs may change before general availability.

The Uniform SDK for Svelte and SvelteKit provides a full integration between Uniform DXP and Svelte applications. It spans three complementary packages:

- **`@uniformdev/canvas-svelte`** -- Svelte components for rendering Canvas compositions (`UniformComposition`, `UniformSlot`, `UniformText`, `UniformRichText`) and contextual editing support.
- **`@uniformdev/canvas-sveltekit`** -- SvelteKit-specific integrations: route loading (`createUniformLoad`), server hooks (`createUniformHandle`), preview handlers, and ISR configuration.
- **`@uniformdev/context-svelte`** -- Svelte bindings for Uniform Context, enabling personalization, A/B testing, visitor scoring, and behavior tracking via the `UniformContext` provider and reactive helpers.

> **Tip:**
>
> **Just want to try it out?** Clone one of the working examples and explore:
>
> - [**SvelteKit Starter**](https://github.com/uniformdev/examples/tree/main/examples/sveltekit-starter) -- Minimal starter with composition rendering, component mapping, and visual editing.
> - [**SvelteKit Demo**](https://github.com/uniformdev/examples/tree/main/examples/sveltekit-demo) -- Full-featured demo with personalization, A/B testing, and edge middleware.

---

## How it works

Understanding the request lifecycle helps you reason about configuration choices and debug issues:

```mermaid
flowchart TD
    A["Visitor requests /about"] --> B["SvelteKit catch-all route"]
    B --> C["+page.server.ts calls createUniformLoad"]
    C --> D["RouteClient resolves path via Project Map"]
    D --> E["Composition data returned to +page.svelte"]
    E --> F["UniformComposition renders component tree"]
    F --> G["componentMap maps each type to a Svelte component"]
    G --> H["UniformSlot renders child components in slots"]
    H --> I["UniformText / UniformRichText enable inline editing"]
    
    style A fill:#e0e7ff,stroke:#4f46e5
    style F fill:#f0fdf4,stroke:#16a34a
    style I fill:#fef3c7,stroke:#d97706
```

1. **A visitor requests a page** -- SvelteKit's catch-all route `[...path]/+page.server.ts` receives the request.
2. **The composition is fetched server-side** -- `createUniformLoad` from `@uniformdev/canvas-sveltekit` uses `RouteClient` to resolve the URL against the Project Map and fetch the full composition data.
3. **Components render via the composition tree** -- `UniformComposition` walks the tree and uses your `componentMap` (or a custom `resolveRenderer`) to map each Uniform component type to a Svelte component. Parameters are flattened and passed directly as props.
4. **Personalization and testing are evaluated** -- The `UniformContext` provider evaluates personalization rules and A/B tests either client-side (`standard`) or outputs NESI placeholders for edge evaluation (`edge`).
5. **Visual editing works out of the box** -- When previewing in the Uniform dashboard, the SDK automatically enables contextual editing, inline text editing, and live preview via the playground route.

---

## Getting started

### Prerequisites

- SvelteKit 2+ (Svelte 5)
- Node.js 18+
- A Uniform project with compositions set up

### Install packages

```bash
npm install @uniformdev/canvas @uniformdev/canvas-svelte @uniformdev/canvas-sveltekit @uniformdev/context @uniformdev/context-svelte
```

Install the Uniform CLI as a dev dependency for syncing content and downloading manifests:

```bash
npm install -D @uniformdev/cli
```

| Package | Purpose |
| --- | --- |
| `@uniformdev/canvas` | Core Canvas API client, types, and utilities |
| `@uniformdev/canvas-svelte` | Svelte components: `UniformComposition`, `UniformSlot`, `UniformText`, `UniformRichText` |
| `@uniformdev/canvas-sveltekit` | SvelteKit integrations: route loading, server hooks, preview handler, ISR |
| `@uniformdev/context` | Core Context personalization engine |
| `@uniformdev/context-svelte` | `UniformContext` provider and reactive helpers (`getScores`, `getQuirks`) |
| `@uniformdev/cli` | CLI for pulling manifests and syncing content |

### Environment variables

Create a `.env` file in your project root:

```bash
UNIFORM_API_KEY=your-api-key
UNIFORM_PROJECT_ID=your-project-id
UNIFORM_PREVIEW_SECRET=your-preview-secret
```

> **Note:**
>
> You can find these values in your Uniform project under **Security > Service Accounts**. See the [API access guide](https://docs.uniform.app/docs/guides/api-access) for more information.

---

## Project setup

The Uniform SDK requires a specific set of files to wire up routing, rendering, and preview. Here is the minimal file structure:

```
your-sveltekit-app/
├── src/
│   ├── app.d.ts                          # TypeScript declarations for preview
│   ├── app.html                          # HTML template
│   ├── hooks.server.ts                   # Server hooks (preview detection)
│   ├── routes/
│   │   ├── +layout.svelte                # Root layout with UniformContext
│   │   ├── [...path]/
│   │   │   ├── +page.server.ts           # Fetches compositions from Canvas API
│   │   │   ├── +page.svelte              # Renders compositions
│   │   │   └── +error.svelte             # Error boundary
│   │   ├── playground/
│   │   │   └── +page.svelte              # Component preview for Canvas editor
│   │   └── preview/
│   │       └── +server.ts                # Preview API endpoint
│   └── lib/
│       ├── components/                   # Svelte components mapped to Uniform types
│       │   ├── Hero.svelte
│       │   ├── Page.svelte
│       │   └── index.ts
│       └── uniform/
│           ├── componentMap.ts           # Maps Uniform types to Svelte components
│           └── contextManifest.json      # Downloaded personalization manifest
├── middleware.ts                          # Edge middleware (optional, for Vercel)
├── uniform.config.ts                     # CLI configuration
├── svelte.config.js
├── vite.config.ts
└── .env
```

The following sections walk through each file.

### Step 1: TypeScript declarations

Update `src/app.d.ts` to include Uniform preview data types:

```ts
// src/app.d.ts
import type { UniformPreviewData } from '@uniformdev/canvas-sveltekit';

declare global {
  namespace App {
    interface Locals {
      uniformPreview?: UniformPreviewData;
    }
  }
}

export {};
```

### Step 2: Root layout with UniformContext

Create `src/routes/+layout.svelte` to initialize the Uniform Context provider:

```svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { dev } from '$app/environment';
  import {
    Context,
    CookieTransitionDataStore,
    enableContextDevTools,
    enableDebugConsoleLogDrain,
  } from '@uniformdev/context';
  import { UniformContext } from '@uniformdev/context-svelte';
  import type { ManifestV2 } from '@uniformdev/context';
  import manifestJson from '$lib/uniform/contextManifest.json';

  let { children } = $props();

  const context = new Context({
    defaultConsent: true,
    transitionStore: new CookieTransitionDataStore({
      serverCookieValue: undefined,
    }),
    plugins: [enableContextDevTools(), enableDebugConsoleLogDrain('debug')],
    manifest: manifestJson as ManifestV2,
  });
</script>

<UniformContext {context} outputType={dev ? 'standard' : 'edge'}>
  {@render children()}
</UniformContext>
```

#### UniformContext props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `context` | `Context` | -- | **Required.** A configured Uniform Context instance |
| `outputType` | `'standard' \| 'edge'` | `'standard'` | How personalization variants are rendered |
| `trackRouteOnRender` | `boolean` | `true` | Whether to track route changes on render |
| `includeTransferState` | `'always' \| 'never' \| 'server-only'` | `'server-only'` | When to include transfer state for SSR hydration |

> **Note:**
>
> The `outputType` setting controls how personalization and A/B test variants are rendered:
>
> - **`standard`** -- Evaluates personalization server-side during SSR or client-side. Outputs only the winning variant. Use for development and standard SSR/SSG deployments.
> - **`edge`** -- Outputs all variants as NESI (Nested Edge-Side Includes) placeholders. An edge middleware selects the winning variant before delivering the page. Use for production with edge personalization.

### Step 3: Server hooks

Create `src/hooks.server.ts` to handle Uniform preview mode detection:

```ts
// src/hooks.server.ts
import { sequence } from '@sveltejs/kit/hooks';
import { createUniformHandle } from '@uniformdev/canvas-sveltekit';

const uniformHandle = createUniformHandle({
  onPreview: (event, previewData) => {
    if (previewData.isUniformContextualEditing) {
      console.log(
        '[Uniform] Contextual editing mode active for:',
        previewData.compositionPath
      );
    }
  },
});

export const handle = sequence(uniformHandle);
```

The `createUniformHandle` hook:

- Parses preview cookies set by the Canvas editor
- Makes preview data available via `event.locals.uniformPreview`
- Optionally calls a callback when preview mode is active

### Step 4: Catch-all route

Create the route structure to serve Uniform compositions.

**`src/routes/[...path]/+page.server.ts`** -- Server-side data loading:

```ts
// src/routes/[...path]/+page.server.ts
import { error } from '@sveltejs/kit';
import { RouteClient } from '@uniformdev/canvas';
import { env } from '$env/dynamic/private';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async (event) => {
  if (!env.UNIFORM_API_KEY || !env.UNIFORM_PROJECT_ID) {
    error(500, 'Uniform credentials not configured.');
  }

  const { createUniformLoad } = await import('@uniformdev/canvas-sveltekit/route');

  const client = new RouteClient({
    apiKey: env.UNIFORM_API_KEY,
    projectId: env.UNIFORM_PROJECT_ID,
  });

  const uniformLoad = createUniformLoad({
    client,
    param: 'path',
  });

  return uniformLoad(event);
};
```

**`src/routes/[...path]/+page.svelte`** -- Composition rendering:

```svelte
<!-- src/routes/[...path]/+page.svelte -->
<script lang="ts">
  import { UniformComposition, UniformSlot } from '@uniformdev/canvas-svelte';
  import { componentMap } from '$lib/uniform/componentMap.js';

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

<UniformComposition
  data={data.data}
  {componentMap}
  matchedRoute={data.matchedRoute}
  dynamicInputs={data.dynamicInputs}
>
  <UniformSlot name="content" />
</UniformComposition>
```

#### createUniformLoad options

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `client` | `RouteClient` | -- | **Required.** A configured RouteClient instance |
| `param` | `string` | `'path'` | The route parameter name (matches `[...path]`) |
| `projectMapId` | `string` | `undefined` | Optional project map ID (uses default if not set) |
| `silent` | `boolean` | `false` | Suppress console logging |
| `requestOptions` | `object` | `undefined` | Customize API request options |
| `handleComposition` | `function` | built-in | Custom handler for composition responses |
| `handleRedirect` | `function` | built-in | Custom handler for redirect responses |
| `handleNotFound` | `function` | built-in | Custom handler for 404 responses |

### Step 5: Component mapping

Create `src/lib/uniform/componentMap.ts` to map Uniform component types to Svelte components:

```ts
// src/lib/uniform/componentMap.ts
import type { ComponentMap } from '@uniformdev/canvas-svelte';
import Hero from '$lib/components/Hero.svelte';
import Page from '$lib/components/Page.svelte';

export const componentMap: ComponentMap = {
  page: Page,
  hero: Hero,

  // Variant mapping: use `type__variant` format
  // hero__dark: HeroDark,
};
```

The `componentMap` is a simple object where keys are Uniform component type strings and values are Svelte components. For variant-specific mappings, use the `type__variant` format (double underscore).

> **Note:**
>
> **Svelte uses `componentMap` instead of `resolveRenderer`.** Unlike the React and Nuxt SDKs which use a `resolveRenderer` function, the Svelte SDK uses a declarative `componentMap` object. This is more idiomatic for Svelte and simpler to maintain.
>
> You can also use a custom `resolveRenderer` function if you need dynamic resolution logic. Pass it as a prop to `UniformComposition` instead of `componentMap`.

### Step 6: Preview handler

Create `src/routes/preview/+server.ts` to enable Canvas contextual editing:

```ts
// src/routes/preview/+server.ts
import { createPreviewHandler } from '@uniformdev/canvas-sveltekit/preview';
import { env } from '$env/dynamic/private';

const handlers = createPreviewHandler({
  secret: () => env.UNIFORM_PREVIEW_SECRET ?? '',
  playgroundPath: '/playground',
  resolveFullPath: ({ path, slug }) => {
    return path || slug || '/';
  },
});

export const GET = handlers.GET;
export const POST = handlers.POST;
export const OPTIONS = handlers.OPTIONS;
```

### Step 7: Playground page

Create `src/routes/playground/+page.svelte` for previewing individual components and patterns:

```svelte
<!-- src/routes/playground/+page.svelte -->
<script lang="ts">
  import { UniformComposition, UniformSlot } from '@uniformdev/canvas-svelte';
  import { componentMap } from '$lib/uniform/componentMap.js';
  import { EMPTY_COMPOSITION } from '@uniformdev/canvas';
</script>

<div class="playground">
  <UniformComposition data={EMPTY_COMPOSITION} {componentMap}>
    <UniformSlot name="content" />
  </UniformComposition>
</div>
```

The playground page renders an empty composition shell that the Canvas editor populates during contextual editing sessions.

---

## Building components

Uniform components in Svelte receive **flattened props** -- parameter values are extracted from their `ComponentParameter` wrappers and spread directly onto the component via Svelte 5's `$props()`. For example, if a Uniform component has a `title` parameter of type `text`, your Svelte component receives a `title` prop with the string value directly.

Every component also receives a `component` prop containing the full `ComponentInstance`, which provides access to raw parameter data, component metadata, variant info, and slots.

### Component props

Every component rendered via the `componentMap` receives these props:

| Prop | Type | Description |
| --- | --- | --- |
| `component` | `ComponentInstance` | The full component instance with raw parameters, slots, variant, and metadata |
| `[parameterName]` | flattened value | Each parameter's `.value` is spread as a top-level prop |

### Basic component with parameters

Use the `ComponentProps<T>` type to define your component's props:

```svelte
<!-- src/lib/components/Hero.svelte -->
<script lang="ts">
  import { UniformText, UniformRichText } from '@uniformdev/canvas-svelte';
  import type { ComponentProps } from '@uniformdev/canvas-svelte';

  interface Props extends ComponentProps<{
    title?: string;
    description?: string;
  }> {}

  let { title, description, component }: Props = $props();
</script>

<section class="hero">
  <UniformText parameterId="title" as="h1" placeholder="Enter hero title" />
  <UniformRichText parameterId="description" as="div" placeholder="Enter description" />
</section>
```

### Component with slots

Use `UniformSlot` to render child components placed in named slots:

```svelte
<!-- src/lib/components/Page.svelte -->
<script lang="ts">
  import { UniformSlot } from '@uniformdev/canvas-svelte';
  import type { ComponentProps } from '@uniformdev/canvas-svelte';

  interface Props extends ComponentProps<{
    title?: string;
    description?: string;
  }> {}

  let { title, description, component }: Props = $props();
</script>

<svelte:head>
  {#if title}
    <title>{title}</title>
  {/if}
  {#if description}
    <meta name="description" content={description} />
  {/if}
</svelte:head>

<main>
  <header>
    <UniformSlot name="header" />
  </header>
  <UniformSlot name="content" />
  <footer>
    <UniformSlot name="footer" />
  </footer>
</main>
```

### Component with variants

Access variant information via the `component` prop to conditionally style components:

```svelte
<!-- src/lib/components/Card.svelte -->
<script lang="ts">
  import { UniformText } from '@uniformdev/canvas-svelte';
  import type { ComponentProps } from '@uniformdev/canvas-svelte';

  interface Props extends ComponentProps<{
    title?: string;
    description?: string;
    link?: string;
  }> {}

  let { title, description, link, component }: Props = $props();

  const isFeatured = $derived(component.variant === 'featured');
</script>

<article class="card" class:featured={isFeatured}>
  <h3>
    <UniformText parameterId="title" as="span" />
    {#if isFeatured}
      <span class="badge">Featured</span>
    {/if}
  </h3>
  {#if description}
    <p><UniformText parameterId="description" as="span" /></p>
  {/if}
  {#if link}
    <a href={link}>Learn more &rarr;</a>
  {/if}
</article>
```

Register variants in the component map using the `type__variant` format:

```ts
export const componentMap: ComponentMap = {
  card: Card,
  card__featured: Card,  // Same component, different styling via variant check
};
```

---

## UniformText

`UniformText` renders text parameters with built-in support for inline editing in the Uniform visual editor:

```svelte
<UniformText
  parameterId="title"
  as="h1"
  placeholder="Enter title"
/>
```

### UniformText props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `parameterId` | `string` | -- | **Required.** The ID of the parameter to render |
| `as` | `string` | `'span'` | The HTML element to render |
| `placeholder` | `string \| ((props: { id: string }) => string)` | `undefined` | Placeholder text shown in the Canvas editor when empty |
| `isMultiline` | `boolean` | `false` | When `true`, adds `white-space: pre-wrap` for line break support |

### Custom rendering with snippets

`UniformText` supports a default snippet slot for custom value rendering:

```svelte
<UniformText parameterId="title" as="h1">
  {#snippet children(value)}
    {value?.toUpperCase()}
  {/snippet}
</UniformText>
```

---

## UniformRichText

`UniformRichText` renders rich text parameters with full formatting support:

```svelte
<UniformRichText
  parameterId="description"
  as="div"
  placeholder="Enter description"
/>
```

### UniformRichText props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `parameterId` | `string` | -- | **Required.** The ID of the rich text parameter |
| `as` | `string` | `undefined` | Optional wrapper element |
| `placeholder` | `string \| ((props: { id: string }) => string)` | `undefined` | Placeholder text when empty |
| `resolveRichTextRenderer` | `RenderRichTextComponentResolver` | `undefined` | Custom function to override default rich text node renderers |

### Custom rich text node rendering

Override how specific rich text node types are rendered:

```svelte
<script lang="ts">
  import { UniformRichText } from '@uniformdev/canvas-svelte';
  import type { RenderRichTextComponentResolver } from '@uniformdev/canvas-svelte';
  import CustomHeading from './CustomHeading.svelte';

  const resolveRichTextRenderer: RenderRichTextComponentResolver = (node) => {
    if (node.type === 'heading') {
      return CustomHeading;
    }
    return null; // Use default renderer
  };
</script>

<UniformRichText
  parameterId="body"
  as="div"
  {resolveRichTextRenderer}
/>
```

---

## UniformSlot

`UniformSlot` renders child components placed in a named slot:

```svelte
<UniformSlot name="content" />
```

When `name` is omitted, `UniformSlot` renders all slots combined.

### UniformSlot props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | `undefined` | The slot name to render. If omitted, all slots are rendered |
| `resolveRenderer` | `RenderComponentResolver` | inherited | Override the component resolver for this slot's children |
| `wrapperComponent` | `Component` | `undefined` | A Svelte component that wraps the slot's children |

### Empty slot placeholder

`UniformSlot` supports an `emptyPlaceholder` snippet for when the slot has no items:

```svelte
<UniformSlot name="sidebar">
  {#snippet emptyPlaceholder()}
    <p class="text-gray-400 italic">Drag components here</p>
  {/snippet}
</UniformSlot>
```

---

## UniformComposition

`UniformComposition` is the entry point for rendering a composition tree:

```svelte
<UniformComposition data={compositionData} {componentMap}>
  <UniformSlot name="content" />
</UniformComposition>
```

### UniformComposition props

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | `RootComponentInstance` | Yes | The composition data from the Uniform API |
| `componentMap` | `ComponentMap` | Yes* | Object mapping component types to Svelte components |
| `resolveRenderer` | `RenderComponentResolver` | No | Custom resolver function (alternative to `componentMap`) |
| `matchedRoute` | `string` | No | The matched route path |
| `dynamicInputs` | `Record<string, string>` | No | Dynamic input values from route parameters |
| `behaviorTracking` | `'onLoad' \| 'onView'` | No | When to track enrichment behavior |
| `contextualEditingEnhancer` | `function` | No | Custom enhancer for contextual editing updates |

> **Note:**
>
> Either `componentMap` or `resolveRenderer` must be provided. `componentMap` is the recommended approach for most projects.

---

## Asset parameters

Use `flattenValues` from `@uniformdev/canvas` to work with asset parameters:

```svelte
<script lang="ts">
  import type { ComponentProps } from '@uniformdev/canvas-svelte';
  import { flattenValues } from '@uniformdev/canvas';

  interface Props extends ComponentProps<{}> {}

  let { component }: Props = $props();

  const hero = flattenValues(component.parameters?.featuredImage, {
    toSingle: true,
  });

  const images = flattenValues(component.parameters?.gallery);
</script>

{#if hero}
  <img
    src={hero.url}
    width={hero.width}
    height={hero.height}
    alt={hero.fields?.alt?.value ?? ''}
  />
{/if}

{#each images ?? [] as img}
  <img src={img?.url} width={img?.width} height={img?.height} alt="" />
{/each}
```

> **Warning:**
>
> Always use `flattenValues` from `@uniformdev/canvas` for asset parameters. Do not create custom utility functions for extracting asset data.

---

## Personalization and testing

Personalization and A/B testing are configured in the Uniform dashboard and evaluated automatically by the SDK. When `UniformSlot` encounters a personalization or test container in the composition tree, it renders the appropriate `Personalize` or `Test` component from `@uniformdev/context-svelte`.

### How personalization works

```mermaid
flowchart LR
    A["Manifest downloaded<br/>(signals, enrichments, quirks)"] --> B["UniformContext initialized<br/>in +layout.svelte"]
    B --> C["Visitor browses site"]
    C --> D["Track / TrackFragment<br/>updates enrichment scores"]
    D --> E["UniformSlot encounters<br/>personalization container"]
    E --> F["Context evaluates visitor scores<br/>against variant criteria"]
    F --> G["Winning variant rendered"]
    
    style A fill:#e0e7ff,stroke:#4f46e5
    style F fill:#f0fdf4,stroke:#16a34a
    style G fill:#fef3c7,stroke:#d97706
```

1. The personalization manifest is downloaded locally (see [Manifest management](#manifest-management)) and passed to the Context in the root layout.
2. The `UniformContext` provider creates a `Context` instance and makes it available to the entire component tree.
3. When `UniformSlot` encounters a personalization container, it evaluates the visitor's scores against the variant criteria and renders the winning variant(s).
4. Score updates happen through enrichment tracking (`Track` component) and route-based signals.

### Output types

The `outputType` prop on `UniformContext` controls how variants are rendered:

- **`standard`** (default) -- Evaluates personalization during SSR or on the client. Only the winning variant HTML is included. Best for development and most deployments.
- **`edge`** -- Outputs all variants as NESI placeholders. An edge middleware selects the winning variant before the response reaches the browser. Best for production with edge personalization (see [Edge personalization](#edge-personalization)).

```svelte
<UniformContext {context} outputType={dev ? 'standard' : 'edge'}>
  {@render children()}
</UniformContext>
```

---

## Client-side context

### getUniformContext

Access the Uniform Context instance directly. Must be called within a `UniformContext` provider:

```svelte
<script lang="ts">
  import { getUniformContext } from '@uniformdev/context-svelte';

  const { context } = getUniformContext();

  const forgetMe = async () => {
    await context.forget(true);
  };
</script>

<button onclick={forgetMe}>Forget me</button>
```

The `context` object is the `@uniformdev/context` `Context` instance:

| Method / Property | Description |
| --- | --- |
| `context.scores` | Current visitor score values (not reactive -- use `getScores()`) |
| `context.quirks` | Current visitor quirk values (not reactive -- use `getQuirks()`) |
| `context.update(options)` | Update visitor data (quirks, enrichments, URL) |
| `context.forget(purge)` | Clear all visitor data. Pass `true` to also clear cookies |
| `context.personalize(options)` | Manually evaluate personalization |

> **Warning:**
>
> The `context.scores` and `context.quirks` properties accessed directly are **not reactive** -- they do not trigger re-renders. Use `getScores()` and `getQuirks()` for reactive access.

### getScores

Provides reactive access to the current visitor's score values. Uses Svelte 5's `$state` rune internally:

```svelte
<script lang="ts">
  import { getScores } from '@uniformdev/context-svelte';

  const scores = getScores();
</script>

<div>Tech interest score: {scores.current?.tech ?? 0}</div>
```

Returns an object with a reactive `.current` property containing the `ScoreVector`.

### getQuirks

Provides reactive access to the current visitor's quirk values:

```svelte
<script lang="ts">
  import { getQuirks } from '@uniformdev/context-svelte';

  const quirks = getQuirks();
</script>

<div>Current country: {quirks.current?.country ?? 'Unknown'}</div>
```

Returns an object with a reactive `.current` property containing the `Quirks` object.

### Updating quirks

Update visitor quirks to trigger personalization changes:

```svelte
<script lang="ts">
  import { getUniformContext } from '@uniformdev/context-svelte';

  const { context } = getUniformContext();

  const setCountry = async (country: string) => {
    await context.update({
      quirks: { country },
    });
  };
</script>

<div>
  <button onclick={() => setCountry('Canada')}>I'm from Canada</button>
  <button onclick={() => setCountry('USA')}>I'm from USA</button>
</div>
```

---

## Contextual editing

The SDK provides built-in support for Uniform's visual/contextual editing. When a composition is opened in the Uniform Canvas editor, the SDK automatically:

1. Listens for composition update messages from the editor
2. Re-renders the component tree with the updated data
3. Enables inline editing for `UniformText` and `UniformRichText` components
4. Annotates components with editing attributes for the Canvas UI

### getUniformContextualEditing

For custom contextual editing setups, use `getUniformContextualEditing` directly:

```svelte
<script lang="ts">
  import { getUniformContextualEditing } from '@uniformdev/canvas-svelte';

  const { composition, isContextualEditing } = getUniformContextualEditing({
    initialCompositionValue: compositionData,
    enhance: (message) => message.composition,
  });
</script>
```

| Return Value | Type | Description |
| --- | --- | --- |
| `composition` | reactive `RootComponentInstance` | The current composition (updates during editing) |
| `isContextualEditing` | reactive `boolean` | Whether the app is in contextual editing mode |

---

## Manifest management

> **Warning:**
>
> **SvelteKit requires downloading the manifest locally.** The manifest must be imported as a JSON file and provided to the `Context` instance in the root layout.

Add these scripts to your `package.json`:

```json
{
  "scripts": {
    "dev": "npm run pull:manifest && vite dev",
    "build": "npm run pull:manifest && vite build",
    "pull:manifest": "uniform context manifest download --output ./src/lib/uniform/contextManifest.json",
    "pull:content": "uniform sync pull",
    "push:content": "uniform sync push",
    "publish:manifest": "uniform context manifest publish"
  }
}
```

| Script | Description |
| --- | --- |
| `pull:manifest` | Downloads the personalization manifest to a local JSON file |
| `pull:content` | Pulls content definitions from your Uniform project |
| `push:content` | Pushes local content changes to your Uniform project |
| `publish:manifest` | Publishes the manifest (required after changing signals, enrichments, or quirks) |

Create a placeholder manifest file at `src/lib/uniform/contextManifest.json`:

```json
{
  "project": {}
}
```

Add `contextManifest.json` to `src/lib/uniform/.gitignore` since it is auto-generated.

---

## Uniform CLI configuration

Create `uniform.config.ts` at your project root:

```ts
// uniform.config.ts
import { uniformConfig } from '@uniformdev/cli/config';

module.exports = uniformConfig({
  preset: 'all',
  disableEntities: ['webhook'],
});
```

---

## Edge personalization

For production deployments on Vercel, you can enable edge-side personalization using NESI (Nested Edge-Side Includes). This moves personalization evaluation from the client to the edge, delivering fully personalized HTML with zero client-side JavaScript overhead.

### How edge personalization works

```mermaid
sequenceDiagram
    participant V as Visitor
    participant E as Vercel Edge
    participant S as SvelteKit Server

    V->>E: Request /about
    E->>S: Forward request (x-from-middleware: true)
    S-->>E: HTML with NESI placeholders
    Note over E: Context evaluates visitor<br/>cookies + URL signals
    Note over E: NESI handler replaces<br/>placeholders with winning variants
    E-->>V: Fully personalized HTML
```

### Setup

Edge personalization requires two additional packages:

```bash
npm install @uniformdev/context-edge @uniformdev/context-edge-sveltekit
```

#### 1. Set output type to `edge` in production

This is already configured if you followed the setup guide:

```svelte
<UniformContext {context} outputType={dev ? 'standard' : 'edge'}>
  {@render children()}
</UniformContext>
```

#### 2. Create edge middleware

Create `middleware.ts` in the project root (for Vercel deployments):

```ts
// middleware.ts
import {
  Context,
  CookieTransitionDataStore,
  UNIFORM_DEFAULT_COOKIE_NAME,
} from '@uniformdev/context';
import { createUniformNesiResponseHandler } from '@uniformdev/context-edge-sveltekit';
import { next } from '@vercel/functions';
import { parse } from 'cookie';
import type { ManifestV2 } from '@uniformdev/context';
import manifestJson from './src/lib/uniform/contextManifest.json';

const manifest = manifestJson as ManifestV2;

export default async function middleware(request: Request) {
  if (request.headers.get('x-from-middleware') === 'true') {
    return next(request);
  }

  const url = new URL(request.url);
  const cookieValue = request.headers.get('cookie');
  const cookies = parse(cookieValue ?? '');

  const context = new Context({
    manifest,
    defaultConsent: true,
    transitionStore: new CookieTransitionDataStore({
      serverCookieValue: cookies[UNIFORM_DEFAULT_COOKIE_NAME] ?? undefined,
    }),
  });

  await context.update({ cookies, url });

  const response = await fetch(url, {
    headers: { 'x-from-middleware': 'true' },
  });

  const handler = createUniformNesiResponseHandler();
  return handler({ response, context });
}

export const config = {
  matcher: [
    '/((?!_app|__data\\.json|@vite|@id|@fs|_next|.*\\..*|favicon\\.ico).*)',
  ],
};
```

---

## ISR (Incremental Static Regeneration)

For Vercel deployments, enable ISR to cache and revalidate pages:

```ts
// src/routes/[...path]/+page.server.ts
import { createVercelIsrConfig } from '@uniformdev/canvas-sveltekit';

export const config = createVercelIsrConfig({
  expiration: 60, // Revalidate every 60 seconds
});
```

And configure the Vercel adapter in `svelte.config.js`:

```js
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter(),
  },
};

export default config;
```

---

## Import reference

### @uniformdev/canvas-svelte

| Export | Description |
| --- | --- |
| `UniformComposition` | Renders a full Canvas composition tree |
| `UniformComponent` | Internal component renderer (for advanced usage) |
| `UniformSlot` | Renders child components from a named slot |
| `UniformText` | Renders text parameters with inline editing support |
| `UniformRichText` | Renders rich text parameters with formatting |
| `DefaultNotImplementedComponent` | Fallback component for unknown types |
| `getUniformCurrentComposition` | Get the current composition data from context |
| `getUniformCurrentComponent` | Get the current component data from context |
| `getUniformContextualEditing` | Set up custom contextual editing |
| `createUniformApiEnhancer` | Create an API-based composition enhancer |
| `getClientConditionsComposition` | Evaluate client-side visibility rules |
| `getClientVisibilityRules` | Get client-side visibility rule definitions |
| `convertComponentToProps` | Convert a `ComponentInstance` to flattened props |
| `getParameterAttributes` | Get inline editing data attributes |
| `createComponentResolver` | Create a resolver from a component map |
| `resolveComponent` | Resolve a component from a map or resolver function |
| `ComponentMap` | Type for the component mapping object |
| `ComponentProps` | Type for component props (flattened) |
| `RenderComponentResolver` | Type for the custom resolver function |

### @uniformdev/canvas-sveltekit

| Export | Description |
| --- | --- |
| `createUniformLoad` | Create a SvelteKit load function for fetching compositions |
| `createUniformHandle` | Create a SvelteKit handle hook for preview mode |
| `createPreviewHandler` | Create preview API endpoint handlers (GET, POST, OPTIONS) |
| `createVercelIsrConfig` | Create ISR configuration for Vercel deployments |
| `createRevalidateHandler` | Create an ISR revalidation endpoint handler |
| `createRouteFetcher` | Low-level route fetcher (used internally by `createUniformLoad`) |
| `createUniformEntries` | Create entry data for static generation |
| `resolvePathFromParams` | Helper to resolve path from SvelteKit route params |
| `logCompositionIssues` | Log composition validation issues |
| `logRouteResponse` | Log route resolution responses |
| `UniformPreviewData` | Type for preview cookie data |
| `UniformPageData` | Type for page data from the load function |
| `UNIFORM_PREVIEW_COOKIE_NAME` | Cookie name constant for preview state |

### @uniformdev/context-svelte

| Export | Description |
| --- | --- |
| `UniformContext` | Provider component for the Uniform Context engine |
| `Personalize` | Renders personalized content based on visitor scores |
| `PersonalizeEdge` | Edge-mode personalization renderer |
| `PersonalizeStandard` | Standard-mode personalization renderer |
| `Test` | Renders A/B test variations based on distribution |
| `TestEdge` | Edge-mode test renderer |
| `TestStandard` | Standard-mode test renderer |
| `Track` | Tracks behavior when content enters the viewport |
| `TrackFragment` | Tracks behavior on page load (no wrapper element) |
| `getUniformContext` | Get the Context instance from the nearest provider |
| `getScores` | Reactive access to visitor scores (Svelte 5 `$state`) |
| `getQuirks` | Reactive access to visitor quirks (Svelte 5 `$state`) |
| `isPersonalized` | Check if inside a Personalize component |

### @uniformdev/canvas

| Export | Description |
| --- | --- |
| `RouteClient` | Client for resolving routes and fetching compositions |
| `flattenValues` | Extract values from asset parameters |
| `EMPTY_COMPOSITION` | Empty composition constant (for playground pages) |
| `RootComponentInstance` | Type for composition root data |
| `ComponentInstance` | Type for individual component data |
| `CANVAS_DRAFT_STATE` | Constant for draft content state |
| `CANVAS_PUBLISHED_STATE` | Constant for published content state |

### @uniformdev/context

| Export | Description |
| --- | --- |
| `Context` | The core personalization and scoring engine |
| `CookieTransitionDataStore` | Store for persisting visitor data in cookies |
| `enableContextDevTools` | Plugin for enabling the Uniform DevTools browser extension |
| `enableDebugConsoleLogDrain` | Plugin for debug logging to console |
| `ManifestV2` | Type for the personalization manifest |
| `UNIFORM_DEFAULT_COOKIE_NAME` | Default cookie name for visitor data |

---

## Type definitions

### ComponentProps

```ts
type ComponentProps<TProps = unknown> = TProps & {
  component: ComponentInstance | RootComponentInstance;
};
```

### ComponentMap

```ts
type ComponentMap = Record<string, Component<ComponentProps<any>>>;
```

Keys can be:

- `"hero"` -- matches type `"hero"` with no variant
- `"hero__featured"` -- matches type `"hero"` with variant `"featured"` (double underscore)

### RenderComponentResolver

```ts
type RenderComponentResolver = (
  component: ComponentInstance
) => Component<ComponentProps<any>> | null;
```

### UniformContextComponentProps

```ts
type UniformContextComponentProps = {
  context: Context;
  outputType?: 'standard' | 'edge';
  trackRouteOnRender?: boolean;
  includeTransferState?: 'always' | 'never' | 'server-only';
};
```

### PersonalizeComponentProps

```ts
type PersonalizeComponentProps<TVariation> = {
  name: string;
  variations: TVariation[];
  component: Component<TVariation & { personalizationResult: PersonalizationResult }>;
  algorithm?: string;
  wrapperComponent?: Component<{ personalizationOccurred: boolean }>;
  count?: number;
  compositionMetadata?: CompositionMetadata;
};
```

### TestComponentProps

```ts
type TestComponentProps<TVariation> = {
  name: string;
  variations: TVariation[];
  component: Component<TVariation>;
  loadingMode?: 'default' | 'none' | Component;
  compositionMetadata?: CompositionMetadata;
};
```

---

## Recipes

### Recipe: Adding Uniform to an existing SvelteKit project

1. Install packages:

```bash
npm install @uniformdev/canvas @uniformdev/canvas-svelte @uniformdev/canvas-sveltekit @uniformdev/context @uniformdev/context-svelte
npm install -D @uniformdev/cli
```

2. Add environment variables to `.env`:

```bash
UNIFORM_API_KEY=your-api-key
UNIFORM_PROJECT_ID=your-project-id
UNIFORM_PREVIEW_SECRET=your-preview-secret
```

3. Update `src/app.d.ts`:

```ts
import type { UniformPreviewData } from '@uniformdev/canvas-sveltekit';

declare global {
  namespace App {
    interface Locals {
      uniformPreview?: UniformPreviewData;
    }
  }
}

export {};
```

4. Download the personalization manifest:

```bash
npx uniform context manifest download --output ./src/lib/uniform/contextManifest.json
```

5. Create the component map (`src/lib/uniform/componentMap.ts`):

```ts
import type { ComponentMap } from '@uniformdev/canvas-svelte';

export const componentMap: ComponentMap = {
  // Map your component types here
};
```

6. Create the root layout (`src/routes/+layout.svelte`) with `UniformContext` (see [Step 2](#step-2-root-layout-with-uniformcontext)).
7. Create the catch-all route (`src/routes/[...path]/+page.server.ts` and `+page.svelte`) (see [Step 4](#step-4-catch-all-route)).
8. Create the preview handler (`src/routes/preview/+server.ts`) (see [Step 6](#step-6-preview-handler)).
9. Create the playground page (`src/routes/playground/+page.svelte`) (see [Step 7](#step-7-playground-page)).
10. Create server hooks (`src/hooks.server.ts`) (see [Step 3](#step-3-server-hooks)).
11. Add scripts to `package.json`:

```json
{
  "scripts": {
    "dev": "npm run pull:manifest && vite dev",
    "build": "npm run pull:manifest && vite build",
    "pull:manifest": "uniform context manifest download --output ./src/lib/uniform/contextManifest.json"
  }
}
```

### Recipe: Building a component with text, rich text, and slots

```svelte
<!-- src/lib/components/Article.svelte -->
<script lang="ts">
  import { UniformText, UniformRichText, UniformSlot } from '@uniformdev/canvas-svelte';
  import type { ComponentProps } from '@uniformdev/canvas-svelte';

  interface Props extends ComponentProps<{
    title?: string;
    subtitle?: string;
  }> {}

  let { title, subtitle, component }: Props = $props();
</script>

<article>
  <UniformText parameterId="title" as="h1" placeholder="Article title" />
  <UniformText parameterId="subtitle" as="p" placeholder="Article subtitle" />

  <div class="grid">
    <div class="main">
      <UniformRichText parameterId="body" as="div" placeholder="Write your article" />
    </div>
    <aside>
      <UniformSlot name="sidebar" />
    </aside>
  </div>

  <section>
    <h2>Related</h2>
    <UniformSlot name="relatedContent" />
  </section>
</article>
```

### Recipe: Component with quirk-based personalization

```svelte
<!-- src/lib/components/PersonalizedGreeting.svelte -->
<script lang="ts">
  import { getQuirks, getScores } from '@uniformdev/context-svelte';

  const quirks = getQuirks();
  const scores = getScores();

  const topInterest = $derived(() => {
    const entries = Object.entries(scores.current);
    if (entries.length === 0) return null;
    return entries.sort(([, a], [, b]) => b - a)[0]?.[0];
  });
</script>

<div>
  {#if quirks.current?.country}
    <p>Welcome from {quirks.current.country}!</p>
  {/if}
  {#if topInterest()}
    <p>We see you're interested in {topInterest()}.</p>
  {/if}
</div>
```

### Recipe: Forget visitor data (reset personalization)

```svelte
<!-- src/lib/components/ForgetMe.svelte -->
<script lang="ts">
  import { getUniformContext } from '@uniformdev/context-svelte';

  const { context } = getUniformContext();

  const forgetMe = async () => {
    await context.forget(true);
  };
</script>

<button onclick={forgetMe}>Forget me</button>
```

### Recipe: DevTools integration

Enable the Uniform Context DevTools browser extension for debugging:

```svelte
<script lang="ts">
  import {
    Context,
    CookieTransitionDataStore,
    enableContextDevTools,
    enableDebugConsoleLogDrain,
  } from '@uniformdev/context';

  const context = new Context({
    manifest,
    defaultConsent: true,
    transitionStore: new CookieTransitionDataStore({
      serverCookieValue: undefined,
    }),
    plugins: [
      enableContextDevTools(),
      enableDebugConsoleLogDrain('debug'),
    ],
  });
</script>
```

The DevTools extension lets you inspect and modify visitor scores and quirks in real time during development.

---

## Svelte vs SvelteKit

The Uniform Svelte SDK is split between framework-agnostic Svelte packages and SvelteKit-specific packages:

| Concern | Svelte package | SvelteKit package |
| --- | --- | --- |
| Composition rendering | `@uniformdev/canvas-svelte` | -- |
| Components (`UniformText`, `UniformSlot`, etc.) | `@uniformdev/canvas-svelte` | -- |
| Context provider | `@uniformdev/context-svelte` | -- |
| Reactive helpers (`getScores`, `getQuirks`) | `@uniformdev/context-svelte` | -- |
| Route loading (`createUniformLoad`) | -- | `@uniformdev/canvas-sveltekit` |
| Server hooks (`createUniformHandle`) | -- | `@uniformdev/canvas-sveltekit` |
| Preview handler | -- | `@uniformdev/canvas-sveltekit` |
| ISR configuration | -- | `@uniformdev/canvas-sveltekit` |
| Edge personalization (NESI) | -- | `@uniformdev/context-edge-sveltekit` |

If you are using SvelteKit (recommended), install all packages. The Svelte-only packages (`canvas-svelte`, `context-svelte`) can also be used in non-SvelteKit Svelte setups, but you will need to handle routing and data fetching yourself.

---

## Best practices

1. **Use TypeScript** -- The SDK is TypeScript-first. Define `Props` interfaces using `ComponentProps<T>` for all components with proper types for each parameter.
2. **Use `componentMap` over `resolveRenderer`** -- The declarative component map is simpler, more readable, and easier to maintain than a resolver function. Reserve `resolveRenderer` for cases where you need dynamic resolution logic.
3. **Download manifest before build** -- Use `npm run pull:manifest && vite build` to ensure the manifest is always up-to-date. Never commit a stale manifest.
4. **Run `publish:manifest` after changes** -- Always run `uniform context manifest publish` after modifying signals, enrichments, or quirks. The manifest must be re-downloaded after publishing.
5. **Use `flattenValues` for assets** -- Always use `flattenValues` from `@uniformdev/canvas` for asset parameters rather than manually traversing the parameter structure.
6. **Access `component` prop for metadata** -- When you need raw parameter data, component IDs, variant info, or slot information, use the `component` prop.
7. **Use `getScores()` and `getQuirks()` for reactive data** -- Accessing `context.scores` directly does not trigger re-renders. Always use the reactive helpers for values that affect the UI.
8. **Keep the preview handler in place** -- The preview API endpoint and server hooks are required for visual editing in the Uniform Canvas editor. Do not remove them.
9. **Prefer `standard` output for development** -- Use `outputType: 'standard'` during development for easier debugging. Switch to `'edge'` only in production with a configured edge middleware.
10. **Use the `type__variant` naming convention** -- For variant-specific component mappings, use double underscore (`hero__dark`) in the component map rather than creating separate resolver logic.

---

## Troubleshooting

### Common issues

1. **"Cannot find module contextManifest.json"**

   - Run `npm run pull:manifest` to download the manifest.
   - Ensure `UNIFORM_API_KEY` and `UNIFORM_PROJECT_ID` are set correctly.
2. **Preview not working**

   - Verify the preview URL is configured in Uniform Canvas settings.
   - Check `UNIFORM_PREVIEW_SECRET` matches in both the `.env` file and Uniform dashboard.
   - See the [troubleshoot preview guide](https://docs.uniform.app/docs/guides/composition/visual-editing/troubleshoot-preview) for more help.
3. **Components not rendering**

   - Verify component type names in `componentMap` match the types defined in Uniform Canvas.
   - Check browser console for mapping errors.
   - Make sure the component is exported and imported correctly.
4. **TypeScript errors with `$props()`**

   - Ensure you are using Svelte 5. The SDK uses Svelte 5's `$props()` rune for component props.
   - Use `interface Props extends ComponentProps<T> {}` pattern for type-safe props.
5. **Edge personalization not working**

   - Ensure `outputType` is set to `'edge'` in production.
   - Verify the edge middleware is deployed and running.
   - Check that the NESI packages (`@uniformdev/context-edge`, `@uniformdev/context-edge-sveltekit`) are installed.
