Guide

Extending the admin UI

Mantle's admin interface is a React application with two extension points: a filter that declares where something appears, and a SlotFill that supplies what appears there. You never need to modify Mantle's own components.

The two-part pattern

Every bundled module follows the same shape. The filter creates the navigation entry; registerPlugin() plus a fill renders the content.

Flow

How a tab reaches the screen

6 relationships
The tab object supplies the title and component; the fill supplies panel content.

Add a top-level tab

  1. Create the view component

    A plain component that returns content. It does not need a Card wrapper — ViewWrapper supplies one.

  2. Register the tab

    Add a tab object through the mantle.navigation_tabs filter with id, path, title, component, order, and requiredCapability.

  3. Register the panel fill

    Call registerPlugin() with a MantlePanels fill whose tabId matches the tab id.

  4. Visit admin.php?page=mantle&view=<id>.

src/modules/Reports/register.js
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';addFilter( 'mantle.navigation_tabs', `mantle/${ slug }`, ( tabs ) => [	...tabs,	{		id: slug,		path: `/${ slug }`,		title: __( 'Reports', 'mantle' ),		icon: MenuIcon,		order: 60,		component: Reports,		requiredCapability: 'mantle_view_reports',	},] );registerPlugin( `mantle-${ slug }`, {	render: () => (		<MantlePanels tabId={ slug }>			<Reports />		</MantlePanels>	),} );
Two files: one registers, one renders.

Tab object properties

Reference

Tab object

Consumed by getTabs() and ViewWrapper. Shapes verified against src/constants/routes.js and the bundled register.js files.
8 fields
id string required
Unique kebab-case identifier. Becomes the ?view= query value.
path string required
Leading-slash path form of the ID, used by the path and location helpers.
title string required
Shown in navigation and rendered as the Card heading by ViewWrapper.
component any required
The React component to render.
order number
Navigation sort order. Dashboard is 10 and Settings is 100, so module tabs sit between. Use multiples of ten.
icon any
Component rendered as the navigation icon.
requiredCapability string
Capability needed to see the tab.
moduleId string
The PHP module ID, when it differs from the tab slug. Lets the sidebar resolve the module’s enabled state.

Add a Settings subtab

Use mantle.settings_tabs with a Mantle.SettingsTabs fill. Naming the subtab after a module ID gives you automatic visibility gating — the Settings view removes subtabs whose name matches an inactive module's slug.

jsx src/modules/Reports/settings.js
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>	),} );
The scope and settingsTabId must both be present.

Add a dashboard widget

jsx src/modules/Reports/widget.js
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>	),} );
Dashboard widgets need no tabId.

Where the pieces live

src/ File tree
  • constants/
    • routes.js
  • components/
    • ViewWrapper.js
  • layouts/
    • index.js
  • slots/
    • index.js
    • panels.js
    • settings-tabs.js
    • dashboard-widgets.js
  • modules/
    • Security/
      • register.js
      • index.js
      • MenuIcon.js
    • Modules/
      • register.js
  • views/
    • Dashboard/
    • Setup/
Follow src/modules/Security/ as the reference implementation.

Build and verify

Rebuild the admin bundle bash
npm run build

Then load admin.php?page=mantle&view=reports. If the tab is missing, work through these in order:

Reference

Tab not appearing

5 fields
Filter returned an object string
The callback must return an array. This is the most common cause.
register.js never imported string
The module’s register.js has to be reachable from the admin entry point, or the filter is never added.
Capability missing string
requiredCapability names a capability the current user lacks. Register it and run a capability sync.
Bundle not rebuilt string
Run npm run build, or npm run start while developing.
tabId mismatch string
The tab appears but the panel is empty: the tabId on MantlePanels does not match the tab id.

See also

Was this helpful?