Svelte / SvelteKit SDK

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 -- Minimal starter with composition rendering, component mapping, and visual editing.
  • SvelteKit Demo -- Full-featured demo with personalization, A/B testing, and edge middleware.

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

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.


  • SvelteKit 2+ (Svelte 5)
  • Node.js 18+
  • A Uniform project with compositions set up
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:

npm install -D @uniformdev/cli
PackagePurpose
@uniformdev/canvasCore Canvas API client, types, and utilities
@uniformdev/canvas-svelteSvelte components: UniformComposition, UniformSlot, UniformText, UniformRichText
@uniformdev/canvas-sveltekitSvelteKit integrations: route loading, server hooks, preview handler, ISR
@uniformdev/contextCore Context personalization engine
@uniformdev/context-svelteUniformContext provider and reactive helpers (getScores, getQuirks)
@uniformdev/cliCLI for pulling manifests and syncing content

Create a .env file in your project root:

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 Settings > API Keys. See the API keys guide for more information.


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.

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

// src/app.d.ts import type { UniformPreviewData } from '@uniformdev/canvas-sveltekit'; declare global { namespace App { interface Locals { uniformPreview?: UniformPreviewData; } } } export {};

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

<!-- 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>
PropTypeDefaultDescription
contextContext--Required. A configured Uniform Context instance
outputType'standard' | 'edge''standard'How personalization variants are rendered
trackRouteOnRenderbooleantrueWhether 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.

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

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

Create the route structure to serve Uniform compositions.

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

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

<!-- 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>
OptionTypeDefaultDescription
clientRouteClient--Required. A configured RouteClient instance
paramstring'path'The route parameter name (matches [...path])
projectMapIdstringundefinedOptional project map ID (uses default if not set)
silentbooleanfalseSuppress console logging
requestOptionsobjectundefinedCustomize API request options
handleCompositionfunctionbuilt-inCustom handler for composition responses
handleRedirectfunctionbuilt-inCustom handler for redirect responses
handleNotFoundfunctionbuilt-inCustom handler for 404 responses

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

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

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

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

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

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


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.

Every component rendered via the componentMap receives these props:

PropTypeDescription
componentComponentInstanceThe full component instance with raw parameters, slots, variant, and metadata
[parameterName]flattened valueEach parameter's .value is spread as a top-level prop

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

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

Use UniformSlot to render child components placed in named slots:

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

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

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

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

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

<UniformText parameterId="title" as="h1" placeholder="Enter title" />
PropTypeDefaultDescription
parameterIdstring--Required. The ID of the parameter to render
asstring'span'The HTML element to render
placeholderstring | ((props: { id: string }) => string)undefinedPlaceholder text shown in the Canvas editor when empty
isMultilinebooleanfalseWhen true, adds white-space: pre-wrap for line break support

UniformText supports a default snippet slot for custom value rendering:

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

UniformRichText renders rich text parameters with full formatting support:

<UniformRichText parameterId="description" as="div" placeholder="Enter description" />
PropTypeDefaultDescription
parameterIdstring--Required. The ID of the rich text parameter
asstringundefinedOptional wrapper element
placeholderstring | ((props: { id: string }) => string)undefinedPlaceholder text when empty
resolveRichTextRendererRenderRichTextComponentResolverundefinedCustom function to override default rich text node renderers

Override how specific rich text node types are rendered:

<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 renders child components placed in a named slot:

<UniformSlot name="content" />

When name is omitted, UniformSlot renders all slots combined.

PropTypeDefaultDescription
namestringundefinedThe slot name to render. If omitted, all slots are rendered
resolveRendererRenderComponentResolverinheritedOverride the component resolver for this slot's children
wrapperComponentComponentundefinedA Svelte component that wraps the slot's children

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

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

UniformComposition is the entry point for rendering a composition tree:

