@contentful/experiences-svelte
⚠️ Alpha. Published to npm. APIs are unstable and will change.
The Svelte adapter for the Contentful Experiences SDK. You bring your own Svelte 5 components; it renders Experience payloads from the Experience Delivery API (XDA) with them.
npm install @contentful/experiences-svelte
This is the only rendering SDK package you install. It re-exports everything you need from @contentful/experiences-sdk-core, @contentful/experiences-design, and @contentful/experiences-client. The optional @contentful/experiences-live-preview package is also customer-facing and provides the framework-neutral live-preview client.
The public API mirrors @contentful/experiences-react 1:1 in shape; only the rendering primitives change (Svelte 5 Components, Snippets instead of children: ReactNode, and getDesignValues() instead of useDesignValues()).
Public API
Authoring
defineComponent<Props>(config); // Type-narrowing identity for component-type configs
defineExperienceTemplate<Props>(config); // Same shape, for coded Experience Template configs
Fetching
fetchExperience(experienceOptions, clientOptions, resolveOptions); // Async; fetches by Destination path/node id or Experience id and resolves in one call
fetchDestinationSitemap(destinationOptions, clientOptions, paginationOptions?); // Async; lists every routable path in a Destination
fetchPreviewSession(previewSessionOptions, clientOptions, resolveOptions); // Async; fetches and resolves a Preview Session Experience
Serve pages from a Destination: the URL path is the Destination path. A redirect node comes back as a value, not an error. Preview is not supported on Destinations (it throws DestinationPreviewNotSupportedError); use the by-id form for preview.
const result = await fetchExperience(
{ spaceId, destinationId, path: '/demo/landing' },
{ accessToken },
{ config: experienceConfig }
);
if ('redirect' in result) {
// follow it: navigate to result.redirect.path
}
// otherwise `result` is a PortableRenderPlan
// Enumerate every routable path, for example to prerender
const sitemap = await fetchDestinationSitemap({ spaceId, destinationId }, { accessToken });
const paths = sitemap.items.map((item) => item.path); // follow sitemap.pages.next for more
Use fetchPreviewSession for the initial render when the Contentful app
provides a preview_session_id. It fetches the current Preview Session
snapshot and resolves it into a PortableRenderPlan. Use useLivePreview to
receive subsequent WebSocket updates:
const plan = await fetchPreviewSession(
{ spaceId, environmentId, sessionId },
{ previewToken },
{ config: experienceConfig }
);
If the Preview Session references resources from other spaces, pass the same
encoded resourceResolution value in the previewSessionOptions passed to
fetchPreviewSession and useLivePreview.
Resolver
resolveExperience(payload, config, opts?); // Async; walks payload, runs resolveData, returns a PortableRenderPlan
Live preview
Use useLivePreview when the app should subscribe to Preview Session updates,
resolve each payload, and render the resulting plan from data:
<script lang="ts">
const livePreview = useLivePreview(() => ({
previewSessionOptions,
initialPayload,
initialPlan,
resolveOptions: { config: experienceConfig },
}));
</script>
<ClientExperienceRenderer experience={livePreview.data} config={experienceConfig} />
For separate access to the raw payload and rendered plan, use
useLivePreviewExperience and useExperiencePlan:
<script lang="ts">
const livePreview = useLivePreviewExperience(() => ({
previewSessionOptions: {
spaceId,
environmentId,
previewToken,
sessionId,
},
initialPayload,
}));
const plan = useExperiencePlan(() => ({
payload: livePreview.data,
initialPlan,
resolveOptions: { config: experienceConfig },
}));
</script>
<ClientExperienceRenderer experience={plan.data} config={experienceConfig} />
useLivePreviewExperience returns the latest raw Experience payload.
initialPayload seeds the first value. useExperiencePlan turns that payload
into the PortableRenderPlan consumed by the renderer and keeps the current rendered
experience while an update is being resolved.
The live-preview results also expose error when the Preview Session connection
fails. The last valid data remains available.
Renderers
ServerExperienceRenderer; // SSR-safe renderer
ClientExperienceRenderer; // Client renderer
MissingComponent; // Default fallback for unregistered component types
NodesRenderer; // Exposed so you can re-render a slot's raw nodes yourself (see Slot children)
Styling + runtime context (helpers)
getDesignValues<T>(); // Escape hatch: the same resolved design record that auto-fills props; read in a $derived to stay reactive
toCss(design, options?); // Turns a design record into a plain style object, keeping only real CSS keys
getExperience(); // RenderContext: debug, metadata
getContentfulComponent(); // Raw payload for the enclosing node (or undefined), including `attribution`
getContentfulExperienceTemplate(); // Same, for an enclosing coded Experience Template node
getTrackingAttributes(attribution); // Attributes to spread on a node's outermost element for interaction tracking
type ToCssOptions;
Resolved design values are auto-filled onto your component's props by key, alongside content. Styling from $props() is the one recommended path. getDesignValues() exposes the same record as an escape hatch. Reach for it only for a nested child that isn't itself a registered component, or for design needed outside the render path (an effect, an imperative measurement) — see Styling components. Token resolution is configured with resolveToken on your Config (type ResolveToken).
Re-exported types and utilities
// From core
type Config, Components, ExperienceTemplates, Registration, ExperienceTemplateRegistration,
type ComponentConfig, ExperienceTemplateConfig,
type ContentfulComponent, ContentfulExperienceTemplate,
type RenderContext, ResolveToken,
type ExperiencePayload, ExperienceNode, ComponentNode, ExperienceTemplateNode,
type PortableRenderPlan, PortableRenderNode, PortableRegistration,
type DesignPropValue, ManualDesignValue, DesignToken, ExperienceContext, ResolveContext,
type ResolverConfig, ResolveExperienceOptions
// From design
isCssProperty, toCssKey, CSS_PROPERTIES
// From client
fetchExperience, fetchDestinationSitemap, fetchPreviewSession, createClient, ContentfulViewDelivery,
ContentfulViewDeliveryClient, NotFoundError, DestinationPreviewNotSupportedError, PreviewSessionFetchError,
LivePreviewConnectionError,
DELIVERY_HOST, PREVIEW_HOST, PREVIEW_WEBSOCKET_HOST
type ExperienceOptions, ByIdExperienceOptions, ByDestinationPathExperienceOptions,
ByDestinationNodeIdExperienceOptions, DestinationRedirectResult, DestinationSitemapResult,
FetchDestinationSitemapOptions, FetchDestinationSitemapPaginationOptions, SitemapPathItem,
PreviewSessionExperienceOptions, PreviewSessionClientOptions,
PreviewSessionResolveOptions, ClientOptions, ResolveOptions, CreateClientOptions
Quick reference
<!-- Button.svelte: content + resolved design both arrive as props -->
<script lang="ts">
interface ButtonProps {
label?: string;
url?: string;
backgroundColor?: string; // resolved design, auto-filled
color?: string;
}
let { label = 'Button', url, backgroundColor, color }: ButtonProps = $props();
</script>
{#if url}
<a href={url} style="background: {backgroundColor}; color: {color};">{label}</a>
{:else}
<button type="button" style="background: {backgroundColor}; color: {color};">{label}</button>
{/if}
// experience-config.ts
import {
defineComponent,
type Components,
type Config,
type ResolveToken,
} from '@contentful/experiences-svelte';
import Button from './components/Button.svelte';
import type { ButtonProps } from './components/Button.svelte';
const components: Components = {
// Bare component, or defineComponent({...}) when you need defaults/resolveData.
Button: defineComponent<ButtonProps>({
resolveData: ({ content }) => ({ url: ensureScheme(content.url) }),
component: Button,
}),
};
const resolveToken: ResolveToken = (token) => designTokens[token.value];
export const experienceConfig: Config = { components, resolveToken };
<!-- +page.svelte -->
<script lang="ts">
import { ServerExperienceRenderer } from '@contentful/experiences-svelte';
let { data } = $props();
</script>
<ServerExperienceRenderer experience={data.experience} config={experienceConfig} />
Slot children
Every slot arrives as a prop named after the slot, holding an array of Snippets (Snippet[]), one per child — not a single wrapping Snippet. For the common "just render them" case, iterate the array with {#each} and {@render}; to wrap, reorder, or drop children individually, do it inside that loop.
<!-- Section.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte';
let { children }: { children?: Snippet[] } = $props();
</script>
<!-- Common case — render them all: -->
<div>
{#each children ?? [] as child}
{@render child()}
{/each}
</div>
<!-- Or take control of each child: -->
<!--
<div>
{#each children ?? [] as child, i}
<div class="cell">{@render child()}</div>
{/each}
</div>
-->
children is not special — it is simply the conventional name for the default slot. Every slot in the payload becomes a same-named Snippet[] prop, so a component with a header slot just declares header?: Snippet[] and renders it the same way. This applies identically to coded Experience Templates: a template with a content slot receives a content prop.
If you'd rather render a slot's raw nodes yourself, they are still on the payload at getContentfulComponent().slots (a Record<string, PortableRenderNode[]>) — hand them to NodesRenderer.
For the full getting-started walkthrough, merge-precedence rules, and design rationale, see the root README and AGENTS.md.
License
MIT. See the repository LICENSE and NOTICE for full attribution.
Runtime Experience parameters
By-ID fetchExperience accepts parameters, for example
{ featuredPosts: { limit: 6, order: '-fields.title' } }. Scalars are strings or
numbers; Record leaves have one level. Order is one signed fields.<apiName> or
sys.<id> path. Refetch with changed values, then replace the renderer's plan.
Exports include ExperienceParameters, ExperienceParameterValue,
ExperienceParameterRecord, and ExperienceParameterOrder.
See the collection/refetch example.