# API

Two entry points. `create-slot/core` is React-free — manifests, descriptors and
the resolver — importable from server components, Node scripts and tests. The
root entry is the React adapter, and it re-exports the core, so a client module
needs one import. Hover any identifier below — the types come from the
published package, not from prose.

```ts
import {
  ContributionBoundary,
  createSlot,
  definePlugin,
  defineSlot,
  entriesOf,
  resolvePlugins,
  SlotHost,
  SlotProvider,
  useContribution,
  useSlotProps,
} from "create-slot"
```

| Export | Entry | What it is |
| --- | --- | --- |
| [`defineSlot`](#defineslot) | core | Creates a slot descriptor: `name`, `contribute`, `override`. |
| [`definePlugin`](#defineplugin) | core | Identity plus an `id` check. Your own fields keep their types. |
| [`resolvePlugins`](#resolveplugins) | core | The whole registry as one pure function: plugins in, `Resolution` out. |
| [`entriesOf`](#entriesof) | core | The entries of one slot, typed by its descriptor. |
| [`SlotProvider`](#slotprovider) | adapter | Puts one Resolution in scope for every host below it. |
| [`SlotHost`](#slothost) | adapter | Renders every contribution to a slot, in resolved order. |
| [`useSlotProps`](#useslotprops) | adapter | The nearest host's props for a slot. |
| [`useContribution`](#usecontribution) | adapter | Which contribution is rendering: slot, plugin id, contribution id. |
| [`ContributionBoundary`](#contributionboundary) | adapter | The per-contribution isolation wrapper, for hand-rolled hosts. |
| [`createSlot`](#createslot) | façade | The runtime channel, whole and alone — SPA-only, source-compatible with 2.x/3.x. |

Everything that can throw is listed on one page, with its cause and its fix —
see [Errors](/errors).

## `defineSlot`

```tsx twoslash
// [!include ~/snippets/api-registry.tsx:prelude]
// ---cut---
// [!include ~/snippets/api-registry.tsx:define-slot]
//    ^?
```

A slot is a descriptor — pure data, safe to import from server modules. Type
safety lives on this object, because a string id cannot carry `Props`.

The name must be non-empty: the Resolution keys contributions by name, so an
empty one is two slots quietly sharing a bucket.

### `slot.contribute(id, spec)`

```tsx twoslash
// [!include ~/snippets/api-registry.tsx:prelude]
// [!include ~/snippets/api-registry.tsx:define-slot]
// ---cut---
// [!include ~/snippets/api-registry.tsx:contribute]
```

| argument | type | notes |
| --- | --- | --- |
| `id` | `string` | Required. Local to the plugin, never contains `/`, unique inside it. The full id `${pluginId}/${id}` is the React key and the override address. |
| `spec.component` | `ComponentType<Props>` | Receives the host's props as its own. May accept fewer; never different ones. |
| `spec.order` | `number` | Defaults to `0`. A priority, not an array index — see [Ordering](/ordering). |

The return value is plain data — `{ slot, id, order, component }` — which is
what lets the resolver enumerate it before render. A malformed id is reported
by the resolver as a diagnostic, not thrown at declaration.

### `slot.override(target, patch)`

A typed patch aimed at one full contribution id. It comes from the slot rather
than a string because this is where a replacement `component` is checked
against the slot's `Props`.

```tsx twoslash
// [!include ~/snippets/ordering-guide.tsx:prelude]
// [!include ~/snippets/ordering-guide.tsx:tie]
// ---cut---
// [!include ~/snippets/ordering-guide.tsx:override]
```

## `definePlugin`

Identity plus a non-empty-`id` check — and the one place where the plugin system
is discoverable in a codebase. It is generic over its argument, so your own
fields keep their types.

```tsx twoslash
// [!include ~/snippets/api-registry.tsx:prelude]
// [!include ~/snippets/api-registry.tsx:define-slot]
// [!include ~/snippets/api-registry.tsx:contribute]
// ---cut---
// [!include ~/snippets/api-registry.tsx:define-plugin]
//           ^?
```

The library reads `id` and `contributes`. Everything else on the object is
yours; see [Plugin state](/state).

## `resolvePlugins`

The whole registry as one pure, synchronous, deterministic function: grouping,
`disable`, `override` patches, sorting (`order`, then plugin position, then
declaration position), key minting. Inputs are never mutated. The same plugins
and options produce a deep-equal Resolution every time — which is the entire
[SSR contract](/server-rendering#the-contract-in-one-sentence).

```tsx twoslash
// [!include ~/snippets/api-registry.tsx:prelude]
// [!include ~/snippets/api-registry.tsx:define-slot]
// [!include ~/snippets/api-registry.tsx:contribute]
// [!include ~/snippets/api-registry.tsx:define-plugin]
// ---cut---
// [!include ~/snippets/api-registry.tsx:resolve]
```

| option | type | notes |
| --- | --- | --- |
| `disable.plugins` | `readonly string[]` | Plugin ids to drop whole. |
| `disable.contributions` | `readonly string[]` | Full contribution ids to drop one by one. |
| `overrides` | `readonly Override[]` | Typed patches from `slot.override()`. Later patches to one target win. |

Problems come back on `resolution.diagnostics` — never thrown, never silently
dropped. Six codes: `duplicate-plugin-id`, `duplicate-contribution-id`,
`invalid-contribution-id`, `unknown-disable-target`,
`unknown-override-target`, `override-slot-mismatch`. In development the
provider prints them once per content change; in a test,
`expect(resolvePlugins(PLUGINS).diagnostics).toEqual([])` is the catalog
validator.

## `entriesOf`

```tsx twoslash
// [!include ~/snippets/api-registry.tsx:prelude]
// [!include ~/snippets/api-registry.tsx:define-slot]
// [!include ~/snippets/api-registry.tsx:contribute]
// [!include ~/snippets/api-registry.tsx:define-plugin]
// [!include ~/snippets/api-registry.tsx:resolve]
// ---cut---
// [!include ~/snippets/api-registry.tsx:entries-of]
//           ^?
```

Restores what `contribute` erased: each entry's `component` is typed by the
slot, no cast. This is what a hand-rolled host — including a
[server host](/server-rendering#a-server-host-in-userland) — maps over.

## `SlotProvider`

```tsx twoslash
// [!include ~/snippets/api-registry.tsx:prelude]
// [!include ~/snippets/api-registry.tsx:define-slot]
// [!include ~/snippets/api-registry.tsx:contribute]
// [!include ~/snippets/api-registry.tsx:define-plugin]
// [!include ~/snippets/api-registry.tsx:resolve]
// ---cut---
// [!include ~/snippets/api-registry.tsx:provider]
```

| prop | type | notes |
| --- | --- | --- |
| `resolution` | `Resolution` | The pre-resolved graph. Identity-compared: hold it at module scope or in `useMemo`. |
| `onError` | `(error: SlotError) => void` | Reported when a contribution throws. Inline arrows are free. |
| `Failed` | `ComponentType<SlotError & { reset }>` | Rendered in place of a contribution that threw; `reset` retries it. |
| `Pending` | `ComponentType<ContributionInfo>` | Rendered while a deferred contribution loads. Unset keeps `null`. |

`Failed` and `Pending` are **components**, not render props: a component
reference has stable identity and crosses an RSC boundary, where a closure
cannot. The handlers live in a context of their own, read only where a
contribution is isolated, so inline values never re-render a host.

## `SlotHost`

Renders every contribution to a slot, in resolved order. Its own children are
the placeholder while nothing is contributed.

| prop | type | notes |
| --- | --- | --- |
| `slot` | `Slot<Props>` | Which slot to render. |
| `props` | `Props` | An explicit bag, not a spread — the host's own props can never collide with a slot's, and `children` structurally cannot leak into a contribution. Optional when `Props` is empty. |
| `children` | `ReactNode` | The placeholder. Renders only while nothing is contributed. |
| `renderEntries` | `(entries: readonly HostEntry[]) => ReactNode` | Full-ownership escape hatch — see below. |

Host props are value-held: the host keeps them stable while their values stay
the same, and it compares each resolved entry by content, so a host that
re-renders for someone else's sake does not re-render the contributions. A
host outside a `SlotProvider`
[throws](/errors#slothost-rendered-outside-of-slotprovider).

### `renderEntries`

Called with every entry — with zero too — and owns layout, wrappers and the
empty state; `children` is then ignored. Each `HostEntry` carries the identity
(`key`, `pluginId`, `contributionId`, `order`) plus `node`, the contribution
already wrapped in memo, error boundary, `Suspense` and identity. Key your own
wrappers with `entry.key`, never with an array index.

```tsx twoslash
// [!include ~/snippets/api-registry.tsx:prelude]
// [!include ~/snippets/api-registry.tsx:define-slot]
// [!include ~/snippets/api-registry.tsx:contribute]
// [!include ~/snippets/api-registry.tsx:define-plugin]
// [!include ~/snippets/api-registry.tsx:resolve]
// ---cut---
// [!include ~/snippets/api-registry.tsx:render-entries]
```

## `useSlotProps`

```tsx twoslash
// [!include ~/snippets/api-registry.tsx:prelude]
// [!include ~/snippets/api-registry.tsx:define-slot]
// ---cut---
// [!include ~/snippets/api-registry.tsx:use-slot-props]
```

Returns `Props | null` — null outside a host. A declared contribution rarely
needs it: the host's props arrive as its own. It exists for components nested
deeper inside a contribution, and it is context, so it is client-only — a
[server host](/server-rendering#a-server-host-in-userland) passes serializable
props directly.

## `useContribution`

```tsx twoslash
// [!include ~/snippets/api-registry.tsx:prelude]
// ---cut---
// [!include ~/snippets/api-registry.tsx:use-contribution]
```

Throws outside a contribution. Use it to give each plugin a store, a prefixed
logger, a settings namespace, or a telemetry tag.

## `ContributionBoundary`

The per-contribution isolation wrapper the default host places around every
entry: contribution identity, an error boundary, and a `Suspense` boundary.
Exported so hand-rolled hosts — including RSC server components mapping a
Resolution — keep the same failure semantics as the default host.

```tsx twoslash
// [!include ~/snippets/api-registry.tsx:prelude]
// [!include ~/snippets/api-registry.tsx:define-slot]
// [!include ~/snippets/api-registry.tsx:contribute]
// [!include ~/snippets/api-registry.tsx:define-plugin]
// [!include ~/snippets/api-registry.tsx:resolve]
// ---cut---
// [!include ~/snippets/api-registry.tsx:boundary]
```

## `createSlot`

The [runtime channel](/channels), whole and alone — SPA-only by design: the
server and hydration snapshots are always empty. Each factory owns a private
store, shared with nothing; the registry never merges it.

```tsx twoslash
// [!include ~/snippets/api-create-slot.tsx:prelude]
// ---cut---
// [!include ~/snippets/api-create-slot.tsx:factory]
//    ^?
```

### `<Slot order?>`

The component itself is the contributor. It renders nothing where it sits and
takes exactly one element as its child.

```tsx twoslash
// [!include ~/snippets/api-create-slot.tsx:prelude]
// [!include ~/snippets/api-create-slot.tsx:factory]
// [!include ~/snippets/api-create-slot.tsx:use-props]
// ---cut---
// [!include ~/snippets/api-create-slot.tsx:fill]
```

| prop | type | notes |
| --- | --- | --- |
| `children` | `ReactElement` | Required. Exactly one element — [not text, not siblings](/errors#a-fill-expects-a-single-react-element-as-its-child). |
| `order` | `number` | Defaults to `0`. [Read once, on mount](/ordering#read-once). |

Both are also the React key's source: the key is assigned on mount and kept, so
changing the child's *content* reconciles instead of remounting.

### `<Slot.Host {...props}>`

```tsx twoslash
// [!include ~/snippets/api-create-slot.tsx:prelude]
// [!include ~/snippets/api-create-slot.tsx:factory]
// ---cut---
// [!include ~/snippets/api-create-slot.tsx:host]
```

Unlike `SlotHost`, the façade host spreads its props — the 2.x shape — and
tolerates rendering with no provider anywhere, because the façade has none.

### `Slot.useProps()`

```tsx twoslash
// [!include ~/snippets/api-create-slot.tsx:prelude]
// [!include ~/snippets/api-create-slot.tsx:factory]
// ---cut---
// [!include ~/snippets/api-create-slot.tsx:use-props]
```

Unlike `useSlotProps`, this one is not nullable: a `Slot`'s children only ever
render inside one of its factory's hosts, so the façade can promise the props.

## Types

All exported from the root entry; the core types also from `create-slot/core`.

| Type | What it describes |
| --- | --- |
| `Slot<Props>` | What `defineSlot` returns: `name`, `contribute`, `override`. Pure data. |
| `ContributionSpec<Props>` | The second argument to `contribute`: `{ component, order? }`. |
| `Contribution` | What `contribute` returns: `{ slot, id, order, component }`. Plain data. |
| `Override` | What `slot.override` returns. Opaque data; `resolvePlugins` consumes it. |
| `PluginDefinition` | The two fields the library reads: `id`, `contributes?`. |
| `ResolveOptions` | The options bag: `disable?`, `overrides?`. |
| `Resolution` | The resolved graph: `slots` (name → sorted entries) plus `diagnostics`. |
| `ResolvedEntry<Props>` | One entry: `key`, `pluginId`, `contributionId`, `slot`, `order`, `seq`, `component`. |
| `Diagnostic` | A problem the resolver found. Returned, never thrown. |
| `ErasedComponent` | A contribution's component with its props erased, so one graph holds every slot's. |
| `SlotProviderProps` | `resolution`, `onError?`, `Failed?`, `Pending?`, `children?`. |
| `SlotHostProps<Props>` | `slot`, `props`, `children?`, `renderEntries?`. |
| `HostEntry` | What `renderEntries` receives: identity plus the finished `node`. |
| `SlotError` | `{ pluginId, contributionId, slot, error }` — what `onError` and `Failed` receive. |
| `ContributionInfo` | `{ slot, pluginId, contributionId }` — what `useContribution` returns and `Pending` receives. |
| `RuntimeSlot<Props>` | What `createSlot` returns: the contributor, plus `Host` and `useProps`. Named `Slot<Props>` before 4.0. |

`PluginDefinition` is the one to extend. `definePlugin` is generic over its
argument, so an intersection type keeps every field of your own:

```tsx twoslash
// [!include ~/snippets/api-registry.tsx:prelude]
// ---cut---
// [!include ~/snippets/api-registry.tsx:extend]
//                  ^?
```

## Behavioural notes

* A host with no contributions renders its own children.
* Multiple hosts of one slot each render the same contributions;
  `useSlotProps` reflects the host doing the rendering.
* `order` is a priority. Equal values are a stable tie: plugin position in the
  list, then declaration position. A façade fill reads its `order` once, on
  mount.
* Entries are compared by **content**, because `resolvePlugins` mints fresh
  entry objects on every call and an inline call per render is legal. A host
  holds its props steady while their values are unchanged. See
  [Performance](/performance).
* Each `createSlot()` factory owns a private store: two factories can never
  exchange fills, two React roots using one factory always do, and no
  module-level registry exists for a duplicated package copy to split.
