Admin Panel API: Injection zones
Page summary:
Injection zones are predefined areas in the admin UI where plugins can inject React components. Use
getPlugin('content-manager').injectComponent()to extend built-in views, or define your own zones withinjectionZonesinregisterPlugin.
Plugins can extend and customize existing admin panel sections by injecting custom React components into predefined areas. This allows you to add functionality to Strapi's built-in interfaces without modifying core code.
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
Injection zones are defined in the register lifecycle function, but components are injected in the bootstrap lifecycle function.
Injection zones vs. Content Manager APIs
For adding panels, actions, or buttons to the Content Manager, the Content Manager APIs (addDocumentAction, addEditViewSidePanel, etc.) are often more robust and better typed than injection zones. Use injection zones when you need to insert components into specific UI areas not covered by the Content Manager APIs.
Content Manager APIs and injection zones are both extension points to customize the admin panel, but they solve different needs:
| Need | Recommended API | Why |
|---|---|---|
| Add a custom panel in the Edit View side area | Content Manager API (addEditViewSidePanel) | Best for contextual information or controls that stay visible while editing. |
| Add actions in a document action menu | Content Manager API (addDocumentAction) | Best for document-level actions in the Edit View actions menu. |
| Add actions in the Edit View header | Content Manager API (addDocumentHeaderAction) | Best for quick, prominent actions next to the document title. |
| Add actions for selected entries in List View | Content Manager API (addBulkAction) | Best for workflows that apply to multiple entries at once. |
| Add UI to a predefined zone in a plugin view (localized visual customization) | Injection Zones API (injectComponent) | Best when you target a specific zone exposed by a plugin. |
For implementation details and up-to-date API signatures, please refer to the content-manager file in the Strapi codebase.
Predefined injection zones
Strapi's Content Manager provides predefined injection zones that plugins can use:
| View | Injection zone | Location |
|---|---|---|
| List view | listView.actions | Between the Filters and the cogs icon |
| List view | listView.publishModalAdditionalInfos | Informational content in the publish confirmation modal |
| List view | listView.unpublishModalAdditionalInfos | Informational content in the unpublish confirmation modal |
| List view | listView.deleteModalAdditionalInfos | Informational content in the delete confirmation modal |
| Edit view | editView.right-links | Between the "Configure the view" and "Edit" buttons |
| Edit view | editView.informations | In the informations box of the Edit view (internal, see note below) |
| Preview | preview.actions | In the preview view action area |
The listView.*ModalAdditionalInfos zones are intended to enrich the informational content displayed in publish, unpublish, and delete confirmation modals.
The editView.informations zone exists in the Content Manager source code but is considered internal. For third-party plugins, editView.right-links is the most stable and officially recommended Edit view extension point. Use editView.informations only when you specifically need the information panel area and accept potential UI changes between versions.
Injecting into Content Manager zones
To inject a component into a Content Manager injection zone, use getPlugin('content-manager').injectComponent() in the bootstrap lifecycle:
- JavaScript
- TypeScript
import { MyCustomButton } from './components/MyCustomButton';
import { PreviewAction } from './components/PreviewAction';
import { PublishModalInfo } from './components/PublishModalInfo';
export default {
register(app) {
app.registerPlugin({
id: 'my-plugin',
name: 'My Plugin',
});
},
bootstrap(app) {
// Inject a button into the Edit view's right-links zone
app
.getPlugin('content-manager')
.injectComponent('editView', 'right-links', {
name: 'my-plugin-custom-button',
Component: MyCustomButton,
});
// Inject a component into the List view's actions zone
app
.getPlugin('content-manager')
.injectComponent('listView', 'actions', {
name: 'my-plugin-list-action',
Component: () => <button>Custom List Action</button>,
});
// Inject additional information into the publish modal
app
.getPlugin('content-manager')
.injectComponent('listView', 'publishModalAdditionalInfos', {
name: 'my-plugin-publish-modal-info',
Component: PublishModalInfo,
});
// Inject a component into the Preview view's actions zone
app
.getPlugin('content-manager')
.injectComponent('preview', 'actions', {
name: 'my-plugin-preview-action',
Component: PreviewAction,
});
},
};
import type { StrapiApp } from '@strapi/admin/strapi-admin';
import { MyCustomButton } from './components/MyCustomButton';
import { PreviewAction } from './components/PreviewAction';
import { PublishModalInfo } from './components/PublishModalInfo';
export default {
register(app: StrapiApp) {
app.registerPlugin({
id: 'my-plugin',
name: 'My Plugin',
});
},
bootstrap(app: StrapiApp) {
// Inject a button into the Edit view's right-links zone
app
.getPlugin('content-manager')
.injectComponent('editView', 'right-links', {
name: 'my-plugin-custom-button',
Component: MyCustomButton,
});
// Inject a component into the List view's actions zone
app
.getPlugin('content-manager')
.injectComponent('listView', 'actions', {
name: 'my-plugin-list-action',
Component: () => <button>Custom List Action</button>,
});
// Inject additional information into the publish modal
app
.getPlugin('content-manager')
.injectComponent('listView', 'publishModalAdditionalInfos', {
name: 'my-plugin-publish-modal-info',
Component: PublishModalInfo,
});
// Inject a component into the Preview view's actions zone
app
.getPlugin('content-manager')
.injectComponent('preview', 'actions', {
name: 'my-plugin-preview-action',
Component: PreviewAction,
});
},
};
Custom injection zones
Plugins can define their own injection zones to allow other plugins to extend their UI. Declare injection zones in the registerPlugin configuration:
- JavaScript
- TypeScript
export default {
register(app) {
app.registerPlugin({
id: 'dashboard',
name: 'Dashboard',
injectionZones: {
homePage: {
top: [],
middle: [],
bottom: [],
},
sidebar: {
before: [],
after: [],
},
},
});
},
};
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
register(app: StrapiApp) {
app.registerPlugin({
id: 'dashboard',
name: 'Dashboard',
injectionZones: {
homePage: {
top: [],
middle: [],
bottom: [],
},
sidebar: {
before: [],
after: [],
},
},
});
},
};
Rendering injection zones in components
In Strapi 5, the InjectionZone component from @strapi/helper-plugin is removed and has no direct replacement export. To render injected components, create your own component with useStrapiApp from @strapi/strapi/admin.
- JavaScript
- TypeScript
import { useStrapiApp } from '@strapi/strapi/admin';
export const CustomInjectionZone = ({ area, ...props }) => {
const getPlugin = useStrapiApp('CustomInjectionZone', (state) => state.getPlugin);
const [pluginName, view, zone] = area.split('.');
const plugin = getPlugin(pluginName);
const components = plugin?.getInjectedComponents(view, zone);
if (!components?.length) {
return null;
}
return components.map(({ name, Component }) => <Component key={name} {...props} />);
};
import { CustomInjectionZone } from '../components/CustomInjectionZone';
const Dashboard = () => {
return (
<div>
<h1>Dashboard</h1>
{/* Render components injected into the top zone */}
<CustomInjectionZone area="dashboard.homePage.top" />
<div className="main-content">{/* Main dashboard content */}</div>
{/* Render components injected into the bottom zone */}
<CustomInjectionZone area="dashboard.homePage.bottom" />
</div>
);
};
export default Dashboard;
import { useStrapiApp } from '@strapi/strapi/admin';
type CustomInjectionZoneProps = {
area: `${string}.${string}.${string}`;
[key: string]: unknown;
};
export const CustomInjectionZone = ({ area, ...props }: CustomInjectionZoneProps) => {
const getPlugin = useStrapiApp('CustomInjectionZone', (state) => state.getPlugin);
const [pluginName, view, zone] = area.split('.');
const plugin = getPlugin(pluginName);
const components = plugin?.getInjectedComponents(view, zone);
if (!components?.length) {
return null;
}
return components.map(({ name, Component }) => <Component key={name} {...props} />);
};
import { CustomInjectionZone } from '../components/CustomInjectionZone';
const Dashboard = () => {
return (
<div>
<h1>Dashboard</h1>
{/* Render components injected into the top zone */}
<CustomInjectionZone area="dashboard.homePage.top" />
<div className="main-content">{/* Main dashboard content */}</div>
{/* Render components injected into the bottom zone */}
<CustomInjectionZone area="dashboard.homePage.bottom" />
</div>
);
};
export default Dashboard;
Injecting into custom zones
Other plugins can inject components into your custom injection zones using the injectComponent() method in their bootstrap lifecycle:
- JavaScript
- TypeScript
import { Widget } from './components/Widget';
export default {
register(app) {
app.registerPlugin({
id: 'widget-plugin',
name: 'Widget Plugin',
});
},
bootstrap(app) {
const dashboardPlugin = app.getPlugin('dashboard');
if (dashboardPlugin) {
dashboardPlugin.injectComponent('homePage', 'top', {
name: 'widget-plugin-statistics',
Component: Widget,
});
}
},
};
import type { StrapiApp } from '@strapi/admin/strapi-admin';
import { Widget } from './components/Widget';
export default {
register(app: StrapiApp) {
app.registerPlugin({
id: 'widget-plugin',
name: 'Widget Plugin',
});
},
bootstrap(app: StrapiApp) {
const dashboardPlugin = app.getPlugin('dashboard');
if (dashboardPlugin) {
dashboardPlugin.injectComponent('homePage', 'top', {
name: 'widget-plugin-statistics',
Component: Widget,
});
}
},
};
Injection component parameters
The injectComponent() method accepts the following parameters:
| Parameter | Type | Description |
|---|---|---|
view | string | The view name where the component should be injected |
zone | string | The zone name within the view where the component should be injected |
component | object | Configuration object with name (unique string) and Component (React component to inject) |
Content Manager data access
When injecting components into Content Manager injection zones, you can access the Edit View data using the useContentManagerContext hook:
- JavaScript
- TypeScript
import {
unstable_useContentManagerContext as useContentManagerContext,
} from '@strapi/strapi/admin';
export const MyCustomButton = () => {
const {
slug, // Content type slug (e.g., 'api::article.article')
model, // Content type model
id, // Document ID (undefined when creating)
collectionType, // 'single-types' or 'collection-types'
isCreatingEntry, // Whether creating a new entry
isSingleType, // Whether the content type is a single type
hasDraftAndPublish, // Whether draft & publish is enabled
contentType, // Content type schema
components, // Component schemas
layout, // Content type layout
form, // Form state and handlers
} = useContentManagerContext();
const { initialValues, values, onChange } = form;
const handleCustomAction = () => {
onChange({ target: { name: 'customField', value: 'new value' } });
};
return <button onClick={handleCustomAction}>Custom Action</button>;
};
import {
unstable_useContentManagerContext as useContentManagerContext,
} from '@strapi/strapi/admin';
export const MyCustomButton = () => {
const {
slug, // Content type slug (e.g., 'api::article.article')
model, // Content type model
id, // Document ID (undefined when creating)
collectionType, // 'single-types' or 'collection-types'
isCreatingEntry, // Whether creating a new entry
isSingleType, // Whether the content type is a single type
hasDraftAndPublish, // Whether draft & publish is enabled
contentType, // Content type schema
components, // Component schemas
layout, // Content type layout
form, // Form state and handlers
} = useContentManagerContext();
const { initialValues, values, onChange } = form;
const handleCustomAction = () => {
onChange({ target: { name: 'customField', value: 'new value' } });
};
return <button onClick={handleCustomAction}>Custom Action</button>;
};
The useContentManagerContext hook is currently exported as unstable_useContentManagerContext. The unstable_ prefix indicates the API may change in future releases. This hook replaces the deprecated useCMEditViewDataManager from @strapi/helper-plugin which is not available in Strapi 5.
Best practices
-
Use descriptive zone names. Choose clear names for your injection zones (e.g.,
top,bottom,before,after). -
Check plugin availability. Always verify that a plugin exists before injecting components into its zones:
bootstrap(app) {
const targetPlugin = app.getPlugin('target-plugin');
if (targetPlugin) {
targetPlugin.injectComponent('view', 'zone', {
name: 'my-component',
Component: MyComponent,
});
}
} -
Use unique component names. Ensure component names are unique to avoid conflicts with other plugins.
-
Handle missing zones gracefully. Components should handle cases where injection zones might not be available.
-
Document your injection zones. Clearly document which injection zones your plugin provides and their intended use.