Features Apograph CMS on GitHub

Slots

Named extension points, the ones that ship, and the rule that makes them safe.

Documents 0.5.2 Updated Edit this page Report a problem

On this page

A slot is a named extension point. One plugin defines it; others contribute into it as plain data, with no import between them.

// shell/admin defines it
export const SIDEBAR_NAV_SLOT = createSlot<NavItem>('sidebar.nav');

// any plugin contributes
slots: [
    {
        slot: SIDEBAR_NAV_SLOT,
        items: [{ id: 'reviews', label: 'Reviews', to: '/reviews', order: 40 }]
    }
];

Contributions are wired once at boot and read sorted by whoever renders them.

The catalogue

The app shell

SlotFills
SIDEBAR_NAV_SLOTTop-level sidebar entries
SIDEBAR_SECTION_SLOTA whole section of the sidebar
SIDEBAR_FOOTER_SLOTThe sidebar’s footer
HOME_SECTION_SLOTA band on the home page
NAVBAR_START_SLOTThe leading end of the top bar
COMMAND_SLOTEntries in the command palette

Inside a workspace

SlotFills
WORKSPACE_NAV_SLOTThe workspace sidebar’s entries
WORKSPACE_SECTION_SLOTA section of the workspace sidebar
WORKSPACE_ROUTE_SLOTRoutes inside the workspace shell
WORKSPACE_SETTINGS_TAB_SLOTA tab on the workspace settings page

The content library

SlotFills
RECORDS_TOOLBAR_SLOTControls in the records table’s toolbar
RECORDS_MENU_SLOTWhole-collection actions, in the toolbar’s ⋯ menu
RECORDS_COLUMN_SLOTExtra columns in the records table
RECORDS_FILTER_FIELDS_SLOTVirtual filterable fields
RECORDS_BULK_ACTION_SLOTActions on a selection, in the selection bar’s ⋯ menu
ENTRY_TAB_SLOTA tab in the entry editor
ENTRY_HEADER_SLOTThe entry editor’s header
ENTRY_SIDEBAR_WIDGET_SLOTA block in the properties panel
ENTRY_FIELD_CONTROL_SLOTThe control a field type renders as
ENTRY_MENU_SLOTItems in the entry’s action menu
ENTRY_PARAMS_SLOTExtra query parameters on entry requests
ENTRY_PRESAVE_SLOTA hook that runs before a save
ENTRY_PUBLISH_GUARD_SLOTA verdict on whether this entry may be published, and why not
REVISION_EXTRA_SLOTRows in a revision preview, for state stored beside the values
CONTENT_OVERLAY_SLOTAn overlay over the content area

Elsewhere

SlotFills
INSIGHTS_WIDGET_SLOTA card on the Insights dashboard
INSIGHTS_SECTION_SLOTA band of the dashboard
WYSIWYG_MEDIA_SLOTA media source for the rich-text editor

Two examples of what this buys

The rich-text editor is a slot contribution. The wysiwyg plugin contributes one item to ENTRY_FIELD_CONTROL_SLOT, and that is its entire surface — no route, no navigation entry, no page. It appears everywhere an entry form does without knowing about any of them.

The Insights dashboard ships no widgets. It provides the frame and defines the slots; content, media and localization each fill them. A dashboard is the module most likely to slowly become the one that knows about every other one, and the slot is what prevents it.

Slots are boot-frozen

Contributions are registered once at boot. Nothing adds or removes an item at runtime.

That constraint is what makes an item safe to expose a hook that the render site calls in a loop: the set of items cannot change between renders, so calling a hook per item does not violate the rules of hooks.

Gate on the item, and again inside

An item carries a permission, and the component checks it too.

The duplication is deliberate: the renderer has to know whether a section has any visible items before it draws that section’s heading, or a user without the permission gets a heading over nothing.

Defining your own

export const REVIEWS_PANEL_SLOT = createSlot<ReviewsPanel>('reviews.panel');

Export the token from your package index so another plugin can import it, and document the item shape. An item is data — an id, an order, a permission, and a zero-prop component that reads what it needs.

Prefer a zero-prop component

The shipped slots pass no props: the item names a component, and the component reads what it needs from context. Props are a contract that has to change on both sides; context is one the render site already provides.