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
| Slot | Fills |
|---|---|
SIDEBAR_NAV_SLOT | Top-level sidebar entries |
SIDEBAR_SECTION_SLOT | A whole section of the sidebar |
SIDEBAR_FOOTER_SLOT | The sidebar’s footer |
HOME_SECTION_SLOT | A band on the home page |
NAVBAR_START_SLOT | The leading end of the top bar |
COMMAND_SLOT | Entries in the command palette |
Inside a workspace
| Slot | Fills |
|---|---|
WORKSPACE_NAV_SLOT | The workspace sidebar’s entries |
WORKSPACE_SECTION_SLOT | A section of the workspace sidebar |
WORKSPACE_ROUTE_SLOT | Routes inside the workspace shell |
WORKSPACE_SETTINGS_TAB_SLOT | A tab on the workspace settings page |
The content library
| Slot | Fills |
|---|---|
RECORDS_TOOLBAR_SLOT | Controls in the records table’s toolbar |
RECORDS_MENU_SLOT | Whole-collection actions, in the toolbar’s ⋯ menu |
RECORDS_COLUMN_SLOT | Extra columns in the records table |
RECORDS_FILTER_FIELDS_SLOT | Virtual filterable fields |
RECORDS_BULK_ACTION_SLOT | Actions on a selection, in the selection bar’s ⋯ menu |
ENTRY_TAB_SLOT | A tab in the entry editor |
ENTRY_HEADER_SLOT | The entry editor’s header |
ENTRY_SIDEBAR_WIDGET_SLOT | A block in the properties panel |
ENTRY_FIELD_CONTROL_SLOT | The control a field type renders as |
ENTRY_MENU_SLOT | Items in the entry’s action menu |
ENTRY_PARAMS_SLOT | Extra query parameters on entry requests |
ENTRY_PRESAVE_SLOT | A hook that runs before a save |
ENTRY_PUBLISH_GUARD_SLOT | A verdict on whether this entry may be published, and why not |
REVISION_EXTRA_SLOT | Rows in a revision preview, for state stored beside the values |
CONTENT_OVERLAY_SLOT | An overlay over the content area |
Elsewhere
| Slot | Fills |
|---|---|
INSIGHTS_WIDGET_SLOT | A card on the Insights dashboard |
INSIGHTS_SECTION_SLOT | A band of the dashboard |
WYSIWYG_MEDIA_SLOT | A 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.