window.EasySvgPanel.registerTab()
Registers your own tab in the Easy SVG Icons panel: an id, a render function that returns a React element, and optional label, area, order and icon.
JavaScript · Easy SVG · Status: Stable
registerTab() adds a screen of your own to the Icons panel. You give it an id and a function that returns what the tab shows, and the panel puts the tab into its left rail, in the area you choose. Register the same id again and your new version replaces the old one.
The panel draws a tab from another add-on only while Easy SVG Pro is active and licensed. On a site with the free plugin alone, registerTab() accepts your tab but the panel does not show it. If your add-on must work without Pro, put your icons into the Icon library with registerCollections() instead, which has no such condition.
Usage
window.EasySvgPanel.registerTab( {
id: 'myaddon-report', // required
render: ( ctx ) => element, // required
label: 'SVG report',
area: 'inspect',
order: 30,
icon: 'audit',
} );
When to use it
- Your add-on needs a full screen inside the Icons panel: a report, a tool, a settings form of its own.
- You want that screen next to Easy SVG's own tabs instead of a separate admin page, with the same look and the same menu.
- Combined with
easy_svg_panel_menu_tabs, you want the tab reachable from the Icons menu and, if you like, by users withoutmanage_options.
When not to use it:
- To show icons from your add-on. The Icon library tab can list them beside the site's own icons through
registerCollections(). - On sites where Easy SVG Pro is not licensed, because the tab will not be drawn there (see the callout above).
- To decide who may use the tab. The capability is declared in PHP, not here.
Parameters
A unique id for the tab. It becomes the value of ?tab= in the address bar and must match the key you use in easy_svg_panel_menu_tabs, because the panel looks up the tab's capability by this id. Use lowercase letters, digits, _ and -, prefixed with your add-on's slug. The ids library, settings, upsell, sets, audit, configurator and licence are taken.
Called with one argument, ctx, and must return something React can render: an element, a string or null. Return an element of your own component, for example ( ctx ) => wp.element.createElement( MyTab, { ctx } ), and keep state, effects and requests inside that component. The ctx object holds:
restRoot(string): the REST API root with a trailing slash, for examplehttps://example.com/wp-json/. With plain permalinks it is the?rest_route=/form, so append your route to it rather than building the URL yourself.nonce(string): awp_restnonce. Send it as theX-WP-Nonceheader.proActive(boolean): Easy SVG Pro is installed and active.proLicensed(boolean): Easy SVG Pro reports a valid licence.iconsEnabled(boolean): the icon library feature is switched on.iconBlock(boolean): the core Icon block is available (WordPress 7.1 icon registry).maxMbCeiling: the highest upload cap the site allows, in megabytes. It passes throughwp_localize_script(), so it can arrive as a string; wrap it inNumber().onActivate(function or undefined): switches to Pro's licence tab when there is one.
The text in the rail. Falls back to the id. Translate it with wp.i18n.__() and your own text domain. It is separate from the menu label you pass in PHP, so keep both the same.
Which section of the rail the tab goes into. One of icons, design, inspect, security or setup, shown as Icons, Design, Inspect, Security and Setup. A missing or unknown value puts the tab into setup. Contract level 2. The older name group is still read as an alias when it holds one of these values.
The position within the area, lower first. Default 50. For orientation: Easy SVG's own tabs use 10 and 20, the Pro upsell tab uses 90. Ties keep registration order.
The rail icon. Either a keyword of the panel's own set (settings, library, sets, configurator, audit, licence, packs, features, styles, bulk, history, upsell) or your own drawing as children of an svg element with viewBox="0 0 24 24", fill="none", stroke="currentColor" and a stroke width of 1.5. An unknown keyword draws an empty square. Contract level 2.
Return value
Nothing. There is no error and no return value when the tab is rejected; check with window.EasySvgPanel.get( id ) if you need to know.
Default
| Field | When you leave it out |
|---|---|
label | the tab id |
area | setup |
order | 50 |
icon | empty icon box |
| capability | manage_options, unless easy_svg_panel_menu_tabs names another one for this id |
Examples
A basic tab
No build step: wp.element is the React that WordPress ships and the panel uses. Enqueue this file through easy_svg_panel_enqueue with the panel handle, wp-element and wp-i18n as dependencies.
( function () {
const panel = window.EasySvgPanel;
if ( ! panel ) {
return;
}
const { createElement: el } = window.wp.element;
const { __ } = window.wp.i18n;
function HelloTab() {
return el( 'p', null, __( 'Hello from my add-on.', 'myaddon' ) );
}
panel.registerTab( {
id: 'myaddon-hello',
label: __( 'Hello', 'myaddon' ),
area: 'setup',
order: 60,
render: () => el( HelloTab ),
} );
} )();
A tab that loads data from your REST route
ctx carries everything a request needs. The component aborts its request when the user switches tabs, because the panel unmounts tabs that are not open.
( function () {
const panel = window.EasySvgPanel;
if ( ! panel ) {
return;
}
const { createElement: el, useEffect, useState } = window.wp.element;
const { __ } = window.wp.i18n;
function ReportTab( { ctx } ) {
const [ rows, setRows ] = useState( null );
const [ error, setError ] = useState( '' );
useEffect( () => {
const controller = new AbortController();
fetch( ctx.restRoot + 'myaddon/v1/report', {
credentials: 'same-origin',
headers: { 'X-WP-Nonce': ctx.nonce },
signal: controller.signal,
} )
.then( ( response ) =>
response.ok ? response.json() : Promise.reject( new Error( response.statusText ) )
)
.then( ( data ) => setRows( Array.isArray( data ) ? data : [] ) )
.catch( ( e ) => {
if ( 'AbortError' !== e.name ) {
setError( e.message );
}
} );
return () => controller.abort();
}, [ ctx.restRoot, ctx.nonce ] );
if ( error ) {
return el( 'p', { role: 'alert' }, error );
}
if ( null === rows ) {
return el( 'p', null, __( 'Loading…', 'myaddon' ) );
}
return el(
'ul',
null,
rows.map( ( row ) => el( 'li', { key: row.id }, row.title ) )
);
}
panel.registerTab( {
id: 'myaddon-report',
label: __( 'SVG report', 'myaddon' ),
area: 'inspect',
order: 30,
icon: 'audit',
render: ( ctx ) => el( ReportTab, { ctx } ),
} );
} )();
Defensive: older panels and your own icon
area and a custom icon need contract level 2. On level 1 the tab still works and lands in Setup.
( function () {
const panel = window.EasySvgPanel;
if ( ! panel || 'function' !== typeof panel.registerTab ) {
return;
}
const { createElement: el } = window.wp.element;
const level = Number( panel.api || 1 );
const tab = {
id: 'myaddon-report',
label: 'SVG report',
render: () => el( 'p', null, 'Report' ),
};
if ( 2 <= level ) {
tab.area = 'inspect';
tab.order = 30;
// A check mark, drawn on the panel's 24 by 24 grid.
tab.icon = el( 'path', { d: 'M5 12l4 4L19 7' } );
}
panel.registerTab( tab );
} )();
How it works
registerTab() itself only stores the object in a map keyed by id and notifies the panel. Everything else happens when the panel renders (src/panel/App.jsx):
- Collecting. The panel starts with its own tabs (
library,settings), the Pro catalogue (sets,audit,configurator,licence) and theupselltab. Registered tabs with any other id are added as extra tabs, but only whenEasySvgPanelData.proLicensedis true. A registered tab with a catalogue id replaces the locked placeholder of that Pro tab, once the licence is valid (thelicencetab at any time, so a key can be entered). - Normalising. Each extra tab gets its area (
area, else the legacygroup, elsesetup), its order (Number( order )when finite, else 50) and its label (label, else the id). - Filtering by capability. A tab stays only when the user holds
caps[ tabCaps[ id ] ], falling back tomanage_optionsfor ids that have no entry ineasy_svg_panel_menu_tabs. - Sorting. By area (icons, design, inspect, security, setup), then by order.
- Rendering. The rail draws a button per tab. For the content, the panel calls every tab's
render( ctx )on each render of the panel, but the tabs component mounts only the open tab (lazyMountandunmountOnExit). Switching tabs unmounts the previous one. - Choosing the open tab. The
?tab=value in the address bar wins; an id that is not in the list opens the first tab. Clicking a tab rewrites?tab=withhistory.replaceState().
Pitfalls
- No Pro licence, no tab. See the callout at the top. Only add your menu entry when the tab will be drawn, otherwise the entry opens the first tab.
- No hooks directly in
render. The panel callsrender( ctx )for every tab on every render, as a plain function, so hooks called there would belong to the panel's own component and break as soon as the number of tabs changes. PutuseState,useEffectand requests inside a component and return an element of it. - Keep
rendercheap and free of side effects. It runs even for tabs that are closed. - State is lost when the user switches tabs. The tab unmounts. Keep unsaved input short, save it, or store it outside the component.
- Abort your requests in the cleanup. Otherwise a slow answer arrives after the user left and can overwrite newer state.
- Effects run twice with
SCRIPT_DEBUG. The panel renders in React'sStrictMode; with the development build of React every effect mounts, unmounts and mounts again. - Use WordPress's React.
wp.elementorwindow.React. A React copy bundled into your script breaks hooks. rendermust not return a DOM node. If you want to draw with plain DOM code, return an element with arefcallback and fill the node there.- Collisions. Registering
settings,libraryorupsellis ignored. Registeringsets,audit,configuratororlicencetakes over a Pro tab. The last registration of an id wins. ctxvalues from PHP are not always typed.maxMbCeilingcan be a string; the booleans are already converted.
Your tab is admin UI and runs with the rights of whoever opens it. React escapes text you render, but dangerouslySetInnerHTML does not: never put SVG or HTML from a request into it unless it went through a sanitizer such as easy_svg_sanitizer() on the server. Every route your tab calls must check current_user_can() itself; the capability in easy_svg_panel_menu_tabs only hides the tab.