> This page is for Experience Orchestration.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://contentful.com/developers/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://contentful.com/developers/docs/_mcp/server.

# Register components

> Map the component and experience template IDs authored in Contentful to your own components.

> **Info**
>
> Experiences SDK is currently available in alpha. APIs are unstable and subject to change.

The SDK renders an experience with your own components. You register each one under the ID it has in Contentful, in a configuration object that you pass to `fetchExperience` and to the renderer.

## Create the configuration

The configuration has two registries and an optional token resolver:

* **`components`**: Components, keyed by component ID.
* **`experienceTemplates`**: Coded experience templates, keyed by experience template ID. Optional.
* **`resolveToken`**: Resolves design tokens to values. See [Style components](/developers/docs/experience-orchestration/experiences-sdk/style-components#resolve-design-tokens). Optional.

#### React

```tsx
// lib/experience-config.tsx
import {
  defineComponent,
  type Components,
  type Config,
  type ExperienceTemplates,
} from '@contentful/experiences-react';

import { Button } from './components/Button';
import { Heading, type HeadingProps } from './components/Heading';
import { Page } from './components/Page';

const components: Components = {
  Button,
  Heading: defineComponent<HeadingProps>({
    defaults: { text: 'Untitled' },
    component: Heading,
  }),
};

const experienceTemplates: ExperienceTemplates = {
  page: Page,
};

export const experienceConfig: Config = { components, experienceTemplates };
```

#### Svelte

```ts
// lib/experience-config.ts
import { defineComponent, type Components, type Config } from '@contentful/experiences-svelte';

import Button from './components/Button.svelte';
import Heading, { type HeadingProps } from './components/Heading.svelte';

const components: Components = {
  Button,
  Heading: defineComponent<HeadingProps>({ defaults: { text: 'Untitled' }, component: Heading }),
};

export const experienceConfig: Config = { components };
```

#### Angular

```ts
// lib/experience-config.ts
import { defineComponent, type Components, type Config } from '@contentful/experiences-angular';

import { ButtonComponent } from './components/button.component';
import { HeadingComponent, type HeadingProps } from './components/heading.component';

const components: Components = {
  Button: ButtonComponent,
  Heading: defineComponent<HeadingProps>({
    defaults: { text: 'Untitled' },
    component: HeadingComponent,
  }),
};

export const experienceConfig: Config = { components };
```

Keep the configuration in one file. Your components stay plain framework components with no SDK imports, so they work in Storybook, in tests, and in other apps. The configuration is the only place that knows about Contentful.

## Choose registry keys

The registry key is the last segment of the component's URN in Contentful. For `crn:contentful:::experience:spaces/$self/environments/$self/components/Button`, the key is `Button`. Experience template keys come from the experience template node's URN the same way.

To find the IDs an experience uses, render it with debug mode on. Each unregistered component renders a box that names its ID. See [Errors and debugging](/developers/docs/experience-orchestration/experiences-sdk/errors-and-debugging#debug-mode).

A component registered under an ID never satisfies an experience template with the same ID: each registry only resolves its own node type.

## Register a component

Register a bare component for the common case. Use `defineComponent` when the component needs default props or data loaded before rendering:

| Field         | Description                                                                             |
| ------------- | --------------------------------------------------------------------------------------- |
| `component`   | The component to render. Required.                                                      |
| `defaults`    | Props used when the experience doesn't supply a value. They have the lowest precedence. |
| `resolveData` | A sync or async function that derives props before the experience renders.              |

`defineComponent<Props>` narrows `defaults`, `resolveData`, and `component` to your component's prop type. It returns the configuration unchanged.

### Load data with resolveData

`resolveData` runs once per fetch, inside `fetchExperience` or `resolveExperience`, before the experience renders. Its return value is merged into the component's props. It receives:

* **`content`**: The node's content properties, as delivered.
* **`design`**: The node's design properties, as raw design values (`{ type, value }`).
* **`experience`**: The render context: `debug` and the `metadata` passed to `fetchExperience`.

```tsx
card: defineComponent<CardProps>({
  resolveData: async ({ content, experience }) => {
    const { badge } = await fetchCardEnrichment(content.title as string);
    const locale = (experience.metadata.locale as string) ?? 'en-US';
    return { badge, ctaUrl: `/${locale}${content.ctaUrl as string}` };
  },
  component: Card,
}),
```

The SDK runs every `resolveData` function in parallel, so a slow one doesn't block the others. A function that throws or rejects is reported as a diagnostic, and that component renders without the resolved data.

`resolveData` doesn't run again when the page re-renders or on client-side navigation. To load new data, fetch the experience again.

## Merge order

A component's props are merged in this order, last wins:

1. `defaults` from `defineComponent`
2. Design properties, with design tokens resolved
3. Content properties
4. The output of `resolveData`
5. Slots

A content value always wins over a default or a design value with the same name. A default only applies when the experience doesn't supply that property.

## Render slots

Each slot arrives as a prop named after the slot, holding one entry per child node. `children` is the conventional name for the default slot but isn't special: a component with a `header` slot receives a `header` prop.

#### React

A slot is a `ReactNode[]`. Render the array directly, or map over it to wrap or reorder children:

```tsx
// components/Section.tsx
import type { ReactNode } from 'react';

export function Section({ children }: { children?: ReactNode[] }) {
  return <div className="section">{children}</div>;
}
```

#### Svelte

A slot is a `Snippet[]`. Render each snippet with `{@render}`:

```svelte
<!-- Section.svelte -->
<script lang="ts">
  import type { Snippet } from 'svelte';

  let { children }: { children?: Snippet[] } = $props();
</script>

<div class="section">
  {#each children ?? [] as child}
    {@render child()}
  {/each}
</div>
```

#### Angular

A slot is an input holding the child nodes (`SlotNodes`). Render them with the `*cfNodes` directive, or loop over them and render each with `*cfNode`:

```ts
// section.component.ts
import { Component, Input, signal } from '@angular/core';
import { NodesRenderer, type SlotNodes } from '@contentful/experiences-angular';

@Component({
  selector: 'app-section',
  imports: [NodesRenderer],
  template: `<div class="section"><ng-container *cfNodes="nodes()"></ng-container></div>`,
})
export class SectionComponent {
  protected readonly nodes = signal<SlotNodes | undefined>(undefined);

  @Input() set children(value: SlotNodes | undefined) {
    this.nodes.set(value);
  }
}
```

A slot your component never renders is never instantiated.

## Register experience templates

A coded experience template renders the page around an experience's content. Register it in `experienceTemplates`, with a bare component or `defineExperienceTemplate`, which takes the same fields as `defineComponent`. Its slots arrive as props, like a component's: a template with a `content` slot receives a `content` prop.

```tsx
// components/Page.tsx
import type { ReactNode } from 'react';

export function Page({ content }: { content?: ReactNode[] }) {
  return <main>{content}</main>;
}
```

An experience whose template was composed in the Contentful editor has no template node: its components render at the root.

## Read the raw node payload

Components receive merged props. To read a node's original payload, call the component payload accessor from inside the component:

| Field         | Description                                                                                                                                                                                                                                     |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `componentId` | The component ID. Experience templates expose `experienceTemplateId` instead.                                                                                                                                                                   |
| `nodeId`      | The node's ID from the payload, when it has one. The SDK never generates IDs.                                                                                                                                                                   |
| `content`     | Content properties, as delivered.                                                                                                                                                                                                               |
| `design`      | Design properties, as raw design values.                                                                                                                                                                                                        |
| `resolved`    | The output of `resolveData`, if the component has one.                                                                                                                                                                                          |
| `attribution` | The experiences and experience fragments the node belongs to. Present only when the fetch requests the content source map. See [Fetch experiences](/developers/docs/experience-orchestration/experiences-sdk/fetch-experiences#request-the-content-source-map). |

The accessor is `useContentfulComponent()` in React, `getContentfulComponent()` in Svelte, and `injectContentfulComponent()` in Angular. The experience template equivalents are `useContentfulExperienceTemplate()`, `getContentfulExperienceTemplate()`, and `injectContentfulExperienceTemplate()`. Outside a node of that kind, React returns `null` and Svelte returns `undefined`. Angular's accessors return a `Signal`, whose value is `undefined` outside a node.

Use the payload for a generic wrapper that branches by component ID, for analytics keyed by node ID, or for custom design handling.