Admin Panel API: Navigation & settings
Page summary:
Use
addMenuLinkinregisterto add sidebar links. UseaddSettingsLinkto both create new settings sections (pass a section object as the first argument) and extend existing ones (pass a section id string). The legacycreateSettingSectionandaddSettingsLinksmethods are deprecated.
Plugins can customize the admin panel's navigation sidebar and settings pages to provide access to their features. All functions described on this page are called within the register or bootstrap lifecycle functions of your plugin's entry file.
Before diving deeper into the concepts on this page, please ensure you have:
- created a Strapi plugin,
- read and understood the basics of the Admin Panel API
Navigation sidebar (menu links)
The navigation sidebar is the main menu on the left side of the admin panel. Plugins can add links to this sidebar using the addMenuLink() method in the register lifecycle function.
Adding a menu link
Adding a link to the navigation sidebar is done with the addMenuLink() function, which should be registered through the register() lifecycle function of your plugin.
A menu link accepts the following parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
to | string | ✅ | Path the link should point to (relative to the admin panel root) (see additional information) |
icon | React.ElementType | ✅ | React component for the icon to display in the navigation |
intlLabel | object | ✅ | Label for the link, following the React Int'l convention, with:
|
permissions | Array<Permission> | ✅ | Array of permission objects that control link visibility. Pass [] for no restrictions. |
Component | function | ❌ | Function that returns a dynamic import() of the plugin's main page component. The page module must export the component as default. If omitted, no route is registered (label-only entry). |
position | number | ❌ | Numeric position in the menu (lower numbers appear first) |
licenseOnly | boolean | ❌ | If true, displays a ⚡ icon to indicate the feature requires a paid license (default: false) |
target | string | ❌ | Standard anchor target attribute (e.g. _blank for external links) |
notificationsCount | number | ❌ | Badge count shown next to the menu label |
exact | boolean | ❌ | Whether the active-link match should be exact |
The intlLabel.id values should correspond to keys in your translation files located at admin/src/translations/[locale].json. See Admin localization for details.
The permissions parameter only controls whether the link is visible in the navigation. It does not protect the page itself. A user who knows the URL can still access the page directly. To fully secure a plugin route, you must also check permissions inside your page component and register your RBAC actions on the server side with actionProvider.registerMany. See the Admin permissions for plugins guide for a complete walkthrough.
- JavaScript
- TypeScript
import PluginIcon from './components/PluginIcon';
export default {
register(app) {
app.addMenuLink({
to: `/plugins/my-plugin`,
icon: PluginIcon,
intlLabel: {
id: 'my-plugin.plugin.name',
defaultMessage: 'My Plugin',
},
Component: () => import('./pages/App'),
permissions: [], // Array of permission objects
position: 3, // Position in the menu (lower numbers appear first)
licenseOnly: false, // Set to true to show ⚡ icon for paid features
});
app.registerPlugin({
id: 'my-plugin',
name: 'My Plugin',
});
},
};
import PluginIcon from './components/PluginIcon';
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
register(app: StrapiApp) {
app.addMenuLink({
to: `/plugins/my-plugin`,
icon: PluginIcon,
intlLabel: {
id: 'my-plugin.plugin.name',
defaultMessage: 'My Plugin',
},
Component: () => import('./pages/App'),
permissions: [], // Array of permission objects
position: 3, // Position in the menu (lower numbers appear first)
licenseOnly: false, // Set to true to show ⚡ icon for paid features
});
app.registerPlugin({
id: 'my-plugin',
name: 'My Plugin',
});
},
};
The page module referenced by Component must export the component as the default export (e.g. export default App; in admin/src/pages/App.tsx). Earlier versions of Strapi accepted an async callback that returned a named export, but this pattern is deprecated and logs a warning at runtime. Use Component: () => import(path) so the dynamic import directly resolves to the module's default export.
Settings
The Settings API allows plugins to create new settings sections or add links to existing sections. Settings sections are organized groups of configuration pages accessible from the Settings menu item in the navigation sidebar.
Creating a new settings section
To create a new settings section, call addSettingsLink() with a section object ({ id, intlLabel }) as the first argument and an array of link objects as the second argument. This can be done in either the register or bootstrap lifecycle function:
- JavaScript
- TypeScript
export default {
register(app) {
app.addSettingsLink(
{
id: 'my-plugin',
intlLabel: {
id: 'my-plugin.settings.section-label',
defaultMessage: 'My Plugin Settings',
},
},
[
{
intlLabel: {
id: 'my-plugin.settings.general',
defaultMessage: 'General',
},
id: 'general',
to: 'my-plugin/general',
Component: () => import('./pages/Settings/General'),
},
{
intlLabel: {
id: 'my-plugin.settings.advanced',
defaultMessage: 'Advanced',
},
id: 'advanced',
to: 'my-plugin/advanced',
Component: () => import('./pages/Settings/Advanced'),
},
],
);
app.registerPlugin({
id: 'my-plugin',
name: 'My Plugin',
});
},
};
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
register(app: StrapiApp) {
app.addSettingsLink(
{
id: 'my-plugin',
intlLabel: {
id: 'my-plugin.settings.section-label',
defaultMessage: 'My Plugin Settings',
},
},
[
{
intlLabel: {
id: 'my-plugin.settings.general',
defaultMessage: 'General',
},
id: 'general',
to: 'my-plugin/general',
Component: () => import('./pages/Settings/General'),
},
{
intlLabel: {
id: 'my-plugin.settings.advanced',
defaultMessage: 'Advanced',
},
id: 'advanced',
to: 'my-plugin/advanced',
Component: () => import('./pages/Settings/Advanced'),
},
],
);
app.registerPlugin({
id: 'my-plugin',
name: 'My Plugin',
});
},
};
When used to create a new section, addSettingsLink() accepts the following parameters:
-
the first argument is the section configuration:
Parameter Type Required Description idstring✅ Unique identifier for the settings section intlLabelobject✅ Localized label for the section, following the React Int'l convention, with: id: id used to insert the localized labeldefaultMessage: default label for the section
-
the second argument is an array of link objects; each link object contains the following:
Parameter Type Required Description idstring✅ Unique identifier for the settings link tostring✅ Path relative to the settings route (do not include settings/prefix) (see additional information)intlLabelobject✅ Localized label object with idanddefaultMessagepermissionsArray<Permission>✅ Array of permission objects that control link visibility. Pass []for no restrictions.Componentfunction❌ Function that returns a dynamic import()of the settings page component. The page module must export the component asdefault. If omitted, no route is registered (label-only entry).positionnumber❌ Numeric position within the section (lower numbers appear first) licenseOnlyboolean❌ If true, displays a ⚡ icon (default:false)exactboolean❌ Whether the active-link match should be exact
createSettingSection()The dedicated app.createSettingSection(section, links) method is deprecated. It still works (it delegates to addSettingsLink internally) but new code should call addSettingsLink(section, links) directly. See Deprecated methods.
Adding links to existing settings sections
To add links to an existing settings section, use addSettingsLink() in the bootstrap() lifecycle function with a section id string as the first argument. The second argument can be either a single link object or an array of link objects — both forms are supported by the same method.
- JavaScript
- TypeScript
export default {
register(app) {
app.registerPlugin({
id: 'my-plugin',
name: 'My Plugin',
});
},
bootstrap(app) {
// Add a single link to the global settings section
app.addSettingsLink('global', {
intlLabel: {
id: 'my-plugin.settings.documentation',
defaultMessage: 'Documentation',
},
id: 'documentation',
to: 'my-plugin/documentation',
Component: () => import('./pages/Settings/Documentation'),
permissions: [],
licenseOnly: false,
});
// Add multiple links at once to the global settings section
app.addSettingsLink('global', [
{
intlLabel: {
id: 'my-plugin.settings.general',
defaultMessage: 'General',
},
id: 'general',
to: 'my-plugin/general',
Component: () => import('./pages/Settings/General'),
},
{
intlLabel: {
id: 'my-plugin.settings.advanced',
defaultMessage: 'Advanced',
},
id: 'advanced',
to: 'my-plugin/advanced',
Component: () => import('./pages/Settings/Advanced'),
},
]);
},
};
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
register(app: StrapiApp) {
app.registerPlugin({
id: 'my-plugin',
name: 'My Plugin',
});
},
bootstrap(app: StrapiApp) {
// Add a single link to the global settings section
app.addSettingsLink('global', {
intlLabel: {
id: 'my-plugin.settings.documentation',
defaultMessage: 'Documentation',
},
id: 'documentation',
to: 'my-plugin/documentation',
Component: () => import('./pages/Settings/Documentation'),
permissions: [],
licenseOnly: false,
});
// Add multiple links at once to the global settings section
app.addSettingsLink('global', [
{
intlLabel: {
id: 'my-plugin.settings.general',
defaultMessage: 'General',
},
id: 'general',
to: 'my-plugin/general',
Component: () => import('./pages/Settings/General'),
},
{
intlLabel: {
id: 'my-plugin.settings.advanced',
defaultMessage: 'Advanced',
},
id: 'advanced',
to: 'my-plugin/advanced',
Component: () => import('./pages/Settings/Advanced'),
},
]);
},
};
addSettingsLink takes a sectionId string as the first argument (e.g., 'global' or 'permissions'). The second argument is either a single link object or an array of link objects, using the same properties as the links array in Creating a new settings section.
addSettingsLinks()The plural app.addSettingsLinks(sectionId, links) method is deprecated. Call addSettingsLink(sectionId, links) (singular) with an array instead — it accepts both single links and arrays. See Deprecated methods.
Available settings sections
Strapi provides built-in settings sections that plugins can extend:
global: General application settingspermissions: Administration panel settings
Creating a new settings section is typically done in the register lifecycle function, while adding links to existing settings sections is done in bootstrap (because the target section may be registered by another plugin). Both forms call the same addSettingsLink() method, which is exposed on the app argument of register and as addSettingsLink in the bootstrap argument bag.
Path conventions for to
The to parameter behaves differently depending on the context:
| Context | to value | Final URL |
|---|---|---|
addMenuLink | /plugins/my-plugin | http://localhost:1337/admin/plugins/my-plugin |
addSettingsLink (with section object) | my-plugin/general | http://localhost:1337/admin/settings/my-plugin/general |
addSettingsLink (with section id) | my-plugin/documentation | http://localhost:1337/admin/settings/my-plugin/documentation |
For menu links, the path is relative to the admin panel root (/admin). For settings links, the path is relative to the settings route (/admin/settings). Do not include the settings/ prefix in settings link paths.
The permissions parameter on links only controls visibility in the navigation. To fully protect your plugin pages and register RBAC actions, see the Admin permissions for plugins guide.
Deprecated methods
The following methods on the StrapiApp instance are deprecated. They still work for backwards compatibility — both delegate to addSettingsLink() internally — but new code should use addSettingsLink() directly.
createSettingSection(section, links)
Previously used to register a new settings section and its initial links in one call.
// ❌ Deprecated
app.createSettingSection(
{ id: 'my-plugin', intlLabel: { id: 'my-plugin.settings.section-label', defaultMessage: 'My Plugin Settings' } },
[{ id: 'general', to: 'my-plugin/general', intlLabel: { id: 'my-plugin.settings.general', defaultMessage: 'General' }, Component: () => import('./pages/Settings/General') }],
);
// ✅ Replacement: pass the section object as the first argument to addSettingsLink
app.addSettingsLink(
{ id: 'my-plugin', intlLabel: { id: 'my-plugin.settings.section-label', defaultMessage: 'My Plugin Settings' } },
[{ id: 'general', to: 'my-plugin/general', intlLabel: { id: 'my-plugin.settings.general', defaultMessage: 'General' }, Component: () => import('./pages/Settings/General') }],
);
addSettingsLinks(sectionId, links)
Previously used to add multiple links to an existing section in one call. The singular addSettingsLink() now accepts either a single link object or an array.
// ❌ Deprecated
app.addSettingsLinks('global', [
{ id: 'general', to: 'my-plugin/general', intlLabel: { id: 'my-plugin.settings.general', defaultMessage: 'General' }, Component: () => import('./pages/Settings/General') },
{ id: 'advanced', to: 'my-plugin/advanced', intlLabel: { id: 'my-plugin.settings.advanced', defaultMessage: 'Advanced' }, Component: () => import('./pages/Settings/Advanced') },
]);
// ✅ Replacement: pass the array to addSettingsLink (singular)
app.addSettingsLink('global', [
{ id: 'general', to: 'my-plugin/general', intlLabel: { id: 'my-plugin.settings.general', defaultMessage: 'General' }, Component: () => import('./pages/Settings/General') },
{ id: 'advanced', to: 'my-plugin/advanced', intlLabel: { id: 'my-plugin.settings.advanced', defaultMessage: 'Advanced' }, Component: () => import('./pages/Settings/Advanced') },
]);
addSettingsLinksis still exposed on thebootstrapargument bag (alongsideaddSettingsLink,getPlugin, andregisterHook) for backwards compatibility.createSettingSectionis only reachable via the fullappinstance passed toregister(app)— it is not part of thebootstrapargument bag.- Both delegate to
addSettingsLink()internally and may be removed in a future major version. Migrate when convenient.