SlotFills let a module or a separate plugin render React content inside Mantle's admin interface without modifying Mantle. Each slot is created with createSlotFill() from @wordpress/components; Mantle renders the Slot, and your code supplies a Fill.
Available slots
Registered slots
src/slots/. The name column is the string passed to createSlotFill().-
Mantle.Panelsstring -
The main content area for a module’s own tab. Exported as
MantlePanelsand takes atabIdprop so the fill renders only on the matching tab. -
Mantle.DashboardWidgetsstring -
Adds a widget to the Mantle dashboard. Exported as
MantleDashboardWidgets. -
Mantle.SettingsTabsstring -
Adds a section inside the Settings view. Exported as
MantleSettingsTabs. -
Mantle.AdvancedTabstring -
An area reserved for advanced settings. Exported as
MantleAdvancedTab. -
MantleSidebarBottomstring -
The bottom of the admin sidebar, intended for help or promotional content. Exported as
MantleSidebarBottom. Note this slot name has no dot separator. -
Mantle.AdminGlobalstring -
A global area rendered regardless of the active tab. Not re-exported from src/slots/index.js, so import it from its own module.
No fields match this filter.
Rendering into a slot
The bundled modules pair a mantle.navigation_tabs filter, which creates the tab, with registerPlugin() and a MantlePanels fill, which supplies its content.
import { addFilter } from '@wordpress/hooks';import { registerPlugin } from '@wordpress/plugins';import { __ } from '@wordpress/i18n';import Reports from './index';import MenuIcon from './MenuIcon';import { MantlePanels } from '@slots';const slug = 'reports';const title = __( 'Reports', 'mantle' );const capability = 'mantle_view_reports';addFilter( 'mantle.navigation_tabs', `mantle/${ slug }`, ( tabs ) => { const newTab = { id: slug, path: `/${ slug }`, title, icon: MenuIcon, order: 60, component: Reports, requiredCapability: capability, }; return [ ...tabs, newTab ];} );registerPlugin( `mantle-${ slug }`, { render: () => ( <MantlePanels tabId={ slug }> <Reports /> </MantlePanels> ),} );
-
Line 19Order sits between Dashboard (10) and Settings (100). Use multiples of ten.
-
Line 28
tabIdmust match the tabidor the panel never renders.
Adding a Settings subtab
Mantle.SettingsTabs pairs with the mantle.settings_tabs filter. The fill takes a settingsTabId matching the tab's name, plus an area prop, and its registerPlugin() call must declare scope: 'mantle-settings-tabs'.
import { addFilter } from '@wordpress/hooks';import { registerPlugin } from '@wordpress/plugins';import { __ } from '@wordpress/i18n';import ReportsSettings from './ReportsSettings';import { MantleSettingsTabs } from '@slots';addFilter( 'mantle.settings_tabs', 'mantle/reports/register', ( tabs ) => [ ...tabs, { name: 'reports', title: __( 'Reports', 'mantle' ), priority: 50, className: 'mantle-settings-tab-reports', },] );registerPlugin( 'mantle-settings-reports', { scope: 'mantle-settings-tabs', render: () => ( <MantleSettingsTabs settingsTabId="reports" area="content"> <ReportsSettings /> </MantleSettingsTabs> ),} );
-
Line 11Naming this after a module ID makes the subtab hide automatically when that module is inactive.
-
Line 19The scope is required — without it the fill never renders.
-
Line 21
settingsTabIdmust equal the tabnameabove.
Adding a dashboard widget
import { registerPlugin } from '@wordpress/plugins';import { __ } from '@wordpress/i18n';import { MantleDashboardWidgets } from '@slots';const ReportsWidget = () => ( <div> <h3>{ __( 'Reports', 'mantle' ) }</h3> <p>{ __( 'Nothing to report yet.', 'mantle' ) }</p> </div>);registerPlugin( 'mantle-reports-widget', { render: () => ( <MantleDashboardWidgets> <ReportsWidget /> </MantleDashboardWidgets> ),} );
Where slots live
-
slots/-
index.js -
panels.js -
dashboard-widgets.js -
settings-tabs.js -
advanced-tab.js -
sidebar-bottom.js -
global.js
-
-
modules/-
ClientInfo/-
register.js
-
-
Security/-
register.js
-
-
Optimizations/-
register.js
-
-
-
constants/-
routes.js
-
-
components/-
ViewWrapper.js
-
Building against Mantle from another plugin
Mantle's slots are registered on the Mantle JavaScript surface at runtime, so an external plugin must enqueue its script after Mantle's admin bundle and declare it as a dependency. Because the slot components come from Mantle's own bundle rather than a published package, external plugins normally re-create the SlotFill by name:
import { createSlotFill } from '@wordpress/components';import { registerPlugin } from '@wordpress/plugins';// Recreate the SlotFill pair by name. createSlotFill() returns the same// registered slot when the name matches Mantle's.const { Fill: PanelsFill } = createSlotFill( 'Mantle.Panels' );registerPlugin( 'my-plugin-mantle-panel', { render: () => ( <PanelsFill> <p>Rendered from a separate plugin.</p> </PanelsFill> ),} );
See also
- Extending the admin UI — the full walkthrough.
- Hooks —
mantle.navigation_tabsand the PHP filters.