Core plugin slots
This page shows the Core host API for plugin slots.
Use Core slots when registered plugins must return BaseRenderable nodes for host-defined layout regions. The host
keeps control of layout and slot types. Plugins render only through the context and data that the host supplies.
Start with Plugin slots for the shared registry, mode, ordering, and error model.
What Core adds#
Core adds these APIs to createSlotRegistry:
createCoreSlotRegistry: Creates a registry forBaseRenderablenodes.registerCorePlugin: Registers the Core-specificCorePlugininterface.SlotRenderable: Mounts a slot in the renderable tree.resolveCoreSlot: Resolves entries without mounting them.
Core slot renderers receive both the host context and slot data: (ctx, data) => BaseRenderable.
createCoreSlotRegistry accepts the same SlotRegistryOptions as createSlotRegistry.
Core hosts use @opentui/core/runtime-plugin-support when contributions come from Bun runtime-loaded modules. See
Load plugins and modules at runtime for setup and module maps.
CorePlugin interface#
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
yes | Unique identifier. Duplicate ids throw. |
order |
number |
no | Sort priority (ascending). Defaults to 0. |
setup |
(ctx, renderer) => void |
no | Called once at registration time. If it throws, the plugin is not registered. |
dispose |
() => void |
no | OpenTUI calls it when the registry unregisters the plugin or clears all plugins. |
slots |
Partial<Record<SlotName, CoreSlotContribution>> |
yes | Each value is a renderer function or a managed slot object. |
A CoreSlotContribution is a plain renderer (ctx, data) => BaseRenderable or a CoreManagedSlot with node ownership
hooks.
Basic usage#
A SlotRenderable extends Renderable, so any renderable parent can own it. It resolves contributions and reconciles
their output as child nodes.
Each contribution must return one synchronous BaseRenderable. A promise, the slot mount itself, or a node attached
to another parent causes a reported render failure.
import {
BoxRenderable,
createCliRenderer,
createCoreSlotRegistry,
registerCorePlugin,
SlotRenderable,
TextRenderable,
} from "@opentui/core"
type Slots = "statusbar"
type SlotData = { label: string }
const context = { appName: "core-app", version: "1.0.0" }
const renderer = await createCliRenderer()
const registry = createCoreSlotRegistry<Slots, typeof context, SlotData>(renderer, context)
const unregister = registerCorePlugin(registry, {
id: "clock-plugin",
order: 0,
slots: {
statusbar(_ctx, data) {
return new TextRenderable(renderer, {
id: "clock-status",
content: `clock: ${data.label}`,
})
},
},
})
const slot = new SlotRenderable(renderer, {
id: "statusbar-slot",
registry,
name: "statusbar",
data: { label: "ok" },
mode: "append",
width: "100%",
height: 3,
flexDirection: "row",
fallback: () =>
new TextRenderable(renderer, {
id: "statusbar-fallback",
content: "fallback",
}),
})
renderer.root.add(slot)
// later
slot.mode = "replace"
slot.data = { label: "updated" }
slot.refresh()
slot.destroy()registerCorePlugin returns an unregister function. Calling it removes the plugin and calls its dispose hook.
Because SlotRenderable extends Renderable, it accepts standard layout options such as width, height,
flexDirection, and padding.
SlotRenderable options#
| Option | Type | Required | Description |
|---|---|---|---|
id |
string |
no | Unique renderable identifier. OpenTUI generates one when omitted. |
registry |
CoreSlotRegistry |
yes | The registry to read plugins from |
name |
slot name | yes | Which slot to mount |
data |
object | no | Slot data passed to plugin renderers as the second argument |
mode |
SlotMode |
no | "append" (default), "replace", or "single_winner". See slot modes. |
fallback |
BaseRenderable | BaseRenderable[] | () => ... |
no | Fallback nodes or a factory that creates them |
pluginFailurePlaceholder |
(failure, ctx) => BaseRenderable | BaseRenderable[] | undefined |
no | Creates placeholder UI when a plugin throws |
| layout options | RenderableOptions |
no | width, height, flexDirection, padding, and other standard layout props |
Instance API#
| Member | Description |
|---|---|
mode |
Getter/setter. Changing the mode automatically refreshes the slot. |
refresh() |
Re-resolve plugins and reconcile mounted children. |
destroy() |
Inherits from Renderable. SlotRenderable unsubscribes from the registry, calls onDeactivate on active managed slots, and then calls onDispose on all managed slots. It destroys host-owned nodes. It detaches plugin-owned nodes for the plugin to release in onDispose. |
resolveCoreSlot#
Resolve slot entries without mounting them. Use this API to inspect or render contribution output manually.
import { createCliRenderer, createCoreSlotRegistry, resolveCoreSlot } from "@opentui/core"
const renderer = await createCliRenderer()
const registry = createCoreSlotRegistry<"statusbar">(renderer, {
appName: "core-app",
version: "1.0.0",
})
const entries = resolveCoreSlot(registry, "statusbar")
// Array<{ id: string, renderer: (ctx, data) => BaseRenderable }>Plugin failure placeholders#
If a contribution throws while it renders, the host can create placeholder nodes for that failure:
const slot = new SlotRenderable(renderer, {
registry,
name: "statusbar",
fallback: () => new TextRenderable(renderer, { id: "fallback", content: "fallback" }),
pluginFailurePlaceholder(failure, ctx) {
return new TextRenderable(renderer, {
id: `error-${failure.pluginId}`,
content: `plugin error: ${failure.pluginId}`,
})
},
})The registry reports contribution failures with source core and phase render. If the placeholder throws, the
registry reports phase error_placeholder, and that contribution has no placeholder output. The host owns and destroys
placeholder nodes.
Managed slot contributions#
A Core slot contribution can be a plain function or a managed slot object with node ownership hooks:
registerCorePlugin(registry, {
id: "managed-plugin",
slots: {
statusbar: {
render(_ctx, data) {
return new TextRenderable(renderer, { id: "managed", content: "managed" })
},
onActivate(ctx) {
// The contribution became active in this slot.
},
onDeactivate(ctx) {
// The contribution is no longer active.
},
onDispose(ctx) {
// Release the plugin-owned nodes for this slot.
},
},
},
})CoreManagedSlot interface#
| Field | Type | Required | Description |
|---|---|---|---|
render |
(ctx, data) => BaseRenderable |
yes | Creates the renderable node for this slot |
onActivate |
(ctx) => void |
no | Runs when the contribution becomes active in the slot |
onDeactivate |
(ctx) => void |
no | Runs when the contribution is no longer active |
onDispose |
(ctx) => void |
no | Runs when the registry removes the plugin or the host destroys the slot |
Node ownership#
For a plain function contribution, the host owns the returned nodes. On deactivation or disposal, the host detaches and destroys them.
For a managed contribution, the plugin owns the returned nodes. On deactivation, the host detaches them but does not
destroy them. The plugin can retain and return those nodes if the contribution becomes active again. On disposal, the
host calls onDispose. The plugin must then destroy or release its nodes.
Destroying SlotRenderable unsubscribes it from the registry. It calls onDeactivate for active managed
contributions, then calls onDispose for all managed contributions. Host-owned failure placeholders and fallback nodes
are also destroyed.
Example#
See the Core plugin slots example.