<UniformComposition data={compositionData} {componentMap}> <UniformSlot name="content" /> </UniformComposition>
PropTypeRequiredDescription
dataRootComponentInstanceYesThe composition data from the Uniform API
componentMapComponentMapYes*Object mapping component types to Svelte components
resolveRendererRenderComponentResolverNoCustom resolver function (alternative to componentMap)
matchedRoutestringNoThe matched route path
dynamicInputsRecord<string, string>NoDynamic input values from route parameters
behaviorTracking'onLoad' | 'onView'NoWhen to track enrichment behavior
contextualEditingEnhancerfunctionNoCustom enhancer for contextual editing updates

note

Either componentMap or resolveRenderer must be provided. componentMap is the recommended approach for most projects.


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

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

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

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

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

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

<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 / PropertyDescription
context.scoresCurrent visitor score values (not reactive -- use getScores())
context.quirksCurrent 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.

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

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

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

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

Update visitor quirks to trigger personalization changes:

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

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

For custom contextual editing setups, use getUniformContextualEditing directly:

<script lang="ts"> import { getUniformContextualEditing } from '@uniformdev/canvas-svelte'; const { composition, isContextualEditing } = getUniformContextualEditing({ initialCompositionValue: compositionData, enhance: (message) => message.composition, }); </script>
Return ValueTypeDescription
compositionreactive RootComponentInstanceThe current composition (updates during editing)
isContextualEditingreactive booleanWhether the app is in contextual editing mode

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:

{ "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" } }
ScriptDescription
pull:manifestDownloads the personalization manifest to a local JSON file
pull:contentPulls content definitions from your Uniform project
push:contentPushes local content changes to your Uniform project
publish:manifestPublishes the manifest (required after changing signals, enrichments, or quirks)

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

{ "project": {} }

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


Create uniform.config.ts at your project root:

// uniform.config.ts import { uniformConfig } from '@uniformdev/cli/config'; module.exports = uniformConfig({ preset: 'all', disableEntities: ['webhook'], });

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.

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

Edge personalization requires two additional packages:

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

This is already configured if you followed the setup guide:

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

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

// 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).*)', ], };

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

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

// svelte.config.js import adapter from '@sveltejs/adapter-vercel'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { adapter: adapter(), }, }; export default config;

ExportDescription
UniformCompositionRenders a full Canvas composition tree
UniformComponentInternal component renderer (for advanced usage)
UniformSlotRenders child components from a named slot
UniformTextRenders text parameters with inline editing support
UniformRichTextRenders rich text parameters with formatting
DefaultNotImplementedComponentFallback component for unknown types
getUniformCurrentCompositionGet the current composition data from context
getUniformCurrentComponentGet the current component data from context
getUniformContextualEditingSet up custom contextual editing
createUniformApiEnhancerCreate an API-based composition enhancer
getClientConditionsCompositionEvaluate client-side visibility rules
getClientVisibilityRulesGet client-side visibility rule definitions
convertComponentToPropsConvert a ComponentInstance to flattened props
getParameterAttributesGet inline editing data attributes
createComponentResolverCreate a resolver from a component map
resolveComponentResolve a component from a map or resolver function
ComponentMapType for the component mapping object
ComponentPropsType for component props (flattened)
RenderComponentResolverType for the custom resolver function
ExportDescription
createUniformLoadCreate a SvelteKit load function for fetching compositions
createUniformHandleCreate a SvelteKit handle hook for preview mode
createPreviewHandlerCreate preview API endpoint handlers (GET, POST, OPTIONS)
createVercelIsrConfigCreate ISR configuration for Vercel deployments
createRevalidateHandlerCreate an ISR revalidation endpoint handler
createRouteFetcherLow-level route fetcher (used internally by createUniformLoad)
createUniformEntriesCreate entry data for static generation
resolvePathFromParamsHelper to resolve path from SvelteKit route params
logCompositionIssuesLog composition validation issues
logRouteResponseLog route resolution responses
UniformPreviewDataType for preview cookie data
UniformPageDataType for page data from the load function
UNIFORM_PREVIEW_COOKIE_NAMECookie name constant for preview state
ExportDescription
UniformContextProvider component for the Uniform Context engine
PersonalizeRenders personalized content based on visitor scores
PersonalizeEdgeEdge-mode personalization renderer
PersonalizeStandardStandard-mode personalization renderer
TestRenders A/B test variations based on distribution
TestEdgeEdge-mode test renderer
TestStandardStandard-mode test renderer
TrackTracks behavior when content enters the viewport
TrackFragmentTracks behavior on page load (no wrapper element)
getUniformContextGet the Context instance from the nearest provider
getScoresReactive access to visitor scores (Svelte 5 $state)
getQuirksReactive access to visitor quirks (Svelte 5 $state)
isPersonalizedCheck if inside a Personalize component
ExportDescription
RouteClientClient for resolving routes and fetching compositions
flattenValuesExtract values from asset parameters
EMPTY_COMPOSITIONEmpty composition constant (for playground pages)
RootComponentInstanceType for composition root data
ComponentInstanceType for individual component data
CANVAS_DRAFT_STATEConstant for draft content state
CANVAS_PUBLISHED_STATEConstant for published content state
ExportDescription
ContextThe core personalization and scoring engine
CookieTransitionDataStoreStore for persisting visitor data in cookies
enableContextDevToolsPlugin for enabling the Uniform DevTools browser extension
enableDebugConsoleLogDrainPlugin for debug logging to console
ManifestV2Type for the personalization manifest
UNIFORM_DEFAULT_COOKIE_NAMEDefault cookie name for visitor data

type ComponentProps<TProps = unknown> = TProps & { component: ComponentInstance | RootComponentInstance; };
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)
type RenderComponentResolver = ( component: ComponentInstance ) => Component<ComponentProps<any>> | null;
type UniformContextComponentProps = { context: Context; outputType?: 'standard' | 'edge'; trackRouteOnRender?: boolean; includeTransferState?: 'always' | 'never' | 'server-only'; };
type PersonalizeComponentProps<TVariation> = { name: string; variations: TVariation[]; component: Component<TVariation & { personalizationResult: PersonalizationResult }>; algorithm?: string; wrapperComponent?: Component<{ personalizationOccurred: boolean }>; count?: number; compositionMetadata?: CompositionMetadata; };
type TestComponentProps<TVariation> = { name: string; variations: TVariation[]; component: Component<TVariation>; loadingMode?: 'default' | 'none' | Component; compositionMetadata?: CompositionMetadata; };

  1. Install packages:
npm install @uniformdev/canvas @uniformdev/canvas-svelte @uniformdev/canvas-sveltekit @uniformdev/context @uniformdev/context-svelte npm install -D @uniformdev/cli
  1. Add environment variables to .env:
UNIFORM_API_KEY=your-api-key UNIFORM_PROJECT_ID=your-project-id UNIFORM_PREVIEW_SECRET=your-preview-secret
  1. Update src/app.d.ts:
import type { UniformPreviewData } from '@uniformdev/canvas-sveltekit'; declare global { namespace App { interface Locals { uniformPreview?: UniformPreviewData; } } } export {};
  1. Download the personalization manifest:
npx uniform context manifest download --output ./src/lib/uniform/contextManifest.json
  1. Create the component map (src/lib/uniform/componentMap.ts):
import type { ComponentMap } from '@uniformdev/canvas-svelte'; export const componentMap: ComponentMap = { // Map your component types here };
  1. Create the root layout (src/routes/+layout.svelte) with UniformContext (see Step 2).

  2. Create the catch-all route (src/routes/[...path]/+page.server.ts and +page.svelte) (see Step 4).

  3. Create the preview handler (src/routes/preview/+server.ts) (see Step 6).

  4. Create the playground page (src/routes/playground/+page.svelte) (see Step 7).

  5. Create server hooks (src/hooks.server.ts) (see Step 3).

  6. Add scripts to package.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" } }
<!-- 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>
<!-- 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>
<!-- 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>

Enable the Uniform Context DevTools browser extension for debugging:

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


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

ConcernSvelte packageSvelteKit 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.


  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.


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