From 4428a0039d5739ff2cc7fe352cdb53cb95eac4ba Mon Sep 17 00:00:00 2001 From: marcoautiero Date: Fri, 23 Jan 2026 18:29:36 +0100 Subject: [PATCH 01/64] adding an admin configuration and customization page --- .../admin-configuration-customization.md | 1103 +++++++++++++++++ .../plugins-development/admin-panel-api.md | 2 +- docusaurus/sidebars.js | 1 + 3 files changed, 1105 insertions(+), 1 deletion(-) create mode 100644 docusaurus/docs/cms/plugins-development/admin-configuration-customization.md diff --git a/docusaurus/docs/cms/plugins-development/admin-configuration-customization.md b/docusaurus/docs/cms/plugins-development/admin-configuration-customization.md new file mode 100644 index 0000000000..616e0bfee0 --- /dev/null +++ b/docusaurus/docs/cms/plugins-development/admin-configuration-customization.md @@ -0,0 +1,1103 @@ +--- +title: Admin Configuration & Customization +pagination_prev: cms/plugins-development/plugin-sdk +pagination_next: cms/plugins-development/admin-panel-api +displayed_sidebar: cmsSidebar +toc_max_heading_level: 4 +tags: + - admin panel + - plugin configuration + - plugin customization + - registerPlugin + - plugin APIs + - plugin lifecycle + - plugins development +--- + +# Admin Configuration & Customization + +This section covers advanced configuration and customization options for Strapi plugins in the admin panel. Learn how to configure plugins, expose APIs, manage initialization, and customize the admin interface. + +:::prerequisites +You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin). +::: + +## Overview + +The admin panel entry file (`admin/src/index.ts`) exports an object with lifecycle functions that control how your plugin integrates with Strapi's admin interface. The entry file can export three functions: + +### Available Exports + +| Function | Type | Description | +| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `register` | Required | Lifecycle function called to load the plugin before the app is bootstrapped. Use this to register the plugin, add menu links, create settings sections, define injection zones, and add reducers. | +| `bootstrap` | Optional | Lifecycle function executed after all plugins are registered. Use this to extend other plugins, register hooks, add settings links, inject components into injection zones, and add Content Manager actions. | +| `registerTrads` | Required | Async function to register translation files for all locales. This creates separate chunks for application translations to reduce build size. | + +## Base Configuration + +The `registerPlugin` method is the core function for registering a plugin in the admin panel. This method is called within the `register` lifecycle function of your plugin's entry file (`admin/src/index.ts`) and configures the main options to integrate the plugin with Strapi's admin interface. + +```typescript +// admin/src/index.ts +export default { + register(app) { + app.registerPlugin({ + id: 'my-plugin', + name: 'My Plugin', + apis: { + // APIs exposed to other plugins + }, + initializer: MyInitializerComponent, + injectionZones: { + // Areas where other plugins can inject components + }, + isReady: false, // Plugin readiness status + }); + }, +}; +``` + +### Configuration Options + +| Parameter | Type | Required | Description | +| ---------------- | ------------------------- | -------- | ----------------------------------------- | +| `id` | `string` | ✅ | Unique plugin identifier | +| `name` | `string` | ✅ | Plugin display name | +| `apis` | `Record` | ❌ | APIs exposed to other plugins | +| `initializer` | `React.ComponentType` | ❌ | Component for plugin initialization | +| `injectionZones` | `object` | ❌ | Available injection zones | +| `isReady` | `boolean` | ❌ | Plugin readiness status (default: `true`) | + +## Admin Localization + +Plugins can provide translations for multiple languages to make the admin interface accessible to users worldwide. Strapi automatically loads and merges plugin translations with core translations, making them available throughout the admin panel. + +### Translation File Structure + +Translation files are stored in the `admin/src/translations/` directory, with one JSON file per locale: + +``` +admin/src/translations/ + ├── en.json + ├── fr.json + ├── de.json + └── ... +``` + +Each translation file contains key-value pairs where keys are translation identifiers and values are the translated strings: + +```json +// admin/src/translations/en.json +{ + "plugin.name": "My Plugin", + "plugin.description": "A custom Strapi plugin", + "settings.title": "Settings", + "settings.general": "General", + "settings.advanced": "Advanced" +} +``` + +### The `registerTrads` Function + +The `registerTrads` function is a required lifecycle function that loads translation files for all configured locales. Strapi calls this function during the admin panel initialization to collect translations from all plugins. + +#### Basic Implementation + +```typescript +// admin/src/index.ts +import { prefixPluginTranslations } from './utils/prefixPluginTranslations'; +import { PLUGIN_ID } from './pluginId'; + +export default { + register(app) { + app.registerPlugin({ + id: PLUGIN_ID, + name: 'My Plugin', + }); + }, + async registerTrads({ locales }) { + const importedTranslations = await Promise.all( + locales.map((locale) => { + return import(`./translations/${locale}.json`) + .then(({ default: data }) => { + return { + data: prefixPluginTranslations(data, PLUGIN_ID), + locale, + }; + }) + .catch(() => { + return { + data: {}, + locale, + }; + }); + }), + ); + + return importedTranslations; + }, +}; +``` + +#### Function Parameters + +The `registerTrads` function receives an object with the following property: + +| Parameter | Type | Description | +| --------- | ---------- | -------------------------------------------------------------------------------- | +| `locales` | `string[]` | Array of locale codes configured in the admin panel (e.g., `['en', 'fr', 'de']`) | + +#### Return Value + +The function must return a `Promise` that resolves to an array of translation objects. Each object has the following structure: + +```typescript +{ + data: Record; // Translation key-value pairs + locale: string; // Locale code (e.g., 'en', 'fr') +} +``` + +#### Translation Key Prefixing + +**Important**: Translation keys must be prefixed with your plugin ID to avoid conflicts with other plugins and core Strapi translations. For example, if your plugin ID is `my-plugin`, a key like `plugin.name` should become `my-plugin.plugin.name`. + +Use the `prefixPluginTranslations` utility function to automatically prefix all keys: + +```typescript +// admin/src/utils/prefixPluginTranslations.ts +type TradOptions = Record; + +const prefixPluginTranslations = ( + trad: TradOptions, + pluginId: string, +): TradOptions => { + if (!pluginId) { + throw new TypeError("pluginId can't be empty"); + } + return Object.keys(trad).reduce((acc, current) => { + acc[`${pluginId}.${current}`] = trad[current]; + return acc; + }, {} as TradOptions); +}; + +export { prefixPluginTranslations }; +``` + +**Example**: If your translation file contains: + +```json +{ + "plugin.name": "My Plugin", + "settings.title": "Settings" +} +``` + +After prefixing with plugin ID `my-plugin`, these become: + +- `my-plugin.plugin.name` +- `my-plugin.settings.title` + +#### Handling Missing Translation Files + +The `registerTrads` function should gracefully handle missing translation files by returning an empty object for that locale. The `.catch()` handler in the example above ensures that if a translation file doesn't exist, the plugin still returns a valid translation object: + +```typescript +.catch(() => { + return { + data: {}, + locale, + }; +}); +``` + +This allows plugins to provide translations for only some locales (e.g., only English) without breaking the admin panel for other locales. + +### Using Translations in Components + +To use translations in your React components, use the `useIntl` hook from `react-intl`: + +```typescript +// admin/src/pages/HomePage.tsx +import { useIntl } from 'react-intl'; +import { PLUGIN_ID } from '../pluginId'; + +const HomePage = () => { + const { formatMessage } = useIntl(); + + return ( +
+

+ {formatMessage({ + id: `${PLUGIN_ID}.plugin.name`, + defaultMessage: 'My Plugin', + })} +

+

+ {formatMessage({ + id: `${PLUGIN_ID}.plugin.description`, + defaultMessage: 'A custom Strapi plugin', + })} +

+
+ ); +}; + +export default HomePage; +``` + +#### Helper Function for Translation Keys + +To avoid repeating the plugin ID prefix, create a helper function: + +```typescript +// admin/src/utils/getTranslation.ts +import { PLUGIN_ID } from '../pluginId'; + +export const getTranslation = (id: string) => `${PLUGIN_ID}.${id}`; +``` + +Then use it in components: + +```typescript +// admin/src/pages/HomePage.tsx +import { useIntl } from 'react-intl'; +import { getTranslation } from '../utils/getTranslation'; + +const HomePage = () => { + const { formatMessage } = useIntl(); + + return ( +
+

+ {formatMessage({ + id: getTranslation('plugin.name'), + defaultMessage: 'My Plugin', + })} +

+
+ ); +}; +``` + +### Using Translations in Configuration + +Translation keys are also used when configuring menu links, settings sections, and other admin panel elements: + +```typescript +// admin/src/index.ts +export default { + register(app) { + app.addMenuLink({ + to: '/plugins/my-plugin', + icon: PluginIcon, + intlLabel: { + id: 'my-plugin.plugin.name', // Prefixed translation key + defaultMessage: 'My Plugin', // Fallback if translation missing + }, + Component: async () => { + const { App } = await import('./pages/App'); + return App; + }, + }); + }, +}; +``` + +### How Strapi Integrates Plugin Translations + +Strapi's admin panel automatically: + +1. **Calls `registerTrads`** for all registered plugins during initialization +2. **Merges translations** from all plugins with core Strapi translations +3. **Applies custom translations** from the admin configuration (if any) +4. **Makes translations available** via `react-intl` throughout the admin panel + +The merge order ensures that: + +- Core Strapi translations are loaded first +- Plugin translations are merged on top +- Custom translations from admin config override both (allowing users to customize translations) + +### Best Practices + +1. **Always prefix translation keys**: Use `prefixPluginTranslations` or manually prefix keys with your plugin ID to avoid conflicts. + +2. **Provide default messages**: Always include `defaultMessage` when using `formatMessage` as a fallback if translations are missing. + +3. **Handle missing translations gracefully**: The `registerTrads` function should return empty objects for missing locales rather than throwing errors. + +4. **Use descriptive key names**: Choose clear, hierarchical key names (e.g., `settings.general.title` rather than `title1`). + +5. **Support at least English**: While not required, providing English translations ensures your plugin works out of the box. + +6. **Test with multiple locales**: Verify that your plugin works correctly when different locales are selected in the admin panel. + +:::note +The `en` locale is always available in Strapi and serves as the fallback locale. If a translation is missing for a selected locale, Strapi will use the English translation. +::: + +:::tip +To see which locales are available in your Strapi instance, check the `config.locales` array in your `src/admin/app.ts` or `src/admin/app.js` file. You can also access available locales from the Redux store using `useSelector((state) => state.admin_app?.language?.localeNames)`. +::: + +## Customizing Menu and Settings + +Plugins can customize the admin panel's navigation sidebar and settings pages to provide easy access to their features. This section covers how to add menu links to the main navigation and how to create or extend settings sections. + +### 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, which should be called within the `register` lifecycle function. + +#### Adding a Menu Link + +```typescript +// admin/src/index.ts +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: async () => { + const { App } = await import('./pages/App'); + return 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', + }); + }, +}; +``` + +#### Menu Link Parameters + +| Parameter | Type | Required | Description | +| ------------- | ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `to` | `string` | ✅ | Path the link should point to (relative to the admin panel root) | +| `icon` | `React.Component` | ✅ | React component for the icon to display in the navigation | +| `intlLabel` | `object` | ✅ | Localized label object with:
  • `id`: Translation key used in translation files
  • `defaultMessage`: Default label if translation is missing
| +| `Component` | `async function` | ✅ | Async function that returns a dynamic import of the plugin's main page component | +| `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | +| `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`) | + +:::note +The `intlLabel.id` values should correspond to keys in your translation files located at `admin/src/translations/[locale].json`. +::: + +### 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 with multiple links, use `createSettingSection()` in the `register` lifecycle function: + +```typescript +// admin/src/index.ts +import type { StrapiApp } from '@strapi/admin/strapi-admin'; +import { PERMISSIONS } from './constants'; + +export default { + register(app: StrapiApp) { + app.createSettingSection( + { + 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: async () => { + const { GeneralSettings } = + await import('./pages/Settings/General'); + return GeneralSettings; + }, + }, + { + intlLabel: { + id: 'my-plugin.settings.advanced', + defaultMessage: 'Advanced', + }, + id: 'advanced', + to: 'my-plugin/advanced', + Component: async () => { + const { AdvancedSettings } = + await import('./pages/Settings/Advanced'); + return AdvancedSettings; + }, + }, + ], + ); + + app.registerPlugin({ + id: 'my-plugin', + name: 'My Plugin', + }); + }, +}; +``` + +#### createSettingSection Parameters + +**Function Signature:** + +```typescript +createSettingSection( + sectionConfig: { id: string; intlLabel: { id: string; defaultMessage: string } }, + links: Array +) +``` + +**First Argument - `sectionConfig` (object):** + +| Parameter | Type | Required | Description | +| ----------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | `string` | ✅ | Unique identifier for the settings section | +| `intlLabel` | `object` | ✅ | Localized label object with:
  • `id` (string): Translation key used in translation files
  • `defaultMessage` (string): Default label if translation is missing
| + +**Second Argument - `links` (array):** + +An array of link configuration objects, where each object has the following properties: + +| Parameter | Type | Required | Description | +| ------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------ | +| `id` | `string` | ✅ | Unique identifier for the settings link | +| `to` | `string` | ✅ | Path relative to the settings route (should not include `settings/` prefix) | +| `intlLabel` | `object` | ✅ | Localized label object with `id` and `defaultMessage` | +| `Component` | `async function` | ✅ | Async function that returns a dynamic import of the settings page component | +| `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | +| `licenseOnly` | `boolean` | ❌ | If `true`, displays a ⚡ icon to indicate the feature requires a paid license (default: `false`) | + +#### Adding Links to Existing Settings Sections + +To add links to existing settings sections (like the "Global" section), use `addSettingsLink()` in the `bootstrap` lifecycle function: + +```typescript +// admin/src/index.ts +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 link to the global settings section + app.addSettingsLink('global', { + intlLabel: { + id: 'my-plugin.settings.documentation', + defaultMessage: 'Documentation', + }, + id: 'documentation', + to: 'my-plugin/documentation', + Component: async () => { + const { DocumentationPage } = + await import('./pages/Settings/Documentation'); + return DocumentationPage; + }, + permissions: [], + licenseOnly: false, + }); + }, +}; +``` + +#### addSettingsLink Parameters + +**Function Signature:** + +```typescript +addSettingsLink( + sectionId: string, + linkConfig: LinkConfig +) +``` + +**First Argument - `sectionId` (string):** + +The ID of the existing settings section to extend (e.g., `'global'` or `'permissions'`). + +**Second Argument - `linkConfig` (object):** + +A link configuration object with the following properties: + +| Parameter | Type | Required | Description | +| ------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------ | +| `id` | `string` | ✅ | Unique identifier for the settings link | +| `to` | `string` | ✅ | Path relative to the settings route (should not include `settings/` prefix) | +| `intlLabel` | `object` | ✅ | Localized label object with `id` and `defaultMessage` | +| `Component` | `async function` | ✅ | Async function that returns a dynamic import of the settings page component | +| `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | +| `licenseOnly` | `boolean` | ❌ | If `true`, displays a ⚡ icon to indicate the feature requires a paid license (default: `false`) | + +#### Available Settings Sections + +Strapi provides several built-in settings sections that plugins can extend: + +- **`global`**: General application settings +- **`permissions`**: Administration panel settings + +:::note + +- Creating a new settings section must be done in the `register` lifecycle function +- Adding links to existing settings sections must be done in the `bootstrap` lifecycle function +- The `to` path for settings links should be relative to the settings route (e.g., `'my-plugin/general'` not `'settings/my-plugin/general'`) + ::: + +## Customizing Existing Admin Sections + +Plugins can extend and customize existing admin panel sections, such as the Content Manager's List and Edit views, by injecting custom React components into predefined areas. This allows you to add functionality to Strapi's built-in interfaces without modifying core code. + +### What are Injection Zones? + +Injection zones are the mechanism that enables this customization. They are predefined areas in a plugin's UI where other plugins can inject custom React components. + +:::note +Injection zones are defined in the `register` lifecycle function, but components are injected in the `bootstrap` lifecycle function. +::: + +### Predefined Injection Zones + +Strapi's Content Manager provides predefined injection zones that plugins can use to add components to the Content Manager interface: + +| View | Injection Zone | Location | +| --------- | ---------------------- | --------------------------------------------------- | +| List view | `listView.actions` | Between the Filters and the cogs icon | +| Edit view | `editView.right-links` | Between the "Configure the view" and "Edit" buttons | +| Preview | `preview.actions` | In the preview view action area | + +#### Injecting into Content Manager Zones + +To inject a component into a Content Manager injection zone, use `getPlugin('content-manager').injectComponent()` in the `bootstrap` lifecycle of your plugin: + +```typescript +// admin/src/index.ts +import type { StrapiApp } from '@strapi/admin/strapi-admin'; +import { MyCustomButton } from './components/MyCustomButton'; +import { PreviewAction } from './components/PreviewAction'; + +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: () => , + }); + + // Inject a component into the Preview view's actions zone + app.getPlugin('content-manager').injectComponent('preview', 'actions', { + name: 'my-plugin-preview-action', + Component: PreviewAction, + }); + }, +}; +``` + +### Creating Custom Injection Zones + +Plugins can define their own injection zones to allow other plugins to extend their UI. Injection zones are declared in the `registerPlugin` configuration: + +```typescript +// admin/src/index.ts +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + register(app: StrapiApp) { + app.registerPlugin({ + id: 'dashboard', + name: 'Dashboard', + injectionZones: { + 'dashboard.main': { + top: [], // Components injected at the top + middle: [], // Components injected in the middle + bottom: [], // Components injected at the bottom + }, + 'dashboard.sidebar': { + before: [], // Components injected before sidebar content + after: [], // Components injected after sidebar content + }, + }, + }); + }, +}; +``` + +#### Using Injection Zones in Components + +To render injected components in your plugin's UI, use the `` component from `@strapi/helper-plugin`: + +```typescript +// admin/src/pages/Dashboard.tsx +import { InjectionZone } from '@strapi/helper-plugin'; + +const Dashboard = () => { + return ( +
+

Dashboard

+ + {/* Render components injected into the top zone */} + + +
{/* Main dashboard content */}
+ + {/* Render components injected into the bottom zone */} + +
+ ); +}; + +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: + +```typescript +// Another plugin's admin/src/index.ts +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) { + // Get the dashboard plugin and inject a widget + const dashboardPlugin = app.getPlugin('dashboard'); + + if (dashboardPlugin) { + dashboardPlugin.injectComponent('dashboard.main', '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` | Component configuration object with:
  • `name` (string): Unique name for the component
  • `Component` (React.ComponentType): The React component to inject
| + +### Accessing Content Manager Data + +When injecting components into Content Manager injection zones, you can access the Edit View data using the `useCMEditViewDataManager` hook: + +```typescript +// admin/src/components/MyCustomButton.tsx +import { useCMEditViewDataManager } from '@strapi/helper-plugin'; + +export const MyCustomButton = () => { + const { + slug, // Content type slug (e.g., 'api::article.article') + modifiedData, // Current form data + initialData, // Original data + isCreatingEntry, // Whether creating a new entry + layout, // Content type layout + onPublish, // Function to publish the entry + onChange, // Function to update field values + } = useCMEditViewDataManager(); + + const handleCustomAction = () => { + // Access and modify the entry data + onChange({ target: { name: 'customField', value: 'new value' } }); + }; + + return ; +}; +``` + +### Best Practices + +1. **Use descriptive zone names**: Choose clear, descriptive names for your injection zones (e.g., `top`, `bottom`, `before`, `after`) + +2. **Check plugin availability**: Always verify that a plugin exists before injecting components into its zones: + + ```typescript + bootstrap(app: StrapiApp) { + const targetPlugin = app.getPlugin('target-plugin'); + if (targetPlugin) { + targetPlugin.injectComponent('view', 'zone', { + name: 'my-component', + Component: MyComponent, + }); + } + } + ``` + +3. **Use unique component names**: Ensure component names are unique to avoid conflicts with other plugins + +4. **Handle missing zones gracefully**: Components should handle cases where injection zones might not be available + +5. **Document your injection zones**: Clearly document which injection zones your plugin provides and their intended use + +## Accessing the Redux Store + +Strapi's admin panel uses a global Redux store to manage application state. Plugins can access this store to read state, dispatch actions, and subscribe to state changes. This enables plugins to interact with core admin functionality like theme settings, language preferences, and authentication state. + +### Store Overview + +The Redux store is automatically provided to all plugin components through React Redux's `Provider`. The store contains several slices: + +- **`admin_app`**: Core admin state including theme, language, permissions, and authentication token +- **`adminApi`**: RTK Query API state for admin endpoints +- **Plugin-specific slices**: Additional reducers added by plugins + +### Reading State with `useSelector` + +The most common way to access Redux state in your plugin components is using the `useSelector` hook from `react-redux`: + +```typescript +// admin/src/pages/HomePage.tsx +import { useSelector } from 'react-redux'; + +const HomePage = () => { + // Read current theme + const currentTheme = useSelector( + (state: any) => state.admin_app?.theme?.currentTheme + ); + + // Read current locale + const currentLocale = useSelector( + (state: any) => state.admin_app?.language?.locale + ); + + // Read authentication status + const isAuthenticated = useSelector((state: any) => !!state.admin_app?.token); + + // Read available locales + const availableLocales = useSelector( + (state: any) => state.admin_app?.language?.localeNames || {} + ); + + return ( +
+

Current Theme: {currentTheme}

+

Current Locale: {currentLocale}

+

Authenticated: {isAuthenticated ? 'Yes' : 'No'}

+
+ ); +}; +``` + +#### Available State Properties + +The `admin_app` slice contains the following state properties: + +| Property | Type | Description | +| ----------------------- | ---------------- | -------------------------------------------------- | +| `theme.currentTheme` | `string` | Current theme (`'light'`, `'dark'`, or `'system'`) | +| `theme.availableThemes` | `string[]` | Array of available theme names | +| `language.locale` | `string` | Current locale code (e.g., `'en'`, `'fr'`) | +| `language.localeNames` | `object` | Object mapping locale codes to display names | +| `token` | `string \| null` | Authentication token | +| `permissions` | `object` | User permissions object | + +### Dispatching Actions + +To update the Redux store, use the `useDispatch` hook to dispatch actions: + +```typescript +// admin/src/pages/HomePage.tsx +import { useDispatch } from 'react-redux'; + +const HomePage = () => { + const dispatch = useDispatch(); + const currentTheme = useSelector( + (state: any) => state.admin_app?.theme?.currentTheme + ); + + const handleToggleTheme = () => { + // Dispatch action using Redux Toolkit action type format + const newTheme = + currentTheme === 'light' + ? 'dark' + : currentTheme === 'dark' + ? 'system' + : 'light'; + dispatch({ + type: 'admin/setAppTheme', + payload: newTheme, + } as any); + }; + + const handleChangeLocale = (locale: string) => { + dispatch({ + type: 'admin/setLocale', + payload: locale, + } as any); + }; + + return ( +
+ + +
+ ); +}; +``` + +#### Available Actions + +The `admin_app` slice provides the following actions: + +| Action Type | Payload Type | Description | +| ------------------- | -------------------------------------- | -------------------------------------------------- | +| `admin/setAppTheme` | `string` | Set the theme (`'light'`, `'dark'`, or `'system'`) | +| `admin/setLocale` | `string` | Set the locale (e.g., `'en'`, `'fr'`) | +| `admin/setToken` | `string \| null` | Set the authentication token | +| `admin/login` | `{ token: string, persist?: boolean }` | Login action with token and persistence option | +| `admin/logout` | `void` | Logout action (no payload) | + +:::note +When dispatching actions, use the Redux Toolkit action type format: `'sliceName/actionName'`. The admin slice is named `'admin'`, so actions follow the pattern `'admin/actionName'`. +::: + +### Accessing the Store Instance + +For advanced use cases, you can access the store instance directly using the `useStore` hook: + +```typescript +// admin/src/pages/App.tsx +import { useStore } from 'react-redux'; +import { useEffect } from 'react'; + +const App = () => { + const store = useStore(); + + useEffect(() => { + // Get current state + const state = store.getState(); + console.log('Redux Store State:', state); + + // Subscribe to store changes + const unsubscribe = store.subscribe(() => { + const currentState = store.getState(); + console.log('Store state changed:', { + theme: currentState.admin_app?.theme?.currentTheme, + locale: currentState.admin_app?.language?.locale, + timestamp: new Date().toISOString(), + }); + }); + + // Cleanup subscription on unmount + return () => { + unsubscribe(); + }; + }, [store]); + + return
My Plugin
; +}; +``` + +### Complete Example + +Here's a complete example demonstrating all three Redux store access patterns: + +```typescript +// admin/src/pages/HomePage.tsx +import { Main } from '@strapi/design-system'; +import { Button, Box, Typography, Flex } from '@strapi/design-system'; +import { useSelector, useDispatch, useStore } from 'react-redux'; +import { useEffect, useState } from 'react'; + +const HomePage = () => { + const dispatch = useDispatch(); + const store = useStore(); + + // Example 1: Reading state from Redux store using useSelector + const currentTheme = useSelector( + (state: any) => state.admin_app?.theme?.currentTheme + ); + const currentLocale = useSelector( + (state: any) => state.admin_app?.language?.locale + ); + const isAuthenticated = useSelector((state: any) => !!state.admin_app?.token); + const availableLocales = useSelector( + (state: any) => state.admin_app?.language?.localeNames || {} + ); + + // Example 2: Dispatching actions to update the store + const handleToggleTheme = () => { + const newTheme = + currentTheme === 'light' + ? 'dark' + : currentTheme === 'dark' + ? 'system' + : 'light'; + dispatch({ + type: 'admin/setAppTheme', + payload: newTheme, + } as any); + }; + + const handleChangeLocale = (locale: string) => { + dispatch({ + type: 'admin/setLocale', + payload: locale, + } as any); + }; + + // Example 3: Subscribing to store changes + const [storeChangeCount, setStoreChangeCount] = useState(0); + const [lastChange, setLastChange] = useState(''); + + useEffect(() => { + const unsubscribe = store.subscribe(() => { + const state = store.getState(); + setStoreChangeCount((prev) => prev + 1); + setLastChange(new Date().toLocaleTimeString()); + + console.log('Store state changed:', { + theme: state.admin_app?.theme?.currentTheme, + locale: state.admin_app?.language?.locale, + timestamp: new Date().toISOString(), + }); + }); + + return () => { + unsubscribe(); + }; + }, [store]); + + return ( +
+ + + Redux Store Examples + + + + {/* Example 1: Reading State */} + + + Example 1: Reading State + + + + Current Theme: {currentTheme || 'system'} + + + Current Locale: {currentLocale || 'en'} + + + Authentication Status:{' '} + + {isAuthenticated ? 'Authenticated' : 'Not Authenticated'} + + + + + + {/* Example 2: Dispatching Actions */} + + + Example 2: Dispatching Actions + + + + {Object.keys(availableLocales).map((locale) => ( + + ))} + + + + {/* Example 3: Subscribing to Store Changes */} + + + Example 3: Subscribing to Store Changes + + + + Store has changed {storeChangeCount} time(s) + + {lastChange && ( + + Last change at: {lastChange} + + )} + + + + +
+ ); +}; + +export { HomePage }; +``` + +### Best Practices + +1. **Use `useSelector` for reading state**: Prefer `useSelector` over direct store access for reading state, as it automatically subscribes to updates and re-renders components when the selected state changes. + +2. **Clean up subscriptions**: Always unsubscribe from store subscriptions in `useEffect` cleanup functions to prevent memory leaks. + +3. **Type safety**: For better TypeScript support, consider creating typed selectors or using the typed hooks from `@strapi/admin` if available. + +4. **Avoid unnecessary dispatches**: Only dispatch actions when you need to update state. Reading state doesn't require dispatching actions. + +5. **Respect core state**: Be careful when modifying core admin state (like theme or locale) as it affects the entire admin panel. Consider whether your plugin should modify global state or maintain its own local state. + +:::tip +For plugins that need to add their own state to the Redux store, use the `addReducers` method in the `register` lifecycle function to inject custom reducers. See the [Admin Panel API documentation](/cms/plugins-development/admin-panel-api) for more details. +::: diff --git a/docusaurus/docs/cms/plugins-development/admin-panel-api.md b/docusaurus/docs/cms/plugins-development/admin-panel-api.md index ae4e04691e..e05840d6b5 100644 --- a/docusaurus/docs/cms/plugins-development/admin-panel-api.md +++ b/docusaurus/docs/cms/plugins-development/admin-panel-api.md @@ -1,6 +1,6 @@ --- title: Admin Panel API -pagination_prev: cms/plugins-development/plugin-structure +pagination_prev: cms/plugins-development/admin-configuration-customization pagination_next: cms/plugins-development/content-manager-apis toc_max_heading_level: 4 tags: diff --git a/docusaurus/sidebars.js b/docusaurus/sidebars.js index 03b7a41e58..805a4da8f8 100644 --- a/docusaurus/sidebars.js +++ b/docusaurus/sidebars.js @@ -510,6 +510,7 @@ const sidebars = { 'cms/plugins-development/create-a-plugin', 'cms/plugins-development/plugin-structure', 'cms/plugins-development/plugin-sdk', + 'cms/plugins-development/admin-configuration-customization', 'cms/plugins-development/admin-panel-api', 'cms/plugins-development/content-manager-apis', 'cms/plugins-development/server-api', From ad913e469c0d729a9369a5d2141fd82e81ba7802 Mon Sep 17 00:00:00 2001 From: marcoautiero Date: Fri, 23 Jan 2026 18:29:55 +0100 Subject: [PATCH 02/64] refreshing the create a plugin page --- .../plugins-development/create-a-plugin.md | 211 +++++++++--------- 1 file changed, 105 insertions(+), 106 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/create-a-plugin.md b/docusaurus/docs/cms/plugins-development/create-a-plugin.md index 15e51f1765..c6d1e6cc6a 100644 --- a/docusaurus/docs/cms/plugins-development/create-a-plugin.md +++ b/docusaurus/docs/cms/plugins-development/create-a-plugin.md @@ -11,33 +11,25 @@ tags: # Plugin creation - -The Plugin SDK generates plugins without a Strapi project and links them to an existing app with `watch:link` and yalc. In this documentation: build and verify commands to bundle the plugin for npm or marketplace publishing, and information for monorepos and local setups. - +This guide walks you through the complete process of creating, developing, and publishing Strapi 5 plugins that can be used locally or distributed via NPM and the Marketplace. The Plugin SDK is the recommended way to build plugins, providing a comprehensive toolkit from initial setup to publishing. -There are many ways to create a Strapi 5 plugin, but the fastest and recommended way is to use the Plugin SDK. +The complete list of commands and their parameters are available in the [Plugin SDK reference](/cms/plugins-development/plugin-sdk). This page will guide you through using the main ones. -The Plugin SDK is a set of commands orientated around developing plugins to use them as local plugins or to publish them on NPM and/or submit them to the Marketplace. +Before proceeding with plugin development, it's important to understand the difference between local and external plugins: -With the Plugin SDK, you do not need to set up a Strapi project before creating a plugin. +- **Local plugins**: Plugins that are developed within your Strapi project. By default, if you're within a Strapi project, the plugin will be placed within the `src/plugins` folder with the new folder corresponding to the plugin name. If a `plugins` folder already exists, the new plugin code will be placed there. +- **External plugins**: Plugins that are developed outside your Strapi project (in a separate directory). -The present guide covers creating a plugin from scratch, linking it to an existing Strapi project, and publishing the plugin. If you already have an existing plugin, you can instead retrofit the plugin setup to utilise the Plugin SDK commands (please refer to the [Plugin SDK reference](/cms/plugins-development/plugin-sdk) for a full list of available commands). +The Plugin SDK can create both types of plugins. If you run the Plugin SDK command from within an existing Strapi project, it will create a local plugin. If you run it from outside a Strapi project, it will create an external plugin. The development process differs slightly between the two approaches, as outlined in the sections below. -:::note -This guide assumes you want to develop a plugin external to your Strapi project. However, the steps largely remain the same if you want to develop a plugin within your existing project. If you are not [using a monorepo](#monorepo) the steps are exactly the same. -::: - -:::prerequisites - must be installed globally (with `npm install -g yalc` or `yarn global add yalc`). -::: +In addition to the plugin location, your Strapi project and plugin can be organized in different repository setups, which affects the development workflow: -## Getting started with the Plugin SDK +- **Separate repositories**: Your Strapi project and plugin are in different repositories. This is common for external plugins that you plan to publish or share across multiple projects. +- **Monorepo setup**: Your Strapi project and plugin are in the same repository, often using workspace management tools like Yarn workspaces or npm workspaces. This is useful when you want to maintain everything in one place. -The Plugin SDK helps you creating a plugin, linking it to an existing Strapi project, and building it for publishing. +The development workflow and commands you use will vary based on both the plugin type (local vs external) and the repository setup (separate vs monorepo), as detailed in the sections below. -The full list of commands and their parameters are available in the [Plugin SDK reference](/cms/plugins-development/plugin-sdk). The present page will guide on using the main ones. - -### Creating the plugin +## Creating the plugin To create your plugin, ensure you are in the parent directory of where you want it to be created and run the following command: @@ -61,25 +53,47 @@ npx @strapi/sdk-plugin init my-strapi-plugin -The path `my-strapi-plugin` can be replaced with whatever you want to call your plugin, including the path to where it should be created (e.g., `code/strapi-plugins/my-new-strapi-plugin`). +The `my-strapi-plugin` part can be replaced with whatever you want to call your plugin, including the path to where it should be created (e.g., `code/strapi-plugins/my-new-strapi-plugin`). -You will be ran through a series of prompts to help you setup your plugin. If you selected yes to all options the final structure will be similar to the default [plugin structure](/cms/plugins-development/plugin-structure). +You will be guided through a series of prompts to help you set up your plugin. If you select "yes" to all options, the final structure will be similar to the default [plugin structure](/cms/plugins-development/plugin-structure). -### Linking the plugin to your project +## Developing local plugins -In order to test your plugin during its development, the recommended approach is to link it to a Strapi project. +If you run the Plugin SDK command from within an existing Strapi project, the plugin will be created in the `src/plugins` folder within that project, with the new folder equal to the init command name. If a `plugins` folder already exists, the new plugin code will be placed there. This allows you to develop plugins locally within your project structure. -Linking your plugin to a project is done with the `watch:link` command. The command will output explanations on how to link your plugin to a Strapi project. +When developing your plugin locally, you also need to add the following configuration to your plugins configuration file: -In a new terminal window, run the following commands: +```js title="/config/plugins.js|ts" +myplugin: { + enabled: true, + resolve: `./src/plugins/local-plugin`, +}, +``` + +:::note +This setup can sometimes lead to errors such as: + +```js +Error: 'X must be used within StrapiApp'; +``` + +This error often occurs when your plugin attempts to import core Strapi functionality, for example using: + +```js +import { unstable_useContentManagerContext as useContentManagerContext } from '@strapi/strapi/admin'; +``` + +To resolve this issue, remove `@strapi/strapi` as a dev dependency from your plugin. This ensures that your plugin uses the same instance of Strapi's core modules as the main application, preventing conflicts and the associated errors. +::: + +Use the `watch` command in your plugin folder to start development mode: ```bash -cd /path/to/strapi/project -yarn dlx yalc add --link my-strapi-plugin && yarn install +yarn watch ``` @@ -87,34 +101,33 @@ yarn dlx yalc add --link my-strapi-plugin && yarn install ```bash -cd /path/to/strapi/project -npx yalc add --link my-strapi-plugin && npm install +npm run watch ``` -:::note -In the above examples we use the name of the plugin (`my-strapi-plugin`) when linking it to the project. This is the name of the package, not the name of the folder. -::: +Then run `yarn develop` or `npm run develop` in your Strapi project to start the application with your local plugin. -Because this plugin is installed via `node_modules` you won't need to explicity add it to your `plugins` [configuration file](/cms/configurations/plugins), so running the [`develop command`](/cms/cli#strapi-develop) to start your Strapi project will automatically pick up your plugin. +## Developing external plugins -Now that your plugin is linked to a project, run `yarn develop` or `npm run develop` to start the Strapi application. +:::prerequisites + must be installed globally (with `npm install -g yalc` or `yarn global add yalc`). -You are now ready to develop your plugin how you see fit! If you are making server changes, you will need to restart your server for them to take effect. +**Why yalc?** `yalc` is a local package manager that enables efficient plugin development by allowing you to test your plugin in a real Strapi project without publishing it to npm first. It provides reliable linking between your plugin and Strapi project for immediate testing during development. +::: -### Building the plugin for publishing +For external plugins (plugins that don't sit within your Strapi project), you need to link them to a Strapi project for testing during development. This is done using the `watch:link` command in your plugin folder, which will output explanations on how to link your plugin to a Strapi project. -When you are ready to publish your plugin, you will need to build it. To do this, run the following command: +First, in your plugin folder, run the `watch:link` command: ```bash -yarn build && yarn verify +yarn watch:link ``` @@ -122,108 +135,94 @@ yarn build && yarn verify ```bash -npm run build && npm run verify +npm run watch:link ``` -The above commands will not only build the plugin, but also verify that the output is valid and ready to be published. You can then publish your plugin to NPM as you would any other package. +Then, in a new terminal window, run the following commands in your Strapi project: -## Working with the Plugin SDK in a monorepo environment {#monorepo} + -If you are working with a monorepo environment to develop your plugin, you don't need to use the `watch:link` command because the monorepo workspace setup will handle the symlink. You can use the `watch` command instead. + -However, if you are writing admin code, you might add an `alias` that targets the source code of your plugin to make it easier to work with within the context of the admin panel: +```bash +cd /path/to/strapi/project +yarn dlx yalc add --link my-strapi-plugin && yarn install +``` -```ts -import path from 'node:path'; + -export default (config, webpack) => { - config.resolve.alias = { - ...config.resolve.alias, - 'my-strapi-plugin': path.resolve( - __dirname, - // We've assumed the plugin is local. - '../plugins/my-strapi-plugin/admin/src' - ), - }; + - return config; -}; +```bash +cd /path/to/strapi/project +npx yalc add --link my-strapi-plugin && npm install ``` -:::caution -Because the server looks at the `server/src/index.ts|js` file to import your plugin code, you must use the `watch` command otherwise the code will not be transpiled and the server will not be able to find your plugin. + + + + +:::note +In the above examples, we use the name of the plugin (`my-strapi-plugin`) when linking it to the project. This is the name of the package, not the name of the folder. ::: -### Configuration with a local plugin +:::note +If you're working in a monorepo environment, you don't need `yalc` because the monorepo workspace setup handles the symlinking automatically. +::: -Since the Plugin SDK is primarily designed for developing plugins, not locally, the configuration needs to be adjusted manually for local plugins. +Because this plugin is installed via `node_modules`, you won't need to explicitly add it to your `plugins` configuration file as we would do for a local plugin. Running the `develop` command to start your Strapi project will automatically pick up your plugin. -When developing your plugin locally (using `@strapi/sdk-plugin`), your plugins configuration file looks like in the following example: +Now that your plugin is linked to a project, run `yarn develop` or `npm run develop` to start the Strapi application. -```js title="/config/plugins.js|ts" -myplugin: { - enabled: true, - resolve: `./src/plugins/local-plugin`, -}, -``` +You are now ready to develop your plugin as needed! If you are making server changes, you will need to restart your server for them to take effect. -However, this setup can sometimes lead to errors such as the following: +## Building the plugin for publishing -```js -Error: 'X must be used within StrapiApp'; -``` +When you are ready to publish your plugin, you will need to build it. To do this, run the following command: -This error often occurs when your plugin attempts to import core Strapi functionality, for example using: + -```js -import { unstable_useContentManagerContext as useContentManagerContext } from '@strapi/strapi/admin'; + + +```bash +yarn build && yarn verify ``` -To resolve the issue, remove `@strapi/strapi` as a dev dependency from your plugin. This ensures that your plugin uses the same instance of Strapi’s core modules as the main application, preventing conflicts and the associated errors. + -## Setting a local plugin in a monorepo environment without the Plugin SDK + -In a monorepo, you can configure your local plugin without using the Plugin SDK by adding 2 entry point files at the root of your plugin: +```bash +npm run build && npm run verify +``` -- server entry point: `strapi-server.js|ts` -- admin entry point: `strapi-admin.js|ts` + -### Server entry point + -The server entry point file initializes your plugin’s server-side functionalities. The expected structure for `strapi-server.js` (or its TypeScript variant) is: +These commands will not only build the plugin but also verify that the output is valid and ready to be published. You can then publish your plugin to NPM as you would any other package. -```js -module.exports = () => { - return { - register, - config, - controllers, - contentTypes, - routes, - }; -}; -``` +## Admin panel development -Here, you export a function that returns your plugin's core components such as controllers, routes, and configuration. For more details, please refer to the [Server API reference](/cms/plugins-development/server-api). +If you are writing admin code, you might add an `alias` that targets the source code of your plugin to make it easier to work with within the context of the admin panel: -### Admin entry point +```ts +import path from 'node:path'; -The admin entry point file sets up your plugin within the Strapi admin panel. The expected structure for `strapi-admin.js` (or its TypeScript variant) is: +export default (config, webpack) => { + config.resolve.alias = { + ...config.resolve.alias, + 'my-strapi-plugin': path.resolve( + __dirname, + // We've assumed the plugin is local. + '../plugins/my-strapi-plugin/admin/src', + ), + }; -```js -export default { - register(app) {}, - bootstrap() {}, - registerTrads({ locales }) {}, + return config; }; ``` - -This object includes methods to register your plugin with the admin application, perform bootstrapping actions, and handle translations. For more details, please refer to the [Admin Panel API reference](/cms/plugins-development/admin-panel-api). - -:::tip -For a complete example of how to structure your local plugin in a monorepo environment, please check out our . -::: From 251c6fe06597140d94d8c3ae5dc7e6f0ea5206cb Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:21:38 +0100 Subject: [PATCH 03/64] Restore create-a-plugin.md content to match `main` branch content --- .../plugins-development/create-a-plugin.md | 220 ++++++++++-------- 1 file changed, 117 insertions(+), 103 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/create-a-plugin.md b/docusaurus/docs/cms/plugins-development/create-a-plugin.md index c6d1e6cc6a..4d8cf29c07 100644 --- a/docusaurus/docs/cms/plugins-development/create-a-plugin.md +++ b/docusaurus/docs/cms/plugins-development/create-a-plugin.md @@ -9,27 +9,37 @@ tags: - plugins development --- +import UsingSDKplugin5 from '/docs/snippets/sdk-plugin-v5-v6.md' + # Plugin creation -This guide walks you through the complete process of creating, developing, and publishing Strapi 5 plugins that can be used locally or distributed via NPM and the Marketplace. The Plugin SDK is the recommended way to build plugins, providing a comprehensive toolkit from initial setup to publishing. + +The Plugin SDK generates plugins without a Strapi project and links them to an existing app with `watch:link` and yalc. In this documentation: build and verify commands to bundle the plugin for npm or marketplace publishing, and information for monorepos and local setups. + + +There are many ways to create a Strapi 5 plugin, but the fastest and recommended way is to use the Plugin SDK. -The complete list of commands and their parameters are available in the [Plugin SDK reference](/cms/plugins-development/plugin-sdk). This page will guide you through using the main ones. +The Plugin SDK is a set of commands orientated around developing plugins to use them as local plugins or to publish them on NPM and/or submit them to the Marketplace. -Before proceeding with plugin development, it's important to understand the difference between local and external plugins: +With the Plugin SDK, you do not need to set up a Strapi project before creating a plugin. -- **Local plugins**: Plugins that are developed within your Strapi project. By default, if you're within a Strapi project, the plugin will be placed within the `src/plugins` folder with the new folder corresponding to the plugin name. If a `plugins` folder already exists, the new plugin code will be placed there. -- **External plugins**: Plugins that are developed outside your Strapi project (in a separate directory). +The present guide covers creating a plugin from scratch, linking it to an existing Strapi project, and publishing the plugin. If you already have an existing plugin, you can instead retrofit the plugin setup to utilise the Plugin SDK commands (please refer to the [Plugin SDK reference](/cms/plugins-development/plugin-sdk) for a full list of available commands). + +:::note +This guide assumes you want to develop a plugin external to your Strapi project. However, the steps largely remain the same if you want to develop a plugin within your existing project. If you are not [using a monorepo](#monorepo) the steps are exactly the same. +::: -The Plugin SDK can create both types of plugins. If you run the Plugin SDK command from within an existing Strapi project, it will create a local plugin. If you run it from outside a Strapi project, it will create an external plugin. The development process differs slightly between the two approaches, as outlined in the sections below. +:::prerequisites + must be installed globally (with `npm install -g yalc` or `yarn global add yalc`). +::: -In addition to the plugin location, your Strapi project and plugin can be organized in different repository setups, which affects the development workflow: +## Getting started with the Plugin SDK -- **Separate repositories**: Your Strapi project and plugin are in different repositories. This is common for external plugins that you plan to publish or share across multiple projects. -- **Monorepo setup**: Your Strapi project and plugin are in the same repository, often using workspace management tools like Yarn workspaces or npm workspaces. This is useful when you want to maintain everything in one place. +The Plugin SDK helps you creating a plugin, linking it to an existing Strapi project, and building it for publishing. -The development workflow and commands you use will vary based on both the plugin type (local vs external) and the repository setup (separate vs monorepo), as detailed in the sections below. +The full list of commands and their parameters are available in the [Plugin SDK reference](/cms/plugins-development/plugin-sdk). The present page will guide on using the main ones. -## Creating the plugin +### Creating the plugin To create your plugin, ensure you are in the parent directory of where you want it to be created and run the following command: @@ -53,47 +63,25 @@ npx @strapi/sdk-plugin init my-strapi-plugin -The `my-strapi-plugin` part can be replaced with whatever you want to call your plugin, including the path to where it should be created (e.g., `code/strapi-plugins/my-new-strapi-plugin`). +The path `my-strapi-plugin` can be replaced with whatever you want to call your plugin, including the path to where it should be created (e.g., `code/strapi-plugins/my-new-strapi-plugin`). -You will be guided through a series of prompts to help you set up your plugin. If you select "yes" to all options, the final structure will be similar to the default [plugin structure](/cms/plugins-development/plugin-structure). +You will be ran through a series of prompts to help you setup your plugin. If you selected yes to all options the final structure will be similar to the default [plugin structure](/cms/plugins-development/plugin-structure). -## Developing local plugins +### Linking the plugin to your project -If you run the Plugin SDK command from within an existing Strapi project, the plugin will be created in the `src/plugins` folder within that project, with the new folder equal to the init command name. If a `plugins` folder already exists, the new plugin code will be placed there. This allows you to develop plugins locally within your project structure. +In order to test your plugin during its development, the recommended approach is to link it to a Strapi project. -When developing your plugin locally, you also need to add the following configuration to your plugins configuration file: - -```js title="/config/plugins.js|ts" -myplugin: { - enabled: true, - resolve: `./src/plugins/local-plugin`, -}, -``` - -:::note -This setup can sometimes lead to errors such as: - -```js -Error: 'X must be used within StrapiApp'; -``` - -This error often occurs when your plugin attempts to import core Strapi functionality, for example using: - -```js -import { unstable_useContentManagerContext as useContentManagerContext } from '@strapi/strapi/admin'; -``` - -To resolve this issue, remove `@strapi/strapi` as a dev dependency from your plugin. This ensures that your plugin uses the same instance of Strapi's core modules as the main application, preventing conflicts and the associated errors. -::: +Linking your plugin to a project is done with the `watch:link` command. The command will output explanations on how to link your plugin to a Strapi project. -Use the `watch` command in your plugin folder to start development mode: +In a new terminal window, run the following commands: ```bash -yarn watch +cd /path/to/strapi/project +yarn dlx yalc add --link my-strapi-plugin && yarn install ``` @@ -101,33 +89,34 @@ yarn watch ```bash -npm run watch +cd /path/to/strapi/project +npx yalc add --link my-strapi-plugin && npm install ``` -Then run `yarn develop` or `npm run develop` in your Strapi project to start the application with your local plugin. +:::note +In the above examples we use the name of the plugin (`my-strapi-plugin`) when linking it to the project. This is the name of the package, not the name of the folder. +::: -## Developing external plugins +Because this plugin is installed via `node_modules` you won't need to explicity add it to your `plugins` [configuration file](/cms/configurations/plugins), so running the [`develop command`](/cms/cli#strapi-develop) to start your Strapi project will automatically pick up your plugin. -:::prerequisites - must be installed globally (with `npm install -g yalc` or `yarn global add yalc`). +Now that your plugin is linked to a project, run `yarn develop` or `npm run develop` to start the Strapi application. -**Why yalc?** `yalc` is a local package manager that enables efficient plugin development by allowing you to test your plugin in a real Strapi project without publishing it to npm first. It provides reliable linking between your plugin and Strapi project for immediate testing during development. -::: +You are now ready to develop your plugin how you see fit! If you are making server changes, you will need to restart your server for them to take effect. -For external plugins (plugins that don't sit within your Strapi project), you need to link them to a Strapi project for testing during development. This is done using the `watch:link` command in your plugin folder, which will output explanations on how to link your plugin to a Strapi project. +### Building the plugin for publishing -First, in your plugin folder, run the `watch:link` command: +When you are ready to publish your plugin, you will need to build it. To do this, run the following command: ```bash -yarn watch:link +yarn build && yarn verify ``` @@ -135,94 +124,119 @@ yarn watch:link ```bash -npm run watch:link +npm run build && npm run verify ``` -Then, in a new terminal window, run the following commands in your Strapi project: +The above commands will not only build the plugin, but also verify that the output is valid and ready to be published. You can then publish your plugin to NPM as you would any other package. - +:::tip Upgrading from SDK Plugin v5 +If you're upgrading from `@strapi/sdk-plugin` v5 to v6: +* Delete any `packup.config.ts` file from your plugin (it is no longer used). +* Rely on `package.json#exports` for build configuration (it is now derived automatically). +* Add `--sourcemap` to your build command if you need sourcemaps (they now default to off). - +No other changes are required. +::: -```bash -cd /path/to/strapi/project -yarn dlx yalc add --link my-strapi-plugin && yarn install -``` + - +## Working with the Plugin SDK in a monorepo environment {#monorepo} - +If you are working with a monorepo environment to develop your plugin, you don't need to use the `watch:link` command because the monorepo workspace setup will handle the symlink. You can use the `watch` command instead. -```bash -cd /path/to/strapi/project -npx yalc add --link my-strapi-plugin && npm install -``` +However, if you are writing admin code, you might add an `alias` that targets the source code of your plugin to make it easier to work with within the context of the admin panel: - +```ts +import path from 'node:path'; - +export default (config, webpack) => { + config.resolve.alias = { + ...config.resolve.alias, + 'my-strapi-plugin': path.resolve( + __dirname, + // We've assumed the plugin is local. + '../plugins/my-strapi-plugin/admin/src' + ), + }; -:::note -In the above examples, we use the name of the plugin (`my-strapi-plugin`) when linking it to the project. This is the name of the package, not the name of the folder. -::: + return config; +}; +``` -:::note -If you're working in a monorepo environment, you don't need `yalc` because the monorepo workspace setup handles the symlinking automatically. +:::caution +Because the server looks at the `server/src/index.ts|js` file to import your plugin code, you must use the `watch` command otherwise the code will not be transpiled and the server will not be able to find your plugin. ::: -Because this plugin is installed via `node_modules`, you won't need to explicitly add it to your `plugins` configuration file as we would do for a local plugin. Running the `develop` command to start your Strapi project will automatically pick up your plugin. +### Configuration with a local plugin -Now that your plugin is linked to a project, run `yarn develop` or `npm run develop` to start the Strapi application. +Since the Plugin SDK is primarily designed for developing plugins, not locally, the configuration needs to be adjusted manually for local plugins. -You are now ready to develop your plugin as needed! If you are making server changes, you will need to restart your server for them to take effect. +When developing your plugin locally (using `@strapi/sdk-plugin`), your plugins configuration file looks like in the following example: -## Building the plugin for publishing +```js title="/config/plugins.js|ts" +myplugin: { + enabled: true, + resolve: `./src/plugins/local-plugin`, +}, +``` -When you are ready to publish your plugin, you will need to build it. To do this, run the following command: +However, this setup can sometimes lead to errors such as the following: - +```js +Error: 'X must be used within StrapiApp'; +``` - +This error often occurs when your plugin attempts to import core Strapi functionality, for example using: -```bash -yarn build && yarn verify +```js +import { unstable_useContentManagerContext as useContentManagerContext } from '@strapi/strapi/admin'; ``` - +To resolve the issue, remove `@strapi/strapi` as a dev dependency from your plugin. This ensures that your plugin uses the same instance of Strapi's core modules as the main application, preventing conflicts and the associated errors. - +## Setting a local plugin in a monorepo environment without the Plugin SDK -```bash -npm run build && npm run verify -``` +In a monorepo, you can configure your local plugin without using the Plugin SDK by adding 2 entry point files at the root of your plugin: - +- server entry point: `strapi-server.js|ts` +- admin entry point: `strapi-admin.js|ts` - +### Server entry point -These commands will not only build the plugin but also verify that the output is valid and ready to be published. You can then publish your plugin to NPM as you would any other package. +The server entry point file initializes your plugin's server-side functionalities. The expected structure for `strapi-server.js` (or its TypeScript variant) is: -## Admin panel development +```js +module.exports = () => { + return { + register, + config, + controllers, + contentTypes, + routes, + }; +}; +``` -If you are writing admin code, you might add an `alias` that targets the source code of your plugin to make it easier to work with within the context of the admin panel: +Here, you export a function that returns your plugin's core components such as controllers, routes, and configuration. For more details, please refer to the [Server API reference](/cms/plugins-development/server-api). -```ts -import path from 'node:path'; +### Admin entry point -export default (config, webpack) => { - config.resolve.alias = { - ...config.resolve.alias, - 'my-strapi-plugin': path.resolve( - __dirname, - // We've assumed the plugin is local. - '../plugins/my-strapi-plugin/admin/src', - ), - }; +The admin entry point file sets up your plugin within the Strapi admin panel. The expected structure for `strapi-admin.js` (or its TypeScript variant) is: - return config; +```js +export default { + register(app) {}, + bootstrap() {}, + registerTrads({ locales }) {}, }; ``` + +This object includes methods to register your plugin with the admin application, perform bootstrapping actions, and handle translations. For more details, please refer to the [Admin Panel API reference](/cms/plugins-development/admin-panel-api). + +:::tip +For a complete example of how to structure your local plugin in a monorepo environment, please check out our . +::: \ No newline at end of file From d029786926b5a39496e127251594057b45b253d9 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:22:33 +0100 Subject: [PATCH 04/64] Add dedicated pages for sub-topics --- .../admin-injection-zones.md | 382 ++++++++++++ .../plugins-development/admin-localization.md | 436 +++++++++++++ .../plugins-development/admin-redux-store.md | 576 ++++++++++++++++++ 3 files changed, 1394 insertions(+) create mode 100644 docusaurus/docs/cms/plugins-development/admin-injection-zones.md create mode 100644 docusaurus/docs/cms/plugins-development/admin-localization.md create mode 100644 docusaurus/docs/cms/plugins-development/admin-redux-store.md diff --git a/docusaurus/docs/cms/plugins-development/admin-injection-zones.md b/docusaurus/docs/cms/plugins-development/admin-injection-zones.md new file mode 100644 index 0000000000..3281e74be0 --- /dev/null +++ b/docusaurus/docs/cms/plugins-development/admin-injection-zones.md @@ -0,0 +1,382 @@ +--- +title: Admin injection zones +description: Extend and customize the Strapi admin panel by injecting React components into predefined or custom injection zones. +pagination_prev: cms/plugins-development/admin-localization +pagination_next: cms/plugins-development/admin-redux-store +displayed_sidebar: cmsSidebar +toc_max_heading_level: 4 +tags: + - admin panel + - injection zones + - plugin customization + - Content Manager + - plugins development +--- + +# Admin injection zones + + + +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 with `injectionZones` in `registerPlugin`. + + + +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. + +:::prerequisites +You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin) and are familiar with the [admin entry file lifecycle](/cms/plugins-development/admin-configuration-customization#overview). +::: + +## What are injection zones? + +Injection zones are predefined areas in a plugin's UI where other plugins can inject custom React components. They are defined in the `register` lifecycle function, but components are injected in the `bootstrap` lifecycle function. + +## 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 | +| Edit view | `editView.right-links` | Between the "Configure the view" and "Edit" buttons | +| Preview | `preview.actions` | In the preview view action area | + +### Injecting into Content Manager zones + +To inject a component into a Content Manager injection zone, use `getPlugin('content-manager').injectComponent()` in the `bootstrap` lifecycle: + + + + +```jsx title="admin/src/index.js" +import { MyCustomButton } from './components/MyCustomButton'; +import { PreviewAction } from './components/PreviewAction'; + +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: () => , + }); + + // Inject a component into the Preview view's actions zone + app.getPlugin('content-manager').injectComponent('preview', 'actions', { + name: 'my-plugin-preview-action', + Component: PreviewAction, + }); + }, +}; +``` + + + + +```tsx title="admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; +import { MyCustomButton } from './components/MyCustomButton'; +import { PreviewAction } from './components/PreviewAction'; + +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: () => , + }); + + // Inject a component into the Preview view's actions zone + app.getPlugin('content-manager').injectComponent('preview', 'actions', { + name: 'my-plugin-preview-action', + Component: PreviewAction, + }); + }, +}; +``` + + + + +## Creating 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: + + + + +```jsx title="admin/src/index.js" +export default { + register(app) { + app.registerPlugin({ + id: 'dashboard', + name: 'Dashboard', + injectionZones: { + homePage: { + top: [], + middle: [], + bottom: [], + }, + sidebar: { + before: [], + after: [], + }, + }, + }); + }, +}; +``` + + + + +```ts title="admin/src/index.ts" +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: [], + }, + }, + }); + }, +}; +``` + + + + +### Using injection zones in components + +To render injected components in your plugin's UI, use the `` React component with an `area` prop following the naming convention `plugin-name.viewName.injectionZoneName`: + +```jsx title="admin/src/pages/Dashboard.jsx" +import { InjectionZone } from '@strapi/helper-plugin'; + +const Dashboard = () => { + return ( +
+

Dashboard

+ + {/* Render components injected into the top zone */} + + +
{/* Main dashboard content */}
+ + {/* Render components injected into the bottom zone */} + +
+ ); +}; + +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: + + + + +```jsx title="another-plugin/admin/src/index.js" +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, + }); + } + }, +}; +``` + + + + +```tsx title="another-plugin/admin/src/index.ts" +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) | + +## Accessing Content Manager data + +When injecting components into Content Manager injection zones, you can access the Edit View data using the `useContentManagerContext` hook: + + + + +```jsx title="admin/src/components/MyCustomButton.jsx" +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 ; +}; +``` + + + + +```tsx title="admin/src/components/MyCustomButton.tsx" +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 ; +}; +``` + + + + +:::note +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: + + ```js + 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. \ No newline at end of file diff --git a/docusaurus/docs/cms/plugins-development/admin-localization.md b/docusaurus/docs/cms/plugins-development/admin-localization.md new file mode 100644 index 0000000000..52e774742b --- /dev/null +++ b/docusaurus/docs/cms/plugins-development/admin-localization.md @@ -0,0 +1,436 @@ +--- +title: Admin localization +description: Provide translations for your Strapi plugin's admin panel interface using registerTrads and react-intl. +pagination_prev: cms/plugins-development/admin-configuration-customization +pagination_next: cms/plugins-development/admin-injection-zones +displayed_sidebar: cmsSidebar +toc_max_heading_level: 4 +tags: + - admin panel + - localization + - translations + - registerTrads + - react-intl + - plugins development +--- + +# Admin localization + + + +Register translation files with `registerTrads`, prefix keys with your plugin ID to avoid conflicts, and use `react-intl`'s `useIntl` hook in components. Strapi merges plugin translations with core translations automatically. + + + +Plugins can provide translations for multiple languages to make the admin interface accessible to users worldwide. Strapi automatically loads and merges plugin translations with core translations, making them available throughout the admin panel. + +:::prerequisites +You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin) and are familiar with the [admin entry file lifecycle](/cms/plugins-development/admin-configuration-customization#overview). +::: + +## Translation file structure + +Translation files are stored in the `admin/src/translations/` directory, with 1 JSON file per locale: + +``` +admin/src/translations/ + ├── en.json + ├── fr.json + ├── de.json + └── ... +``` + +Each translation file contains key-value pairs where keys are translation identifiers and values are the translated strings: + +```json +// admin/src/translations/en.json +{ + "plugin.name": "My Plugin", + "plugin.description": "A custom Strapi plugin", + "settings.title": "Settings", + "settings.general": "General", + "settings.advanced": "Advanced" +} +``` + +## The `registerTrads` function + +The `registerTrads` function is a required lifecycle function that loads translation files for all configured locales. Strapi calls this function during admin panel initialization to collect translations from all plugins. + +### Basic implementation + + + + +```js title="admin/src/index.js" +import { prefixPluginTranslations } from './utils/prefixPluginTranslations'; +import { PLUGIN_ID } from './pluginId'; + +export default { + register(app) { + app.registerPlugin({ + id: PLUGIN_ID, + name: 'My Plugin', + }); + }, + async registerTrads({ locales }) { + const importedTranslations = await Promise.all( + locales.map((locale) => { + return import(`./translations/${locale}.json`) + .then(({ default: data }) => { + return { + data: prefixPluginTranslations(data, PLUGIN_ID), + locale, + }; + }) + .catch(() => { + return { + data: {}, + locale, + }; + }); + }), + ); + + return importedTranslations; + }, +}; +``` + + + + +```ts title="admin/src/index.ts" +import { prefixPluginTranslations } from './utils/prefixPluginTranslations'; +import { PLUGIN_ID } from './pluginId'; +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + register(app: StrapiApp) { + app.registerPlugin({ + id: PLUGIN_ID, + name: 'My Plugin', + }); + }, + async registerTrads({ locales }: { locales: string[] }) { + const importedTranslations = await Promise.all( + locales.map((locale) => { + return import(`./translations/${locale}.json`) + .then(({ default: data }) => { + return { + data: prefixPluginTranslations(data, PLUGIN_ID), + locale, + }; + }) + .catch(() => { + return { + data: {}, + locale, + }; + }); + }), + ); + + return importedTranslations; + }, +}; +``` + + + + +### Function parameters + +The `registerTrads` function receives an object with the following property: + +| Parameter | Type | Description | +|---|---|---| +| `locales` | `string[]` | Array of locale codes configured in the admin panel (e.g., `['en', 'fr', 'de']`) | + +### Return value + +The function must return a `Promise` that resolves to an array of translation objects. Each object has the following structure: + +```typescript +{ + data: Record; // Translation key-value pairs + locale: string; // Locale code (e.g., 'en', 'fr') +} +``` + +### Translation key prefixing + +:::caution +Translation keys must be prefixed with your plugin ID to avoid conflicts with other plugins and core Strapi translations. For example, if your plugin ID is `my-plugin`, a key like `plugin.name` should become `my-plugin.plugin.name`. +::: + +Use the `prefixPluginTranslations` utility function to automatically prefix all keys: + + + + +```js title="admin/src/utils/prefixPluginTranslations.js" +const prefixPluginTranslations = (trad, pluginId) => { + if (!pluginId) { + throw new TypeError("pluginId can't be empty"); + } + return Object.keys(trad).reduce((acc, current) => { + acc[`${pluginId}.${current}`] = trad[current]; + return acc; + }, {}); +}; + +export { prefixPluginTranslations }; +``` + + + + +```ts title="admin/src/utils/prefixPluginTranslations.ts" +type TradOptions = Record; + +const prefixPluginTranslations = ( + trad: TradOptions, + pluginId: string, +): TradOptions => { + if (!pluginId) { + throw new TypeError("pluginId can't be empty"); + } + return Object.keys(trad).reduce((acc, current) => { + acc[`${pluginId}.${current}`] = trad[current]; + return acc; + }, {} as TradOptions); +}; + +export { prefixPluginTranslations }; +``` + + + + +For instance, if your translation file contains: + +```json +{ + "plugin.name": "My Plugin", + "settings.title": "Settings" +} +``` + +After prefixing with plugin ID `my-plugin`, these become: + +- `my-plugin.plugin.name` +- `my-plugin.settings.title` + +### Handling missing translation files + +The `registerTrads` function should gracefully handle missing translation files by returning an empty object for that locale. The `.catch()` handler in the example above ensures that if a translation file does not exist, the plugin still returns a valid translation object: + +```js +.catch(() => { + return { + data: {}, + locale, + }; +}); +``` + +This allows plugins to provide translations for only some locales (e.g., only English) without breaking the admin panel for other locales. + +## Using translations in components + +To use translations in your React components, use the `useIntl` hook from `react-intl`: + + + + +```jsx title="admin/src/pages/HomePage.jsx" +import { useIntl } from 'react-intl'; +import { PLUGIN_ID } from '../pluginId'; + +const HomePage = () => { + const { formatMessage } = useIntl(); + + return ( +
+

+ {formatMessage({ + id: `${PLUGIN_ID}.plugin.name`, + defaultMessage: 'My Plugin', + })} +

+

+ {formatMessage({ + id: `${PLUGIN_ID}.plugin.description`, + defaultMessage: 'A custom Strapi plugin', + })} +

+
+ ); +}; + +export default HomePage; +``` + +
+ + +```tsx title="admin/src/pages/HomePage.tsx" +import { useIntl } from 'react-intl'; +import { PLUGIN_ID } from '../pluginId'; + +const HomePage = () => { + const { formatMessage } = useIntl(); + + return ( +
+

+ {formatMessage({ + id: `${PLUGIN_ID}.plugin.name`, + defaultMessage: 'My Plugin', + })} +

+

+ {formatMessage({ + id: `${PLUGIN_ID}.plugin.description`, + defaultMessage: 'A custom Strapi plugin', + })} +

+
+ ); +}; + +export default HomePage; +``` + +
+
+ +### Helper function for translation keys + +To avoid repeating the plugin ID prefix, create a helper function: + + + + +```js title="admin/src/utils/getTranslation.js" +import { PLUGIN_ID } from '../pluginId'; + +export const getTranslation = (id) => `${PLUGIN_ID}.${id}`; +``` + + + + +```ts title="admin/src/utils/getTranslation.ts" +import { PLUGIN_ID } from '../pluginId'; + +export const getTranslation = (id: string) => `${PLUGIN_ID}.${id}`; +``` + + + + +Then use it in components: + + + + +```jsx title="admin/src/pages/HomePage.jsx" +import { useIntl } from 'react-intl'; +import { getTranslation } from '../utils/getTranslation'; + +const HomePage = () => { + const { formatMessage } = useIntl(); + + return ( +
+

+ {formatMessage({ + id: getTranslation('plugin.name'), + defaultMessage: 'My Plugin', + })} +

+
+ ); +}; +``` + +
+ + +```tsx title="admin/src/pages/HomePage.tsx" +import { useIntl } from 'react-intl'; +import { getTranslation } from '../utils/getTranslation'; + +const HomePage = () => { + const { formatMessage } = useIntl(); + + return ( +
+

+ {formatMessage({ + id: getTranslation('plugin.name'), + defaultMessage: 'My Plugin', + })} +

+
+ ); +}; +``` + +
+
+ +## Using translations in configuration + +Translation keys are also used when configuring menu links, settings sections, and other admin panel elements: + +```js title="admin/src/index.js" +export default { + register(app) { + app.addMenuLink({ + to: '/plugins/my-plugin', + icon: PluginIcon, + intlLabel: { + id: 'my-plugin.plugin.name', // Prefixed translation key + defaultMessage: 'My Plugin', // Fallback if translation missing + }, + Component: async () => { + const { App } = await import('./pages/App'); + return App; + }, + }); + }, +}; +``` + +See [Customizing menu and settings](/cms/plugins-development/admin-configuration-customization#customizing-menu-and-settings) for more configuration examples. + +## How Strapi integrates plugin translations + +Strapi's admin panel automatically: + +1. Calls `registerTrads` for all registered plugins during initialization +2. Merges translations from all plugins with core Strapi translations +3. Applies custom translations from the admin configuration (if any) +4. Makes translations available via `react-intl` throughout the admin panel + +The merge order ensures that core Strapi translations are loaded first, plugin translations are merged on top, and custom translations from admin config override both (allowing users to customize translations). + +## Best practices + +- **Always prefix translation keys.** Use `prefixPluginTranslations` or manually prefix keys with your plugin ID to avoid conflicts. +- **Provide default messages.** Always include `defaultMessage` when using `formatMessage` as a fallback if translations are missing. +- **Handle missing translations gracefully.** The `registerTrads` function should return empty objects for missing locales rather than throwing errors. +- **Use descriptive key names.** Choose clear, hierarchical key names (e.g., `settings.general.title` rather than `title1`). +- **Support at least English.** Providing English translations ensures your plugin works out of the box. +- **Verify behavior with multiple locales.** Test that your plugin works correctly when different locales are selected in the admin panel. + +:::note +The `en` locale is always available in Strapi and serves as the fallback locale. If a translation is missing for a selected locale, Strapi uses the English translation. +::: + +:::tip +To see which locales are available in your Strapi instance, check the `config.locales` array in your `src/admin/app.ts` or `src/admin/app.js` file. You can also access available locales from the Redux store using `useSelector((state) => state.admin_app?.language?.localeNames)` (see [Accessing the Redux store](/cms/plugins-development/admin-redux-store)). +::: \ No newline at end of file diff --git a/docusaurus/docs/cms/plugins-development/admin-redux-store.md b/docusaurus/docs/cms/plugins-development/admin-redux-store.md new file mode 100644 index 0000000000..f8df4add06 --- /dev/null +++ b/docusaurus/docs/cms/plugins-development/admin-redux-store.md @@ -0,0 +1,576 @@ +--- +title: Accessing the Redux store +description: Read state, dispatch actions, and subscribe to changes in Strapi's admin panel Redux store from your plugin. +pagination_prev: cms/plugins-development/admin-injection-zones +pagination_next: cms/plugins-development/content-manager-apis +displayed_sidebar: cmsSidebar +toc_max_heading_level: 4 +tags: + - admin panel + - Redux store + - useSelector + - useDispatch + - plugins development +--- + +# Accessing the Redux store + + + +Strapi's admin panel uses a global Redux store. Plugins can read state with `useSelector`, update state with `useDispatch`, and subscribe to changes with `useStore`. The `admin_app` slice exposes theme, locale, permissions, and authentication data. + + + +Strapi's admin panel uses a global Redux store to manage application state. Plugins can access this store to read state, dispatch actions, and subscribe to state changes. This enables plugins to interact with core admin functionality like theme settings, language preferences, and authentication state. + +:::prerequisites +You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin) and are familiar with the [admin entry file lifecycle](/cms/plugins-development/admin-configuration-customization#overview). +::: + +## Store overview + +The Redux store is automatically provided to all plugin components through React Redux's `Provider`. The store contains several slices: + +- `admin_app`: core admin state including theme, language, permissions, and authentication token +- `adminApi`: RTK Query API state for admin endpoints +- Plugin-specific slices: additional reducers added by plugins + +## Reading state with `useSelector` + +The most common way to access Redux state in your plugin components is using the `useSelector` hook from `react-redux`: + + + + +```jsx title="admin/src/pages/HomePage.jsx" +import { useSelector } from 'react-redux'; + +const HomePage = () => { + // Read current theme + const currentTheme = useSelector( + (state) => state.admin_app?.theme?.currentTheme + ); + + // Read current locale + const currentLocale = useSelector( + (state) => state.admin_app?.language?.locale + ); + + // Read authentication status + const isAuthenticated = useSelector((state) => !!state.admin_app?.token); + + // Read available locales + const availableLocales = useSelector( + (state) => state.admin_app?.language?.localeNames || {} + ); + + return ( +
+

Current Theme: {currentTheme}

+

Current Locale: {currentLocale}

+

Authenticated: {isAuthenticated ? 'Yes' : 'No'}

+
+ ); +}; +``` + +
+ + +```tsx title="admin/src/pages/HomePage.tsx" +import { useSelector } from 'react-redux'; + +const HomePage = () => { + // Read current theme + const currentTheme = useSelector( + (state: any) => state.admin_app?.theme?.currentTheme + ); + + // Read current locale + const currentLocale = useSelector( + (state: any) => state.admin_app?.language?.locale + ); + + // Read authentication status + const isAuthenticated = useSelector((state: any) => !!state.admin_app?.token); + + // Read available locales + const availableLocales = useSelector( + (state: any) => state.admin_app?.language?.localeNames || {} + ); + + return ( +
+

Current Theme: {currentTheme}

+

Current Locale: {currentLocale}

+

Authenticated: {isAuthenticated ? 'Yes' : 'No'}

+
+ ); +}; +``` + +
+
+ +### Available state properties + +The `admin_app` slice contains the following state properties: + +| Property | Type | Description | +|---|---|---| +| `theme.currentTheme` | `string` | Current theme (`'light'`, `'dark'`, or `'system'`) | +| `theme.availableThemes` | `string[]` | Array of available theme names | +| `language.locale` | `string` | Current locale code (e.g., `'en'`, `'fr'`) | +| `language.localeNames` | `object` | Object mapping locale codes to display names | +| `token` | `string \| null` | Authentication token | +| `permissions` | `object` | User permissions object | + +## Dispatching actions + +To update the Redux store, use the `useDispatch` hook: + + + + +```jsx title="admin/src/pages/HomePage.jsx" +import { useSelector, useDispatch } from 'react-redux'; + +const HomePage = () => { + const dispatch = useDispatch(); + const currentTheme = useSelector( + (state) => state.admin_app?.theme?.currentTheme + ); + + const handleToggleTheme = () => { + const newTheme = + currentTheme === 'light' + ? 'dark' + : currentTheme === 'dark' + ? 'system' + : 'light'; + dispatch({ + type: 'admin/setAppTheme', + payload: newTheme, + }); + }; + + const handleChangeLocale = (locale) => { + dispatch({ + type: 'admin/setLocale', + payload: locale, + }); + }; + + return ( +
+ + +
+ ); +}; +``` + +
+ + +```tsx title="admin/src/pages/HomePage.tsx" +import { useSelector, useDispatch } from 'react-redux'; + +const HomePage = () => { + const dispatch = useDispatch(); + const currentTheme = useSelector( + (state: any) => state.admin_app?.theme?.currentTheme + ); + + const handleToggleTheme = () => { + const newTheme = + currentTheme === 'light' + ? 'dark' + : currentTheme === 'dark' + ? 'system' + : 'light'; + dispatch({ + type: 'admin/setAppTheme', + payload: newTheme, + } as any); + }; + + const handleChangeLocale = (locale: string) => { + dispatch({ + type: 'admin/setLocale', + payload: locale, + } as any); + }; + + return ( +
+ + +
+ ); +}; +``` + +
+
+ +### Available actions + +The `admin_app` slice provides the following actions: + +| Action type | Payload type | Description | +|---|---|---| +| `admin/setAppTheme` | `string` | Set the theme (`'light'`, `'dark'`, or `'system'`) | +| `admin/setLocale` | `string` | Set the locale (e.g., `'en'`, `'fr'`) | +| `admin/setToken` | `string \| null` | Set the authentication token | +| `admin/login` | `{ token: string, persist?: boolean }` | Login action with token and persistence option | +| `admin/logout` | `void` | Logout action (no payload) | + +:::note +When dispatching actions, use the Redux Toolkit action type format: `'sliceName/actionName'`. The admin slice is named `'admin'`, so actions follow the pattern `'admin/actionName'`. +::: + +## Accessing the store instance + +For advanced use cases, you can access the store instance directly using the `useStore` hook: + + + + +```jsx title="admin/src/pages/App.jsx" +import { useStore } from 'react-redux'; +import { useEffect } from 'react'; + +const App = () => { + const store = useStore(); + + useEffect(() => { + const state = store.getState(); + console.log('Redux Store State:', state); + + const unsubscribe = store.subscribe(() => { + const currentState = store.getState(); + console.log('Store state changed:', { + theme: currentState.admin_app?.theme?.currentTheme, + locale: currentState.admin_app?.language?.locale, + timestamp: new Date().toISOString(), + }); + }); + + return () => { + unsubscribe(); + }; + }, [store]); + + return
My Plugin
; +}; +``` + +
+ + +```tsx title="admin/src/pages/App.tsx" +import { useStore } from 'react-redux'; +import { useEffect } from 'react'; + +const App = () => { + const store = useStore(); + + useEffect(() => { + const state = store.getState(); + console.log('Redux Store State:', state); + + const unsubscribe = store.subscribe(() => { + const currentState = store.getState(); + console.log('Store state changed:', { + theme: currentState.admin_app?.theme?.currentTheme, + locale: currentState.admin_app?.language?.locale, + timestamp: new Date().toISOString(), + }); + }); + + return () => { + unsubscribe(); + }; + }, [store]); + + return
My Plugin
; +}; +``` + +
+
+ +## Complete example + +
+Example combining all 3 patterns (useSelector, useDispatch, useStore) + + + + +```jsx title="admin/src/pages/HomePage.jsx" +import { Main } from '@strapi/design-system'; +import { Button, Box, Typography, Flex } from '@strapi/design-system'; +import { useSelector, useDispatch, useStore } from 'react-redux'; +import { useEffect, useState } from 'react'; + +const HomePage = () => { + const dispatch = useDispatch(); + const store = useStore(); + + // Reading state + const currentTheme = useSelector( + (state) => state.admin_app?.theme?.currentTheme + ); + const currentLocale = useSelector( + (state) => state.admin_app?.language?.locale + ); + const isAuthenticated = useSelector((state) => !!state.admin_app?.token); + const availableLocales = useSelector( + (state) => state.admin_app?.language?.localeNames || {} + ); + + // Dispatching actions + const handleToggleTheme = () => { + const newTheme = + currentTheme === 'light' + ? 'dark' + : currentTheme === 'dark' + ? 'system' + : 'light'; + dispatch({ type: 'admin/setAppTheme', payload: newTheme }); + }; + + const handleChangeLocale = (locale) => { + dispatch({ type: 'admin/setLocale', payload: locale }); + }; + + // Subscribing to store changes + const [storeChangeCount, setStoreChangeCount] = useState(0); + const [lastChange, setLastChange] = useState(''); + + useEffect(() => { + const unsubscribe = store.subscribe(() => { + setStoreChangeCount((prev) => prev + 1); + setLastChange(new Date().toLocaleTimeString()); + }); + return () => unsubscribe(); + }, [store]); + + return ( +
+ + + Redux Store Examples + + + + + + Reading state + + + + Current Theme: {currentTheme || 'system'} + + + Current Locale: {currentLocale || 'en'} + + + Authentication Status:{' '} + + {isAuthenticated ? 'Authenticated' : 'Not Authenticated'} + + + + + + + + Dispatching actions + + + + {Object.keys(availableLocales).map((locale) => ( + + ))} + + + + + + Subscribing to store changes + + + + Store has changed {storeChangeCount} time(s) + + {lastChange && ( + + Last change at: {lastChange} + + )} + + + + +
+ ); +}; + +export { HomePage }; +``` + +
+ + +```tsx title="admin/src/pages/HomePage.tsx" +import { Main } from '@strapi/design-system'; +import { Button, Box, Typography, Flex } from '@strapi/design-system'; +import { useSelector, useDispatch, useStore } from 'react-redux'; +import { useEffect, useState } from 'react'; + +const HomePage = () => { + const dispatch = useDispatch(); + const store = useStore(); + + // Reading state + const currentTheme = useSelector( + (state: any) => state.admin_app?.theme?.currentTheme + ); + const currentLocale = useSelector( + (state: any) => state.admin_app?.language?.locale + ); + const isAuthenticated = useSelector((state: any) => !!state.admin_app?.token); + const availableLocales = useSelector( + (state: any) => state.admin_app?.language?.localeNames || {} + ); + + // Dispatching actions + const handleToggleTheme = () => { + const newTheme = + currentTheme === 'light' + ? 'dark' + : currentTheme === 'dark' + ? 'system' + : 'light'; + dispatch({ type: 'admin/setAppTheme', payload: newTheme } as any); + }; + + const handleChangeLocale = (locale: string) => { + dispatch({ type: 'admin/setLocale', payload: locale } as any); + }; + + // Subscribing to store changes + const [storeChangeCount, setStoreChangeCount] = useState(0); + const [lastChange, setLastChange] = useState(''); + + useEffect(() => { + const unsubscribe = store.subscribe(() => { + setStoreChangeCount((prev) => prev + 1); + setLastChange(new Date().toLocaleTimeString()); + }); + return () => unsubscribe(); + }, [store]); + + return ( +
+ + + Redux Store Examples + + + + + + Reading state + + + + Current Theme: {currentTheme || 'system'} + + + Current Locale: {currentLocale || 'en'} + + + Authentication Status:{' '} + + {isAuthenticated ? 'Authenticated' : 'Not Authenticated'} + + + + + + + + Dispatching actions + + + + {Object.keys(availableLocales).map((locale) => ( + + ))} + + + + + + Subscribing to store changes + + + + Store has changed {storeChangeCount} time(s) + + {lastChange && ( + + Last change at: {lastChange} + + )} + + + + +
+ ); +}; + +export { HomePage }; +``` + +
+
+ +
+ +## Best practices + +- **Use `useSelector` for reading state.** Prefer `useSelector` over direct store access. It automatically subscribes to updates and re-renders components when the selected state changes. +- **Clean up subscriptions.** Always unsubscribe from store subscriptions in `useEffect` cleanup functions to prevent memory leaks. +- **Consider type safety.** For better TypeScript support, create typed selectors or use typed hooks from `@strapi/admin` if available. +- **Avoid unnecessary dispatches.** Only dispatch actions when you need to update state. Reading state does not require dispatching actions. +- **Respect core state.** Be careful when modifying core admin state (like theme or locale) as it affects the entire admin panel. Consider whether your plugin should modify global state or maintain its own local state. + +:::tip +For plugins that need to add their own state to the Redux store, use the `addReducers` method in the `register` lifecycle function to inject custom reducers. See the [Admin Panel API documentation](/cms/plugins-development/admin-panel-api) for details. +::: \ No newline at end of file From 35a40fcbfbca445f4d386c10c0105e80ea2ebbfb Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:22:53 +0100 Subject: [PATCH 05/64] Restore snippet from `main` branch --- docusaurus/docs/snippets/sdk-plugin-v5-v6.md | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 docusaurus/docs/snippets/sdk-plugin-v5-v6.md diff --git a/docusaurus/docs/snippets/sdk-plugin-v5-v6.md b/docusaurus/docs/snippets/sdk-plugin-v5-v6.md new file mode 100644 index 0000000000..78930ad86c --- /dev/null +++ b/docusaurus/docs/snippets/sdk-plugin-v5-v6.md @@ -0,0 +1,23 @@ +:::caution Using SDK Plugin v5 (legacy) +If you need to continue using the previous build system with `@strapi/pack-up`, you can pin to version 5.x: + + + + + +```bash +yarn add @strapi/sdk-plugin@5 +``` + + + + +```bash +npm install @strapi/sdk-plugin@5 +``` + + + + +Version 5.x of the SDK plugin supports `packup.config.ts` for custom build configuration. However, v6 is recommended for security updates and simplified configuration. +::: \ No newline at end of file From 668a2eeaabd0b141a3f3a73396edf1ac4ca167de Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:24:24 +0100 Subject: [PATCH 06/64] Rework admin config. & custom. page --- .../admin-configuration-customization.md | 1123 ++++------------- 1 file changed, 251 insertions(+), 872 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-configuration-customization.md b/docusaurus/docs/cms/plugins-development/admin-configuration-customization.md index 616e0bfee0..343f2a660e 100644 --- a/docusaurus/docs/cms/plugins-development/admin-configuration-customization.md +++ b/docusaurus/docs/cms/plugins-development/admin-configuration-customization.md @@ -1,7 +1,8 @@ --- -title: Admin Configuration & Customization -pagination_prev: cms/plugins-development/plugin-sdk -pagination_next: cms/plugins-development/admin-panel-api +title: Admin configuration & customization +description: Configure and customize Strapi plugins in the admin panel with registerPlugin, menu links, and settings sections. +pagination_prev: cms/plugins-development/admin-panel-api +pagination_next: cms/plugins-development/admin-localization displayed_sidebar: cmsSidebar toc_max_heading_level: 4 tags: @@ -9,14 +10,19 @@ tags: - plugin configuration - plugin customization - registerPlugin - - plugin APIs - plugin lifecycle - plugins development --- -# Admin Configuration & Customization +# Admin configuration & customization -This section covers advanced configuration and customization options for Strapi plugins in the admin panel. Learn how to configure plugins, expose APIs, manage initialization, and customize the admin interface. + + +The admin entry file exports `register`, `bootstrap`, and `registerTrads`. Use `registerPlugin` for base setup, `addMenuLink` and `createSettingSection` for navigation. For localization, injection zones, and Redux store access, see the dedicated sub-pages. + + + +This section covers configuration and customization options for Strapi plugins in the admin panel. Learn how to configure plugins, manage initialization, and customize the admin interface. :::prerequisites You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin). @@ -24,22 +30,22 @@ You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin). ## Overview -The admin panel entry file (`admin/src/index.ts`) exports an object with lifecycle functions that control how your plugin integrates with Strapi's admin interface. The entry file can export three functions: +The admin panel entry file (`admin/src/index.ts`) exports an object with lifecycle functions that control how your plugin integrates with Strapi's admin interface. The entry file can export 3 functions: -### Available Exports +| Function | Type | Description | +|---|---|---| +| `register` | Required | Called to load the plugin before the app is bootstrapped. Use it to register the plugin, add menu links, create settings sections, define [injection zones](/cms/plugins-development/admin-injection-zones), and [add reducers](/cms/plugins-development/admin-panel-api#reducers-api). | +| `bootstrap` | Optional | Executed after all plugins are registered. Use it to extend other plugins, [register hooks](/cms/plugins-development/admin-panel-api#hooks-api), add settings links, inject components into injection zones, and add [Content Manager actions](/cms/plugins-development/content-manager-apis). | +| `registerTrads` | Required | Async function to register translation files for all locales. See [Admin localization](/cms/plugins-development/admin-localization). | -| Function | Type | Description | -| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `register` | Required | Lifecycle function called to load the plugin before the app is bootstrapped. Use this to register the plugin, add menu links, create settings sections, define injection zones, and add reducers. | -| `bootstrap` | Optional | Lifecycle function executed after all plugins are registered. Use this to extend other plugins, register hooks, add settings links, inject components into injection zones, and add Content Manager actions. | -| `registerTrads` | Required | Async function to register translation files for all locales. This creates separate chunks for application translations to reduce build size. | +## Base configuration -## Base Configuration +The `registerPlugin` method is the core function for registering a plugin in the admin panel. Call this method within the `register` lifecycle function of your plugin's entry file (`admin/src/index.ts`). It configures the main options to integrate the plugin with Strapi's admin interface. -The `registerPlugin` method is the core function for registering a plugin in the admin panel. This method is called within the `register` lifecycle function of your plugin's entry file (`admin/src/index.ts`) and configures the main options to integrate the plugin with Strapi's admin interface. + + -```typescript -// admin/src/index.ts +```js title="admin/src/index.js" export default { register(app) { app.registerPlugin({ @@ -58,302 +64,90 @@ export default { }; ``` -### Configuration Options - -| Parameter | Type | Required | Description | -| ---------------- | ------------------------- | -------- | ----------------------------------------- | -| `id` | `string` | ✅ | Unique plugin identifier | -| `name` | `string` | ✅ | Plugin display name | -| `apis` | `Record` | ❌ | APIs exposed to other plugins | -| `initializer` | `React.ComponentType` | ❌ | Component for plugin initialization | -| `injectionZones` | `object` | ❌ | Available injection zones | -| `isReady` | `boolean` | ❌ | Plugin readiness status (default: `true`) | - -## Admin Localization + + -Plugins can provide translations for multiple languages to make the admin interface accessible to users worldwide. Strapi automatically loads and merges plugin translations with core translations, making them available throughout the admin panel. - -### Translation File Structure - -Translation files are stored in the `admin/src/translations/` directory, with one JSON file per locale: - -``` -admin/src/translations/ - ├── en.json - ├── fr.json - ├── de.json - └── ... -``` - -Each translation file contains key-value pairs where keys are translation identifiers and values are the translated strings: - -```json -// admin/src/translations/en.json -{ - "plugin.name": "My Plugin", - "plugin.description": "A custom Strapi plugin", - "settings.title": "Settings", - "settings.general": "General", - "settings.advanced": "Advanced" -} -``` - -### The `registerTrads` Function - -The `registerTrads` function is a required lifecycle function that loads translation files for all configured locales. Strapi calls this function during the admin panel initialization to collect translations from all plugins. - -#### Basic Implementation - -```typescript -// admin/src/index.ts -import { prefixPluginTranslations } from './utils/prefixPluginTranslations'; -import { PLUGIN_ID } from './pluginId'; +```ts title="admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; export default { - register(app) { + register(app: StrapiApp) { app.registerPlugin({ - id: PLUGIN_ID, + id: 'my-plugin', name: 'My Plugin', + apis: { + // APIs exposed to other plugins + }, + initializer: MyInitializerComponent, + injectionZones: { + // Areas where other plugins can inject components + }, + isReady: false, // Plugin readiness status }); }, - async registerTrads({ locales }) { - const importedTranslations = await Promise.all( - locales.map((locale) => { - return import(`./translations/${locale}.json`) - .then(({ default: data }) => { - return { - data: prefixPluginTranslations(data, PLUGIN_ID), - locale, - }; - }) - .catch(() => { - return { - data: {}, - locale, - }; - }); - }), - ); - - return importedTranslations; - }, -}; -``` - -#### Function Parameters - -The `registerTrads` function receives an object with the following property: - -| Parameter | Type | Description | -| --------- | ---------- | -------------------------------------------------------------------------------- | -| `locales` | `string[]` | Array of locale codes configured in the admin panel (e.g., `['en', 'fr', 'de']`) | - -#### Return Value - -The function must return a `Promise` that resolves to an array of translation objects. Each object has the following structure: - -```typescript -{ - data: Record; // Translation key-value pairs - locale: string; // Locale code (e.g., 'en', 'fr') -} -``` - -#### Translation Key Prefixing - -**Important**: Translation keys must be prefixed with your plugin ID to avoid conflicts with other plugins and core Strapi translations. For example, if your plugin ID is `my-plugin`, a key like `plugin.name` should become `my-plugin.plugin.name`. - -Use the `prefixPluginTranslations` utility function to automatically prefix all keys: - -```typescript -// admin/src/utils/prefixPluginTranslations.ts -type TradOptions = Record; - -const prefixPluginTranslations = ( - trad: TradOptions, - pluginId: string, -): TradOptions => { - if (!pluginId) { - throw new TypeError("pluginId can't be empty"); - } - return Object.keys(trad).reduce((acc, current) => { - acc[`${pluginId}.${current}`] = trad[current]; - return acc; - }, {} as TradOptions); }; - -export { prefixPluginTranslations }; ``` -**Example**: If your translation file contains: + + -```json -{ - "plugin.name": "My Plugin", - "settings.title": "Settings" -} -``` +### Configuration options -After prefixing with plugin ID `my-plugin`, these become: +| Parameter | Type | Required | Description | +|---|---|---|---| +| `id` | `string` | ✅ | Unique plugin identifier | +| `name` | `string` | ✅ | Plugin display name | +| `apis` | `Record` | ❌ | APIs exposed to other plugins | +| `initializer` | `React.ComponentType` | ❌ | Component for plugin initialization | +| `injectionZones` | `object` | ❌ | Available injection zones (see [Injection zones](/cms/plugins-development/admin-injection-zones)) | +| `isReady` | `boolean` | ❌ | Plugin readiness status (default: `true`) | -- `my-plugin.plugin.name` -- `my-plugin.settings.title` +## Customizing menu and settings -#### Handling Missing Translation Files +Plugins can customize the admin panel's navigation sidebar and settings pages to provide access to their features. -The `registerTrads` function should gracefully handle missing translation files by returning an empty object for that locale. The `.catch()` handler in the example above ensures that if a translation file doesn't exist, the plugin still returns a valid translation object: +### Navigation sidebar (menu links) -```typescript -.catch(() => { - return { - data: {}, - locale, - }; -}); -``` - -This allows plugins to provide translations for only some locales (e.g., only English) without breaking the admin panel for other locales. - -### Using Translations in Components - -To use translations in your React components, use the `useIntl` hook from `react-intl`: - -```typescript -// admin/src/pages/HomePage.tsx -import { useIntl } from 'react-intl'; -import { PLUGIN_ID } from '../pluginId'; - -const HomePage = () => { - const { formatMessage } = useIntl(); - - return ( -
-

- {formatMessage({ - id: `${PLUGIN_ID}.plugin.name`, - defaultMessage: 'My Plugin', - })} -

-

- {formatMessage({ - id: `${PLUGIN_ID}.plugin.description`, - defaultMessage: 'A custom Strapi plugin', - })} -

-
- ); -}; - -export default HomePage; -``` - -#### Helper Function for Translation Keys - -To avoid repeating the plugin ID prefix, create a helper function: - -```typescript -// admin/src/utils/getTranslation.ts -import { PLUGIN_ID } from '../pluginId'; - -export const getTranslation = (id: string) => `${PLUGIN_ID}.${id}`; -``` +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. -Then use it in components: - -```typescript -// admin/src/pages/HomePage.tsx -import { useIntl } from 'react-intl'; -import { getTranslation } from '../utils/getTranslation'; - -const HomePage = () => { - const { formatMessage } = useIntl(); - - return ( -
-

- {formatMessage({ - id: getTranslation('plugin.name'), - defaultMessage: 'My Plugin', - })} -

-
- ); -}; -``` +#### Adding a menu link -### Using Translations in Configuration + + -Translation keys are also used when configuring menu links, settings sections, and other admin panel elements: +```jsx title="admin/src/index.js" +import PluginIcon from './components/PluginIcon'; -```typescript -// admin/src/index.ts export default { register(app) { app.addMenuLink({ - to: '/plugins/my-plugin', + to: `/plugins/my-plugin`, icon: PluginIcon, intlLabel: { - id: 'my-plugin.plugin.name', // Prefixed translation key - defaultMessage: 'My Plugin', // Fallback if translation missing + id: 'my-plugin.plugin.name', + defaultMessage: 'My Plugin', }, Component: async () => { const { App } = await import('./pages/App'); return 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', }); }, }; ``` -### How Strapi Integrates Plugin Translations - -Strapi's admin panel automatically: - -1. **Calls `registerTrads`** for all registered plugins during initialization -2. **Merges translations** from all plugins with core Strapi translations -3. **Applies custom translations** from the admin configuration (if any) -4. **Makes translations available** via `react-intl` throughout the admin panel - -The merge order ensures that: - -- Core Strapi translations are loaded first -- Plugin translations are merged on top -- Custom translations from admin config override both (allowing users to customize translations) - -### Best Practices + + -1. **Always prefix translation keys**: Use `prefixPluginTranslations` or manually prefix keys with your plugin ID to avoid conflicts. - -2. **Provide default messages**: Always include `defaultMessage` when using `formatMessage` as a fallback if translations are missing. - -3. **Handle missing translations gracefully**: The `registerTrads` function should return empty objects for missing locales rather than throwing errors. - -4. **Use descriptive key names**: Choose clear, hierarchical key names (e.g., `settings.general.title` rather than `title1`). - -5. **Support at least English**: While not required, providing English translations ensures your plugin works out of the box. - -6. **Test with multiple locales**: Verify that your plugin works correctly when different locales are selected in the admin panel. - -:::note -The `en` locale is always available in Strapi and serves as the fallback locale. If a translation is missing for a selected locale, Strapi will use the English translation. -::: - -:::tip -To see which locales are available in your Strapi instance, check the `config.locales` array in your `src/admin/app.ts` or `src/admin/app.js` file. You can also access available locales from the Redux store using `useSelector((state) => state.admin_app?.language?.localeNames)`. -::: - -## Customizing Menu and Settings - -Plugins can customize the admin panel's navigation sidebar and settings pages to provide easy access to their features. This section covers how to add menu links to the main navigation and how to create or extend settings sections. - -### 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, which should be called within the `register` lifecycle function. - -#### Adding a Menu Link - -```typescript -// admin/src/index.ts +```ts title="admin/src/index.ts" import PluginIcon from './components/PluginIcon'; import type { StrapiApp } from '@strapi/admin/strapi-admin'; @@ -383,37 +177,39 @@ export default { }; ``` -#### Menu Link Parameters + + + +#### Menu link parameters -| Parameter | Type | Required | Description | -| ------------- | ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `to` | `string` | ✅ | Path the link should point to (relative to the admin panel root) | -| `icon` | `React.Component` | ✅ | React component for the icon to display in the navigation | -| `intlLabel` | `object` | ✅ | Localized label object with:
  • `id`: Translation key used in translation files
  • `defaultMessage`: Default label if translation is missing
| -| `Component` | `async function` | ✅ | Async function that returns a dynamic import of the plugin's main page component | -| `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | -| `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`) | +| Parameter | Type | Required | Description | +|---|---|---|---| +| `to` | `string` | ✅ | Path the link should point to (relative to the admin panel root) | +| `icon` | `React.Component` | ✅ | React component for the icon to display in the navigation | +| `intlLabel` | `object` | ✅ | Localized label object with `id` (translation key) and `defaultMessage` (fallback label) | +| `Component` | `async function` | ✅ | Async function that returns a dynamic import of the plugin's main page component | +| `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | +| `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`) | :::note -The `intlLabel.id` values should correspond to keys in your translation files located at `admin/src/translations/[locale].json`. +The `intlLabel.id` values should correspond to keys in your translation files located at `admin/src/translations/[locale].json`. See [Admin localization](/cms/plugins-development/admin-localization) for details. ::: ### 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 +#### Creating a new settings section -To create a new settings section with multiple links, use `createSettingSection()` in the `register` lifecycle function: +Use `createSettingSection()` in the `register` lifecycle function: -```typescript -// admin/src/index.ts -import type { StrapiApp } from '@strapi/admin/strapi-admin'; -import { PERMISSIONS } from './constants'; + + +```jsx title="admin/src/index.js" export default { - register(app: StrapiApp) { + register(app) { app.createSettingSection( { id: 'my-plugin', @@ -460,54 +256,100 @@ export default { }; ``` -#### createSettingSection Parameters + + + +```ts title="admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; -**Function Signature:** +export default { + register(app: StrapiApp) { + app.createSettingSection( + { + 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: async () => { + const { GeneralSettings } = + await import('./pages/Settings/General'); + return GeneralSettings; + }, + }, + { + intlLabel: { + id: 'my-plugin.settings.advanced', + defaultMessage: 'Advanced', + }, + id: 'advanced', + to: 'my-plugin/advanced', + Component: async () => { + const { AdvancedSettings } = + await import('./pages/Settings/Advanced'); + return AdvancedSettings; + }, + }, + ], + ); -```typescript -createSettingSection( - sectionConfig: { id: string; intlLabel: { id: string; defaultMessage: string } }, - links: Array -) + app.registerPlugin({ + id: 'my-plugin', + name: 'My Plugin', + }); + }, +}; ``` -**First Argument - `sectionConfig` (object):** + + -| Parameter | Type | Required | Description | -| ----------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `id` | `string` | ✅ | Unique identifier for the settings section | -| `intlLabel` | `object` | ✅ | Localized label object with:
  • `id` (string): Translation key used in translation files
  • `defaultMessage` (string): Default label if translation is missing
| +#### `createSettingSection` parameters -**Second Argument - `links` (array):** +**First argument** (`sectionConfig`): -An array of link configuration objects, where each object has the following properties: +| Parameter | Type | Required | Description | +|---|---|---|---| +| `id` | `string` | ✅ | Unique identifier for the settings section | +| `intlLabel` | `object` | ✅ | Localized label object with `id` (translation key) and `defaultMessage` (fallback label) | -| Parameter | Type | Required | Description | -| ------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------ | -| `id` | `string` | ✅ | Unique identifier for the settings link | -| `to` | `string` | ✅ | Path relative to the settings route (should not include `settings/` prefix) | -| `intlLabel` | `object` | ✅ | Localized label object with `id` and `defaultMessage` | -| `Component` | `async function` | ✅ | Async function that returns a dynamic import of the settings page component | -| `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | -| `licenseOnly` | `boolean` | ❌ | If `true`, displays a ⚡ icon to indicate the feature requires a paid license (default: `false`) | +**Second argument** (`links`), an array of link objects: -#### Adding Links to Existing Settings Sections +| Parameter | Type | Required | Description | +|---|---|---|---| +| `id` | `string` | ✅ | Unique identifier for the settings link | +| `to` | `string` | ✅ | Path relative to the settings route (do not include `settings/` prefix) | +| `intlLabel` | `object` | ✅ | Localized label object with `id` and `defaultMessage` | +| `Component` | `async function` | ✅ | Async function that returns a dynamic import of the settings page component | +| `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | +| `licenseOnly` | `boolean` | ❌ | If `true`, displays a ⚡ icon (default: `false`) | -To add links to existing settings sections (like the "Global" section), use `addSettingsLink()` in the `bootstrap` lifecycle function: +#### Adding links to existing settings sections -```typescript -// admin/src/index.ts -import type { StrapiApp } from '@strapi/admin/strapi-admin'; +To add a single link to an existing settings section, use `addSettingsLink()` in the `bootstrap` lifecycle function. To add multiple links at once, use `addSettingsLinks()`. + + + +```jsx title="admin/src/index.js" export default { - register(app: StrapiApp) { + register(app) { app.registerPlugin({ id: 'my-plugin', name: 'My Plugin', }); }, - bootstrap(app: StrapiApp) { - // Add a link to the global settings section + bootstrap(app) { + // Add a single link to the global settings section app.addSettingsLink('global', { intlLabel: { id: 'my-plugin.settings.documentation', @@ -523,83 +365,45 @@ export default { permissions: [], licenseOnly: false, }); + + // Add multiple links at once to the global settings section + app.addSettingsLinks('global', [ + { + intlLabel: { + id: 'my-plugin.settings.general', + defaultMessage: 'General', + }, + id: 'general', + to: 'my-plugin/general', + Component: async () => { + const { GeneralSettings } = + await import('./pages/Settings/General'); + return GeneralSettings; + }, + }, + { + intlLabel: { + id: 'my-plugin.settings.advanced', + defaultMessage: 'Advanced', + }, + id: 'advanced', + to: 'my-plugin/advanced', + Component: async () => { + const { AdvancedSettings } = + await import('./pages/Settings/Advanced'); + return AdvancedSettings; + }, + }, + ]); }, }; ``` -#### addSettingsLink Parameters - -**Function Signature:** - -```typescript -addSettingsLink( - sectionId: string, - linkConfig: LinkConfig -) -``` - -**First Argument - `sectionId` (string):** - -The ID of the existing settings section to extend (e.g., `'global'` or `'permissions'`). - -**Second Argument - `linkConfig` (object):** - -A link configuration object with the following properties: - -| Parameter | Type | Required | Description | -| ------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------ | -| `id` | `string` | ✅ | Unique identifier for the settings link | -| `to` | `string` | ✅ | Path relative to the settings route (should not include `settings/` prefix) | -| `intlLabel` | `object` | ✅ | Localized label object with `id` and `defaultMessage` | -| `Component` | `async function` | ✅ | Async function that returns a dynamic import of the settings page component | -| `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | -| `licenseOnly` | `boolean` | ❌ | If `true`, displays a ⚡ icon to indicate the feature requires a paid license (default: `false`) | - -#### Available Settings Sections - -Strapi provides several built-in settings sections that plugins can extend: - -- **`global`**: General application settings -- **`permissions`**: Administration panel settings - -:::note - -- Creating a new settings section must be done in the `register` lifecycle function -- Adding links to existing settings sections must be done in the `bootstrap` lifecycle function -- The `to` path for settings links should be relative to the settings route (e.g., `'my-plugin/general'` not `'settings/my-plugin/general'`) - ::: - -## Customizing Existing Admin Sections - -Plugins can extend and customize existing admin panel sections, such as the Content Manager's List and Edit views, by injecting custom React components into predefined areas. This allows you to add functionality to Strapi's built-in interfaces without modifying core code. - -### What are Injection Zones? - -Injection zones are the mechanism that enables this customization. They are predefined areas in a plugin's UI where other plugins can inject custom React components. - -:::note -Injection zones are defined in the `register` lifecycle function, but components are injected in the `bootstrap` lifecycle function. -::: - -### Predefined Injection Zones + + -Strapi's Content Manager provides predefined injection zones that plugins can use to add components to the Content Manager interface: - -| View | Injection Zone | Location | -| --------- | ---------------------- | --------------------------------------------------- | -| List view | `listView.actions` | Between the Filters and the cogs icon | -| Edit view | `editView.right-links` | Between the "Configure the view" and "Edit" buttons | -| Preview | `preview.actions` | In the preview view action area | - -#### Injecting into Content Manager Zones - -To inject a component into a Content Manager injection zone, use `getPlugin('content-manager').injectComponent()` in the `bootstrap` lifecycle of your plugin: - -```typescript -// admin/src/index.ts +```ts title="admin/src/index.ts" import type { StrapiApp } from '@strapi/admin/strapi-admin'; -import { MyCustomButton } from './components/MyCustomButton'; -import { PreviewAction } from './components/PreviewAction'; export default { register(app: StrapiApp) { @@ -609,495 +413,70 @@ export default { }); }, 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: () => , - }); - - // Inject a component into the Preview view's actions zone - app.getPlugin('content-manager').injectComponent('preview', 'actions', { - name: 'my-plugin-preview-action', - Component: PreviewAction, + // 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: async () => { + const { DocumentationPage } = + await import('./pages/Settings/Documentation'); + return DocumentationPage; + }, + permissions: [], + licenseOnly: false, }); - }, -}; -``` - -### Creating Custom Injection Zones -Plugins can define their own injection zones to allow other plugins to extend their UI. Injection zones are declared in the `registerPlugin` configuration: - -```typescript -// admin/src/index.ts -import type { StrapiApp } from '@strapi/admin/strapi-admin'; - -export default { - register(app: StrapiApp) { - app.registerPlugin({ - id: 'dashboard', - name: 'Dashboard', - injectionZones: { - 'dashboard.main': { - top: [], // Components injected at the top - middle: [], // Components injected in the middle - bottom: [], // Components injected at the bottom + // Add multiple links at once to the global settings section + app.addSettingsLinks('global', [ + { + intlLabel: { + id: 'my-plugin.settings.general', + defaultMessage: 'General', }, - 'dashboard.sidebar': { - before: [], // Components injected before sidebar content - after: [], // Components injected after sidebar content + id: 'general', + to: 'my-plugin/general', + Component: async () => { + const { GeneralSettings } = + await import('./pages/Settings/General'); + return GeneralSettings; }, }, - }); - }, -}; -``` - -#### Using Injection Zones in Components - -To render injected components in your plugin's UI, use the `` component from `@strapi/helper-plugin`: - -```typescript -// admin/src/pages/Dashboard.tsx -import { InjectionZone } from '@strapi/helper-plugin'; - -const Dashboard = () => { - return ( -
-

Dashboard

- - {/* Render components injected into the top zone */} - - -
{/* Main dashboard content */}
- - {/* Render components injected into the bottom zone */} - -
- ); -}; - -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: - -```typescript -// Another plugin's admin/src/index.ts -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) { - // Get the dashboard plugin and inject a widget - const dashboardPlugin = app.getPlugin('dashboard'); - - if (dashboardPlugin) { - dashboardPlugin.injectComponent('dashboard.main', 'top', { - name: 'widget-plugin-statistics', - Component: Widget, - }); - } + { + intlLabel: { + id: 'my-plugin.settings.advanced', + defaultMessage: 'Advanced', + }, + id: 'advanced', + to: 'my-plugin/advanced', + Component: async () => { + const { AdvancedSettings } = + await import('./pages/Settings/Advanced'); + return AdvancedSettings; + }, + }, + ]); }, }; ``` -### 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` | Component configuration object with:
  • `name` (string): Unique name for the component
  • `Component` (React.ComponentType): The React component to inject
| - -### Accessing Content Manager Data - -When injecting components into Content Manager injection zones, you can access the Edit View data using the `useCMEditViewDataManager` hook: - -```typescript -// admin/src/components/MyCustomButton.tsx -import { useCMEditViewDataManager } from '@strapi/helper-plugin'; - -export const MyCustomButton = () => { - const { - slug, // Content type slug (e.g., 'api::article.article') - modifiedData, // Current form data - initialData, // Original data - isCreatingEntry, // Whether creating a new entry - layout, // Content type layout - onPublish, // Function to publish the entry - onChange, // Function to update field values - } = useCMEditViewDataManager(); - - const handleCustomAction = () => { - // Access and modify the entry data - onChange({ target: { name: 'customField', value: 'new value' } }); - }; - - return ; -}; -``` - -### Best Practices - -1. **Use descriptive zone names**: Choose clear, descriptive names for your injection zones (e.g., `top`, `bottom`, `before`, `after`) - -2. **Check plugin availability**: Always verify that a plugin exists before injecting components into its zones: - - ```typescript - bootstrap(app: StrapiApp) { - const targetPlugin = app.getPlugin('target-plugin'); - if (targetPlugin) { - targetPlugin.injectComponent('view', 'zone', { - name: 'my-component', - Component: MyComponent, - }); - } - } - ``` - -3. **Use unique component names**: Ensure component names are unique to avoid conflicts with other plugins - -4. **Handle missing zones gracefully**: Components should handle cases where injection zones might not be available - -5. **Document your injection zones**: Clearly document which injection zones your plugin provides and their intended use - -## Accessing the Redux Store - -Strapi's admin panel uses a global Redux store to manage application state. Plugins can access this store to read state, dispatch actions, and subscribe to state changes. This enables plugins to interact with core admin functionality like theme settings, language preferences, and authentication state. - -### Store Overview - -The Redux store is automatically provided to all plugin components through React Redux's `Provider`. The store contains several slices: - -- **`admin_app`**: Core admin state including theme, language, permissions, and authentication token -- **`adminApi`**: RTK Query API state for admin endpoints -- **Plugin-specific slices**: Additional reducers added by plugins +
+ -### Reading State with `useSelector` +#### `addSettingsLink` and `addSettingsLinks` parameters -The most common way to access Redux state in your plugin components is using the `useSelector` hook from `react-redux`: +Both functions take a `sectionId` string as the first argument (e.g., `'global'` or `'permissions'`). The second argument is a single link object for `addSettingsLink` or an array of link objects for `addSettingsLinks`, using the same properties as the `links` array in `createSettingSection` (see table above). -```typescript -// admin/src/pages/HomePage.tsx -import { useSelector } from 'react-redux'; +#### Available settings sections -const HomePage = () => { - // Read current theme - const currentTheme = useSelector( - (state: any) => state.admin_app?.theme?.currentTheme - ); +Strapi provides built-in settings sections that plugins can extend: - // Read current locale - const currentLocale = useSelector( - (state: any) => state.admin_app?.language?.locale - ); - - // Read authentication status - const isAuthenticated = useSelector((state: any) => !!state.admin_app?.token); - - // Read available locales - const availableLocales = useSelector( - (state: any) => state.admin_app?.language?.localeNames || {} - ); - - return ( -
-

Current Theme: {currentTheme}

-

Current Locale: {currentLocale}

-

Authenticated: {isAuthenticated ? 'Yes' : 'No'}

-
- ); -}; -``` - -#### Available State Properties - -The `admin_app` slice contains the following state properties: - -| Property | Type | Description | -| ----------------------- | ---------------- | -------------------------------------------------- | -| `theme.currentTheme` | `string` | Current theme (`'light'`, `'dark'`, or `'system'`) | -| `theme.availableThemes` | `string[]` | Array of available theme names | -| `language.locale` | `string` | Current locale code (e.g., `'en'`, `'fr'`) | -| `language.localeNames` | `object` | Object mapping locale codes to display names | -| `token` | `string \| null` | Authentication token | -| `permissions` | `object` | User permissions object | - -### Dispatching Actions - -To update the Redux store, use the `useDispatch` hook to dispatch actions: - -```typescript -// admin/src/pages/HomePage.tsx -import { useDispatch } from 'react-redux'; - -const HomePage = () => { - const dispatch = useDispatch(); - const currentTheme = useSelector( - (state: any) => state.admin_app?.theme?.currentTheme - ); - - const handleToggleTheme = () => { - // Dispatch action using Redux Toolkit action type format - const newTheme = - currentTheme === 'light' - ? 'dark' - : currentTheme === 'dark' - ? 'system' - : 'light'; - dispatch({ - type: 'admin/setAppTheme', - payload: newTheme, - } as any); - }; - - const handleChangeLocale = (locale: string) => { - dispatch({ - type: 'admin/setLocale', - payload: locale, - } as any); - }; - - return ( -
- - -
- ); -}; -``` - -#### Available Actions - -The `admin_app` slice provides the following actions: - -| Action Type | Payload Type | Description | -| ------------------- | -------------------------------------- | -------------------------------------------------- | -| `admin/setAppTheme` | `string` | Set the theme (`'light'`, `'dark'`, or `'system'`) | -| `admin/setLocale` | `string` | Set the locale (e.g., `'en'`, `'fr'`) | -| `admin/setToken` | `string \| null` | Set the authentication token | -| `admin/login` | `{ token: string, persist?: boolean }` | Login action with token and persistence option | -| `admin/logout` | `void` | Logout action (no payload) | +- `global`: General application settings +- `permissions`: Administration panel settings :::note -When dispatching actions, use the Redux Toolkit action type format: `'sliceName/actionName'`. The admin slice is named `'admin'`, so actions follow the pattern `'admin/actionName'`. -::: - -### Accessing the Store Instance - -For advanced use cases, you can access the store instance directly using the `useStore` hook: - -```typescript -// admin/src/pages/App.tsx -import { useStore } from 'react-redux'; -import { useEffect } from 'react'; - -const App = () => { - const store = useStore(); - - useEffect(() => { - // Get current state - const state = store.getState(); - console.log('Redux Store State:', state); - - // Subscribe to store changes - const unsubscribe = store.subscribe(() => { - const currentState = store.getState(); - console.log('Store state changed:', { - theme: currentState.admin_app?.theme?.currentTheme, - locale: currentState.admin_app?.language?.locale, - timestamp: new Date().toISOString(), - }); - }); - - // Cleanup subscription on unmount - return () => { - unsubscribe(); - }; - }, [store]); - - return
My Plugin
; -}; -``` - -### Complete Example - -Here's a complete example demonstrating all three Redux store access patterns: - -```typescript -// admin/src/pages/HomePage.tsx -import { Main } from '@strapi/design-system'; -import { Button, Box, Typography, Flex } from '@strapi/design-system'; -import { useSelector, useDispatch, useStore } from 'react-redux'; -import { useEffect, useState } from 'react'; - -const HomePage = () => { - const dispatch = useDispatch(); - const store = useStore(); - - // Example 1: Reading state from Redux store using useSelector - const currentTheme = useSelector( - (state: any) => state.admin_app?.theme?.currentTheme - ); - const currentLocale = useSelector( - (state: any) => state.admin_app?.language?.locale - ); - const isAuthenticated = useSelector((state: any) => !!state.admin_app?.token); - const availableLocales = useSelector( - (state: any) => state.admin_app?.language?.localeNames || {} - ); - - // Example 2: Dispatching actions to update the store - const handleToggleTheme = () => { - const newTheme = - currentTheme === 'light' - ? 'dark' - : currentTheme === 'dark' - ? 'system' - : 'light'; - dispatch({ - type: 'admin/setAppTheme', - payload: newTheme, - } as any); - }; - - const handleChangeLocale = (locale: string) => { - dispatch({ - type: 'admin/setLocale', - payload: locale, - } as any); - }; - - // Example 3: Subscribing to store changes - const [storeChangeCount, setStoreChangeCount] = useState(0); - const [lastChange, setLastChange] = useState(''); - - useEffect(() => { - const unsubscribe = store.subscribe(() => { - const state = store.getState(); - setStoreChangeCount((prev) => prev + 1); - setLastChange(new Date().toLocaleTimeString()); - - console.log('Store state changed:', { - theme: state.admin_app?.theme?.currentTheme, - locale: state.admin_app?.language?.locale, - timestamp: new Date().toISOString(), - }); - }); - - return () => { - unsubscribe(); - }; - }, [store]); - - return ( -
- - - Redux Store Examples - - - - {/* Example 1: Reading State */} - - - Example 1: Reading State - - - - Current Theme: {currentTheme || 'system'} - - - Current Locale: {currentLocale || 'en'} - - - Authentication Status:{' '} - - {isAuthenticated ? 'Authenticated' : 'Not Authenticated'} - - - - - - {/* Example 2: Dispatching Actions */} - - - Example 2: Dispatching Actions - - - - {Object.keys(availableLocales).map((locale) => ( - - ))} - - - - {/* Example 3: Subscribing to Store Changes */} - - - Example 3: Subscribing to Store Changes - - - - Store has changed {storeChangeCount} time(s) - - {lastChange && ( - - Last change at: {lastChange} - - )} - - - - -
- ); -}; - -export { HomePage }; -``` - -### Best Practices - -1. **Use `useSelector` for reading state**: Prefer `useSelector` over direct store access for reading state, as it automatically subscribes to updates and re-renders components when the selected state changes. - -2. **Clean up subscriptions**: Always unsubscribe from store subscriptions in `useEffect` cleanup functions to prevent memory leaks. - -3. **Type safety**: For better TypeScript support, consider creating typed selectors or using the typed hooks from `@strapi/admin` if available. - -4. **Avoid unnecessary dispatches**: Only dispatch actions when you need to update state. Reading state doesn't require dispatching actions. - -5. **Respect core state**: Be careful when modifying core admin state (like theme or locale) as it affects the entire admin panel. Consider whether your plugin should modify global state or maintain its own local state. - -:::tip -For plugins that need to add their own state to the Redux store, use the `addReducers` method in the `register` lifecycle function to inject custom reducers. See the [Admin Panel API documentation](/cms/plugins-development/admin-panel-api) for more details. -::: +Creating a new settings section must be done in the `register` lifecycle function. Adding links to existing settings sections must be done in the `bootstrap` lifecycle function. The `to` path for settings links should be relative to the settings route (e.g., `'my-plugin/general'` not `'settings/my-plugin/general'`). +::: \ No newline at end of file From 9f7d387e00f9f238b4fa287e4b7329381d8904ba Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:24:43 +0100 Subject: [PATCH 07/64] Rework admin-panel-api to point to new sub-topics --- .../plugins-development/admin-panel-api.md | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-panel-api.md b/docusaurus/docs/cms/plugins-development/admin-panel-api.md index e05840d6b5..47cb61850c 100644 --- a/docusaurus/docs/cms/plugins-development/admin-panel-api.md +++ b/docusaurus/docs/cms/plugins-development/admin-panel-api.md @@ -1,7 +1,7 @@ --- title: Admin Panel API -pagination_prev: cms/plugins-development/admin-configuration-customization -pagination_next: cms/plugins-development/content-manager-apis +pagination_prev: cms/plugins-development/plugin-sdk +pagination_next: cms/plugins-development/admin-configuration-customization toc_max_heading_level: 4 tags: - admin panel @@ -23,7 +23,7 @@ tags: # Admin Panel API for plugins -The Admin Panel API exposes `register`, `bootstrap`, and `registerTrads` hooks to inject React components and translations into Strapi’s UI. Menu, settings, injection zone, reducer, and hook APIs let plugins add navigation, configuration panels, or custom actions. +The Admin Panel API exposes `register`, `bootstrap`, and `registerTrads` hooks to inject React components and translations into Strapi's UI. Menu, settings, injection zone, reducer, and hook APIs let plugins add navigation, configuration panels, or custom actions. A Strapi plugin can interact with both the [back end](/cms/plugins-development/server-api) and the front end of a Strapi application. The Admin Panel API is about the front end part, i.e. it allows a plugin to customize Strapi's [admin panel](/cms/intro). @@ -121,6 +121,10 @@ export default { }; ``` +:::strapi Additional configuration options +For details on `apis`, `initializer`, and `isReady`, see [Admin configuration & customization](/cms/plugins-development/admin-configuration-customization#base-configuration). +::: + ### bootstrap() **Type**: `Function` @@ -158,6 +162,10 @@ While [`register()`](#register) and [`bootstrap()`](#bootstrap) are lifecycle fu To reduce the build size, the admin panel is only shipped with 2 locales by default (`en` and `fr`). The `registerTrads()` function is used to register a plugin's translations files and to create separate chunks for the application translations. It does not need to be modified. +:::strapi Translations in depth +For a full guide on key prefixing, using translations in components, and best practices, see [Admin localization](/cms/plugins-development/admin-localization). +::: +
Example: Register a plugin's translation files @@ -236,6 +244,10 @@ The Menu API allows a plugin to add a new link to the main navigation through th `intlLabel.id` are ids used in translation files (`[plugin-name]/admin/src/translations/[language].json`) ::: +:::strapi Detailed examples +For more details on menu links and localization, see [Admin configuration & customization](/cms/plugins-development/admin-configuration-customization#navigation-sidebar-menu-links). +::: + **Example:** @@ -315,6 +327,10 @@ All functions accept links as objects with the following parameters: | `permissions` | Array of Objects | Permissions declared in the `permissions.js` file of the plugin | | `licenseOnly` | Boolean | If set to `true`, adds a lightning ⚡️ icon next to the icon or menu entry to indicate that the feature or plugin requires a paid license.
(Defaults to `false`) | +:::strapi TypeScript examples +For more details and TypeScript examples, see [Admin configuration & customization](/cms/plugins-development/admin-configuration-customization#settings). +::: + #### createSettingSection() **Type**: `Function` @@ -444,6 +460,10 @@ Plugins can use: * Strapi's [predefined injection zones](#using-predefined-injection-zones) for the Content Manager, * or custom injection zones, created by a plugin +:::strapi Comprehensive injection zones guide +For details on custom zone creation, component rendering, and Content Manager data access, see [Admin injection zones](/cms/plugins-development/admin-injection-zones). +::: + :::note Injection zones are defined in the [register()](#register) lifecycle but components are injected in the [bootstrap()](#bootstrap) lifecycle. ::: @@ -552,6 +572,10 @@ export default { Once an injection zone is defined, the component to be injected in the Content Manager can have access to all the data of the Edit View through the `useContentManagerContext` React hook. +:::strapi Full hook reference +For a detailed guide on using this hook with all available properties, see [Admin injection zones — Accessing Content Manager data](/cms/plugins-development/admin-injection-zones#accessing-content-manager-data). +::: +
Example of a basic component using the 'useContentManagerContext' hook @@ -636,6 +660,10 @@ export default { ``` +:::strapi Reading and dispatching state +For information on reading and dispatching to the Redux store from plugin components, see [Accessing the Redux store](/cms/plugins-development/admin-redux-store). +::: + ### Hooks API The Hooks API allows a plugin to create and register hooks, i.e. places in the application where plugins can add personalized behavior. @@ -784,4 +812,4 @@ interface LayoutSettings extends Contracts.ContentTypes.Settings { :::note `EditViewLayout` and `ListViewLayout` are parts of the `useDocumentLayout` hook (see ). -::: +::: \ No newline at end of file From 3592385eabda20603d5a444de3ab621edffc4f29 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:24:56 +0100 Subject: [PATCH 08/64] Fix URL in content-manager-apis.md --- docusaurus/docs/cms/plugins-development/content-manager-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/plugins-development/content-manager-apis.md b/docusaurus/docs/cms/plugins-development/content-manager-apis.md index b5d551399d..8d3b72b9a7 100644 --- a/docusaurus/docs/cms/plugins-development/content-manager-apis.md +++ b/docusaurus/docs/cms/plugins-development/content-manager-apis.md @@ -15,7 +15,7 @@ tags: Content Manager APIs add panels and actions to list or edit views through `addEditViewSidePanel`, `addDocumentAction`, `addDocumentHeaderAction`, or `addBulkAction`. Each API accepts component functions with typed contexts, enabling precise control over document-aware UI injections. -Content-Manager APIs are part of the [Admin Panel API](/cms/plugins-development/admin-panel-api). They are a way to add content or options from plugins to the [Content-Manager](/cms/features/content-manager), so you can extend the Content-Manager by adding functionality from your own plugin, just like you can do it with [Injection Zones](/cms/plugins-development/admin-panel-api#injection-zones-api). +Content-Manager APIs are part of the [Admin Panel API](/cms/plugins-development/admin-panel-api). They are a way to add content or options from plugins to the [Content-Manager](/cms/features/content-manager), so you can extend the Content-Manager by adding functionality from your own plugin, just like you can do it with [Injection Zones](/cms/plugins-development/admin-injection-zones). Strapi 5 provides 4 Content-Manager APIs, all accessible through `app.getPlugin('content-manager').apis`: From a3b1f53bb75dec17387a2bfc2c85a95f799391fb Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:25:06 +0100 Subject: [PATCH 09/64] Re-organize sidebar --- docusaurus/sidebars.js | 378 ++++------------------------------------- 1 file changed, 35 insertions(+), 343 deletions(-) diff --git a/docusaurus/sidebars.js b/docusaurus/sidebars.js index 805a4da8f8..dec8d8292f 100644 --- a/docusaurus/sidebars.js +++ b/docusaurus/sidebars.js @@ -487,7 +487,7 @@ const sidebars = { { // Plugins type: 'category', - label: 'Plugins', + label: 'Plugins development', className: 'category-cms-plugins', collapsible: false, collapsed: false, @@ -497,30 +497,54 @@ const sidebars = { id: 'cms/plugins/installing-plugins-via-marketplace', label: 'Marketplace', }, + { + type: 'doc', + label: 'Developing plugins', + id: 'cms/plugins-development/developing-plugins', + }, { type: 'category', - label: 'Plugins development', - collapsed: true, + label: 'Basics', + collapsed: false, items: [ - { - type: 'doc', - label: 'Developing plugins', - id: 'cms/plugins-development/developing-plugins', - }, 'cms/plugins-development/create-a-plugin', - 'cms/plugins-development/plugin-structure', 'cms/plugins-development/plugin-sdk', - 'cms/plugins-development/admin-configuration-customization', + ], + }, + { + type: 'category', + label: 'Admin Panel', + collapsed: false, + items: [ 'cms/plugins-development/admin-panel-api', + 'cms/plugins-development/admin-configuration-customization', + 'cms/plugins-development/admin-localization', + 'cms/plugins-development/admin-injection-zones', + 'cms/plugins-development/admin-redux-store', 'cms/plugins-development/content-manager-apis', + ], + }, + { + type: 'category', + label: 'Server', + collapsed: false, + items: [ 'cms/plugins-development/server-api', 'cms/plugins-development/plugins-extension', + ], + }, + { + type: 'category', + label: 'Guides', + collapsed: false, + items: [ 'cms/plugins-development/guides/pass-data-from-server-to-admin', 'cms/plugins-development/guides/admin-permissions-for-plugins', 'cms/plugins-development/guides/store-and-access-data', 'cms/plugins-development/guides/create-components-for-plugins', ], }, + 'cms/plugins-development/plugins-extension', ], }, @@ -703,338 +727,6 @@ const sidebars = { ], }, ], - // devDocsRestApiSidebar: [ - // { - // type: 'link', - // label: '⬅️ Back to Dev Docs content', - // href: '/cms/intro' - // }, - // { - // type: 'category', - // collapsed: false, - // label: 'REST API reference', - // link: { - // type: 'doc', - // id: 'cms/api/rest' - // }, - // items: [ - // { - // type: 'category', - // label: 'Endpoints and basic requests', - // link: {type: 'doc', id: 'cms/api/rest'}, - // collapsed: false, - // items: [ - // { - // type: 'link', - // label: 'Endpoints', - // href: '/cms/api/rest#endpoints', - // }, - // { - // type: 'link', - // label: 'Get documents', - // href: '/cms/api/rest#get-all' - // }, - // { - // type: 'link', - // label: 'Get a document', - // href: '/cms/api/rest#get' - // }, - // { - // type: 'link', - // label: 'Create a document', - // href: '/cms/api/rest#create' - // }, - // { - // type: 'link', - // label: 'Update a document', - // href: '/cms/api/rest#update' - // }, - // { - // type: 'link', - // label: 'Delete a document', - // href: '/cms/api/rest#delete' - // }, - // ] - // }, - // { - // type: 'doc', - // id: 'cms/api/rest/interactive-query-builder', - // label: '✨ Interactive Query Builder' - // }, - // { - // type: 'doc', - // id: 'cms/api/rest/parameters' - // }, - // { - // type: 'category', - // label: 'Populate and Select', - // link: {type: 'doc', id: 'cms/api/rest/populate-select'}, - // collapsed: false, - // items: [ - // { - // type: 'link', - // label: 'Field selection', - // href: '/cms/api/rest/populate-select#field-selection', - // }, - // { - // type: 'link', - // label: 'Population', - // href: '/cms/api/rest/populate-select#population', - // }, - // ] - // }, - // { - // type: 'category', - // collapsed: false, - // label: 'Filters, Locale, Publication State', - // link: {type: 'doc', id: 'cms/api/rest/filters-locale-publication' }, - // items: [ - // { - // type: 'link', - // label: 'Filtering', - // href: '/cms/api/rest/filters' - // }, - // { - // type: 'link', - // label: 'Complex filtering', - // href: '/cms/api/rest/filters-locale-publication#complex-filtering', - // }, - // { - // type: 'link', - // label: 'Deep filtering', - // href: '/cms/api/rest/filters-locale-publication#deep-filtering', - // }, - // { - // type: 'link', - // label: 'Locale', - // href: '/cms/api/rest/locale', - // }, - // { - // type: 'link', - // label: 'Status', - // href: '/cms/api/rest/status', - // }, - // ], - // }, - // { - // type: 'category', - // collapsed: false, - // label: 'Sort and Pagination', - // link: { type: 'doc', id: 'cms/api/rest/sort-pagination'}, - // items: [ - // { - // type: 'link', - // label: 'Sorting', - // href: '/cms/api/rest/sort-pagination#sorting' - // }, - // { - // type: 'link', - // label: 'Pagination', - // href: '/cms/api/rest/sort-pagination#pagination' - // }, - // { - // type: 'link', - // label: 'Pagination by page', - // href: '/cms/api/rest/sort-pagination#pagination-by-page' - // }, - // { - // type: 'link', - // label: 'Pagination by offset', - // href: '/cms/api/rest/sort-pagination#pagination-by-offset' - // }, - // ] - // }, - // { - // type: 'category', - // collapsed: false, - // label: 'Relations', - // link: {type: 'doc', id: 'cms/api/rest/relations'}, - // items: [ - // { - // type: 'link', - // label: 'connect', - // href: '/cms/api/rest/relations#connect' - // }, - // { - // type: 'link', - // label: 'disconnect', - // href: '/cms/api/rest/relations#disconnect' - // }, - // { - // type: 'link', - // label: 'set', - // href: '/cms/api/rest/relations#set' - // }, - // ] - // }, - // ] - // }, - // { - // type: "category", - // label: "Rest API guides", - // collapsed: false, - // link: { - // type: 'doc', - // id: 'cms/api/rest/guides/intro', - // }, - // items: [ - // { - // type: "doc", - // label: "Understanding populate", - // id: 'cms/api/rest/guides/understanding-populate', - // }, - // { - // type: "doc", - // label: "How to populate creator fields", - // id: 'cms/api/rest/guides/populate-creator-fields', - // }, - // { - // type: 'link', - // label: 'Additional resources', - // href: '/cms/api/rest/guides/intro#additional-resources' - // }, - // ], - // } - // ], - // devDocsConfigSidebar: [ - // { - // type: 'link', - // label: '⬅️ Back to Dev Docs content', - // href: '/cms/intro' - // }, - // { - // type: 'category', - // collapsed: false, - // label: 'Configuration', - // link: { - // type: 'doc', - // id: 'cms/configurations', - // }, - // items: [ - // { - // type: 'doc', - // label: 'Introduction to configurations', - // id: 'cms/configurations', - // }, - // { - // type: 'category', - // collapsed: false, - // label: 'Base configurations', - // link: { - // type: 'doc', - // id: 'cms/configurations' - // }, - // items: [ - // 'cms/configurations/database', - // 'cms/configurations/server', - // 'cms/configurations/admin-panel', - // 'cms/configurations/middlewares', - // 'cms/configurations/api', - // ] - // }, - // { - // type: 'category', - // label: 'Additional configurations', - // collapsed: false, - // link: { - // type: 'doc', - // id: 'cms/configurations' - // }, - // items: [ - // 'cms/configurations/plugins', - // 'cms/configurations/typescript', - // 'cms/configurations/api-tokens', - // 'cms/configurations/functions', - // 'cms/configurations/cron', - // 'cms/configurations/environment', - // 'cms/configurations/sso', - // 'cms/configurations/features', - // ] - // }, - // { - // type: 'category', - // label: 'Guides', - // collapsed: false, - // link: { - // type: 'doc', - // id: 'cms/configurations' - // }, - // items: [ - // 'cms/configurations/guides/rbac', - // 'cms/configurations/guides/public-assets', - // 'cms/configurations/guides/access-cast-environment-variables', - // 'cms/configurations/guides/access-configuration-values', - // 'cms/configurations/guides/use-cron-jobs', - // ] - // } - // ] - // }, - // ], - // devDocsMigrationV5Sidebar: [ - // { - // type: 'link', - // label: '⬅️ Back to Dev Docs content', - // href: '/cms/intro' - // }, - // { - // type: 'category', - // collapsed: false, - // link: { - // type: 'doc', - // id: 'cms/migration/v4-to-v5/introduction-and-faq' - // }, - // label: 'Upgrade to Strapi 5', - // customProps: { - // new: true, - // }, - // items: [ - // { - // type: "doc", - // label: "Introduction and FAQ", - // id: "cms/migration/v4-to-v5/introduction-and-faq" - // }, - // { - // type: "doc", - // label: "Step-by-step guide", - // id: "cms/migration/v4-to-v5/step-by-step" - // }, - // { - // type: "doc", - // label: "Upgrade tool reference", - // id: 'cms/upgrade-tool', - // }, - // { - // type: "category", - // collapsible: true, - // collapsed: true, - // label: "Breaking changes", - // link: { - // type: 'doc', - // id: 'cms/migration/v4-to-v5/breaking-changes' - // }, - // items: [ - // { - // type: "autogenerated", - // dirName: 'cms/migration/v4-to-v5/breaking-changes' - // }, - // ] - // }, - // { - // type: 'category', - // label: 'Specific resources', - // collapsed: false, - // link: { type: 'doc', id: 'cms/migration/v4-to-v5/additional-resources/introduction' }, - // items: [ - // 'cms/migration/v4-to-v5/additional-resources/introduction', - // 'cms/migration/v4-to-v5/additional-resources/from-entity-service-to-document-service', - // 'cms/migration/v4-to-v5/additional-resources/plugins-migration', - // 'cms/migration/v4-to-v5/additional-resources/helper-plugin', - // ] - // } - // ] - // }, - - // ] }; -module.exports = sidebars; +module.exports = sidebars; \ No newline at end of file From b5f90963bc7e2c67a322aea78d57874a39cd8d94 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:35:41 +0100 Subject: [PATCH 10/64] Rework the Admin Panel API page to simplify it and act as a hub --- .../plugins-development/admin-panel-api.md | 793 ++++-------------- 1 file changed, 140 insertions(+), 653 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-panel-api.md b/docusaurus/docs/cms/plugins-development/admin-panel-api.md index 47cb61850c..bacc69323a 100644 --- a/docusaurus/docs/cms/plugins-development/admin-panel-api.md +++ b/docusaurus/docs/cms/plugins-development/admin-panel-api.md @@ -1,47 +1,34 @@ --- -title: Admin Panel API +title: Admin Panel API reference pagination_prev: cms/plugins-development/plugin-sdk pagination_next: cms/plugins-development/admin-configuration-customization toc_max_heading_level: 4 tags: - admin panel - plugin APIs -- asynchronous function -- bootstrap function -- hooks API -- Injection Zones API - lifecycle function -- menu -- settings - plugins - plugins development -- register function -- reducers API -- redux --- -# Admin Panel API for plugins +# Admin Panel API for plugins: An overview The Admin Panel API exposes `register`, `bootstrap`, and `registerTrads` hooks to inject React components and translations into Strapi's UI. Menu, settings, injection zone, reducer, and hook APIs let plugins add navigation, configuration panels, or custom actions. -A Strapi plugin can interact with both the [back end](/cms/plugins-development/server-api) and the front end of a Strapi application. The Admin Panel API is about the front end part, i.e. it allows a plugin to customize Strapi's [admin panel](/cms/intro). +A Strapi plugin can interact with both the back end and the front end of a Strapi application. The Admin Panel API is about the front end part, i.e. it allows a plugin to customize Strapi's [admin panel](/cms/intro). -The admin panel is a application that can embed other React applications. These other React applications are the admin parts of each Strapi plugin. +For more information about the back end part, see [Server API](/cms/plugins-development/server-api). -:::prerequisites -You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin). -::: +## General considerations -The Admin Panel API includes: +The admin panel of Strapi is a application that can embed other React applications. These other React applications are the admin parts of each Strapi feature or plugin. -- an [entry file](#entry-file) which exports the required interface, -- [lifecycle functions](#lifecycle-functions) and the `registerTrad()` [async function](#async-function), -- and several [specific APIs](#available-actions) for your plugin to interact with the admin panel. +To customize the admin panel of Strapi, you can use plugins and tap into the Admin Panel API. This consists in editing an [entry file](#entry-file) to export all the required interface, and choosing which [actions](#available-actions) you want to perform. -:::note -The whole code for the admin panel part of your plugin could live in the `/strapi-admin.js|ts` or `/admin/src/index.js|ts` file. However, it's recommended to split the code into different folders, just like the [structure](/cms/plugins-development/plugin-structure) created by the `strapi generate plugin` CLI generator command. +:::prerequisites +Before diving deeper into the concepts on this page, please ensure you [created a Strapi plugin](/cms/plugins-development/create-a-plugin). ::: ## Entry file @@ -50,14 +37,14 @@ The entry file for the Admin Panel API is `[plugin-name]/admin/src/index.js`. Th | Function type | Available functions | | ------------------- | ------------------------------------------------------------------------ | -| Lifecycle functions |
  • [register](#register)
  • [bootstrap](#bootstrap)
| -| Async function | [registerTrads](#registertrads) | +| Lifecycle functions |
  • [`register()`](#register)
  • [`bootstrap()`](#bootstrap)
| +| Async function | `registerTrads()` (see [admin localization](/cms/plugins-development/admin-localization) for details) | -## Lifecycle functions - -
+:::note +The whole code for the admin panel part of your plugin could live in the `/strapi-admin.js|ts` or `/admin/src/index.js|ts` file. However, it's recommended to split the code into different folders, just like the [structure](/cms/plugins-development/plugin-structure) created by the `strapi generate plugin` CLI generator command. +::: -### register() +### register() {#register} **Type:** `Function` @@ -65,32 +52,17 @@ This function is called to load the plugin, even before the app is actually [boo Within the register function, a plugin can: -* [register itself](#registerplugin) so it's available to the admin panel -* add a new link to the main navigation (see [Menu API](#menu-api)) -* [create a new settings section](#createsettingsection) -* define [injection zones](#injection-zones-api) -* [add reducers](#reducers-api) - -#### registerPlugin() - -**Type:** `Function` - -Registers the plugin to make it available in the admin panel. - -This function returns an object with the following parameters: - -| Parameter | Type | Description | -| ---------------- | ------------------------ | -------------------------------------------------------------------------------------------------- | -| `id` | String | Plugin id | -| `name` | String | Plugin name | -| `injectionZones` | Object | Declaration of available [injection zones](#injection-zones-api) | - -:::note -Some parameters can be imported from the `package.json` file. -::: +* [register itself](/cms/plugins-development/admin-configuration-customization#base-configuration) so it's available to the admin panel +* add a new link to the main navigation (see [Admin configuration & customization](/cms/plugins-development/admin-configuration-customization#navigation-sidebar-menu-links)) +* [create a new settings section](/cms/plugins-development/admin-configuration-customization#creating-a-new-settings-section) +* define [injection zones](/cms/plugins-development/admin-injection-zones) +* [add reducers](/cms/plugins-development/admin-redux-store#adding-custom-reducers) **Example:** + + + ```js title="my-plugin/admin/src/index.js" // Auto-generated component @@ -121,11 +93,42 @@ export default { }; ``` -:::strapi Additional configuration options -For details on `apis`, `initializer`, and `isReady`, see [Admin configuration & customization](/cms/plugins-development/admin-configuration-customization#base-configuration). -::: + + + +```ts title="my-plugin/admin/src/index.ts" +import PluginIcon from './components/PluginIcon'; +import pluginId from './pluginId'; +import type { StrapiApp } from '@strapi/admin/strapi-admin'; -### bootstrap() +export default { + register(app: StrapiApp) { + app.addMenuLink({ + to: `/plugins/${pluginId}`, + icon: PluginIcon, + intlLabel: { + id: `${pluginId}.plugin.name`, + defaultMessage: 'My plugin', + }, + Component: async () => { + const component = await import(/* webpackChunkName: "my-plugin" */ './pages/App'); + + return component; + }, + permissions: [], // array of permissions (object), allow a user to access a plugin depending on its permissions + }); + app.registerPlugin({ + id: pluginId, + name, + }); + }, +}; +``` + + + + +### bootstrap() {#bootstrap} **Type**: `Function` @@ -134,13 +137,16 @@ Exposes the bootstrap function, executed after all the plugins are [registered]( Within the bootstrap function, a plugin can, for instance: * extend another plugin, using `getPlugin('plugin-name')`, -* register hooks (see [Hooks API](#hooks-api)), -* [add links to a settings section](#settings-api), +* register hooks (see [Hooks](/cms/plugins-development/admin-hooks)), +* [add links to a settings section](/cms/plugins-development/admin-configuration-customization#adding-links-to-existing-settings-sections), * add actions and options to the Content Manager's List view and Edit view (see details on the [Content Manager APIs page](/cms/plugins-development/content-manager-apis)). **Example:** -```js + + + +```js title="my-plugin/admin/src/index.js" module.exports = () => { return { // ... @@ -152,16 +158,32 @@ module.exports = () => { }; ``` -## Async function + + -While [`register()`](#register) and [`bootstrap()`](#bootstrap) are lifecycle functions, `registerTrads()` is an async function. +```ts title="my-plugin/admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + // ... + bootstrap(app: StrapiApp) { + // execute some bootstrap code + app.getPlugin('content-manager').injectComponent('editView', 'right-links', { name: 'my-compo', Component: () => 'my-compo' }) + }, +}; +``` -### registerTrads() + + + + - -| View | Injection zone name & Location | -| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| List view | `actions`: sits between Filters and the cogs icon -| Edit view | `right-links`: sits between "Configure the view" and "Edit" buttons | - -#### Creating a custom injection zone - -To create a custom injection zone, declare it as a `` React component with an `area` prop that takes a string with the following naming convention: `plugin-name.viewName.injectionZoneName`. - -#### Injecting components - -A plugin has 2 different ways of injecting a component: - -* to inject a component from a plugin into another plugin's injection zones, use the `injectComponent()` function -* to specifically inject a component into one of the Content Manager's [predefined injection zones](#using-predefined-injection-zones), use the `getPlugin('content-manager').injectComponent()` function instead - -Both the `injectComponent()` and `getPlugin('content-manager').injectComponent()` methods accept the following arguments: - -| Argument | Type | Description | -| --------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| first argument | String | The view where the component is injected -| second argument | String | The zone where the component is injected -| third argument | Object | An object with the following keys:
  • `name` (string): the name of the component
  • `Component` (function or class): the React component to be injected
| - -
-Example: Inject a component in the informations box of the Edit View of the Content Manager: - -```jsx title="my-plugin/admin/src/index.js" - -export default { - bootstrap(app) { - app.getPlugin('content-manager').injectComponent('editView', 'informations', { - name: 'my-plugin-my-compo', - Component: () => 'my-compo', - }); - } -} -``` - -
- -
-Example: Creating a new injection zone and injecting it from a plugin to another one: - -```jsx title="my-plugin/admin/src/injectionZones.js" -// Use the injection zone in a view - -import { InjectionZone } from '@strapi/helper-plugin'; - -const HomePage = () => { - return ( -
-

This is the homepage

- -
- ); -}; -``` - -```jsx title="my-plugin/admin/src/index.js" -// Declare this injection zone in the register lifecycle of the plugin - -export default { - register() { - app.registerPlugin({ - // ... - injectionZones: { - homePage: { - right: [] - } - } - }); - }, -} -``` - -```jsx title="my-other-plugin/admin/src/index.js" -// Inject the component from a plugin in another plugin -export default { - register() { - // ... + return Promise.resolve(importedTrads); }, - bootstrap(app) { - app.getPlugin('my-plugin').injectComponent('homePage', 'right', { - name: 'my-other-plugin-component', - Component: () => 'This component is injected', - }); - } -}; -``` - -
- -#### Accessing data with the `useContentManagerContext` React hook - -Once an injection zone is defined, the component to be injected in the Content Manager can have access to all the data of the Edit View through the `useContentManagerContext` React hook. - -:::strapi Full hook reference -For a detailed guide on using this hook with all available properties, see [Admin injection zones — Accessing Content Manager data](/cms/plugins-development/admin-injection-zones#accessing-content-manager-data). -::: - -
-Example of a basic component using the 'useContentManagerContext' hook - -```js -import { - unstable_useContentManagerContext as useContentManagerContext, -} from '@strapi/strapi/admin'; - -const MyCompo = () => { - const { - slug, - isCreatingEntry, - isSingleType, - hasDraftAndPublish, - layout, - components, - contentType, - form, - model, - collectionType, - id, - } = useContentManagerContext(); - - // Form state and handlers - const { - initialValues, - values, - onChange, - } = form; - - /** - * Layout structure: - * - * `layout` is grouped by Content Manager views. - * - * - layout.edit.layout → edit view layout definition - * - layout.edit.components → component layouts used in the edit view - * - layout.list.layout → list view layout definition - */ - const { - edit: { layout: editLayout, components: editComponents }, - list: { layout: listLayout }, - } = layout; - - return null; }; - -export default MyCompo; ``` -
- -### Reducers API - -Reducers are reducers that can be used to share state between components. Reducers can be useful when: - -* Large amounts of application state are needed in many places in the application. -* The application state is updated frequently. -* The logic to update that state may be complex. - -Reducers can be added to a plugin interface with the `addReducers()` function during the [`register`](#register) lifecycle. + + -A reducer is declared as an object with this syntax: +
--> -**Example:** +## Available actions -```js title="my-plugin/admin/src/index.js" -import { exampleReducer } from './reducers' +The Admin Panel API allows a plugin to take advantage of several small APIs to perform actions that will modify the user interface, user experience, or behavior of the admin panel. -const reducers = { - // Reducer Syntax - [`${pluginId}_exampleReducer`]: exampleReducer -} +Use the following table as a reference to know which API and function to use, and where to declare them, and click on any function name to learn more: -export default { - register(app) { - app.addReducers(reducers) - }, - bootstrap() {}, -}; +| Action | Function to use | Related lifecycle function | +| ---------------------------------------- | ------------------------------------------------- | --------------------------- | +| Add a new link to the main navigation | [`addMenuLink()`](/cms/plugins-development/admin-configuration-customization#navigation-sidebar-menu-links) | [`register()`](#register) | +| Create a new settings section | [`createSettingSection()`](/cms/plugins-development/admin-configuration-customization#creating-a-new-settings-section) | [`register()`](#register) | +| Declare an injection zone | [`registerPlugin()`](/cms/plugins-development/admin-configuration-customization#base-configuration) | [`register()`](#register) | +| Add a reducer | [`addReducers()`](/cms/plugins-development/admin-redux-store#adding-custom-reducers) | [`register()`](#register) | +| Create a hook | [`createHook()`](/cms/plugins-development/admin-hooks) | [`register()`](#register) | +| Add a single link to a settings section | [`addSettingsLink()`](/cms/plugins-development/admin-configuration-customization#adding-links-to-existing-settings-sections) | [`bootstrap()`](#bootstrap) | +| Add multiple links to a settings section | [`addSettingsLinks()`](/cms/plugins-development/admin-configuration-customization#adding-links-to-existing-settings-sections) | [`bootstrap()`](#bootstrap) | +| Inject a Component in an injection zone | [`injectComponent()`](/cms/plugins-development/admin-injection-zones) | [`bootstrap()`](#bootstrap) | +| Add options and actions to the Content Manager's Edit view and List view |
  • `addEditViewSidePanel()`
  • `addDocumentAction`
  • `addDocumentHeaderAction`
  • `addBulkAction`
| [`bootstrap()`](#bootstrap) | +| Register a hook | [`registerHook()`](/cms/plugins-development/admin-hooks) | [`bootstrap()`](#bootstrap) | +
+Click on any of the following cards to get more details about a specific topic: -``` + + + + + + + + -:::strapi Reading and dispatching state -For information on reading and dispatching to the Redux store from plugin components, see [Accessing the Redux store](/cms/plugins-development/admin-redux-store). +:::tip Replacing the WYSIWYG +The WYSIWYG editor can be replaced by taking advantage of [custom fields](/cms/features/custom-fields), for instance using the . ::: -### Hooks API - -The Hooks API allows a plugin to create and register hooks, i.e. places in the application where plugins can add personalized behavior. - -Hooks should be registered during the [bootstrap](#bootstrap) lifecycle of a plugin. - -Hooks can then be run in series, in waterfall or in parallel: - -* `runHookSeries` returns an array corresponding to the result of each function executed, ordered -* `runHookParallel` returns an array corresponding to the result of the promise resolved by the function executed, ordered -* `runHookWaterfall` returns a single value corresponding to all the transformations applied by the different functions starting with the initial value `args`. - -
-Example: Create a hook in a plugin and use it in another plugin - -```jsx title="my-plugin/admin/src/index.js" -// Create a hook in a plugin -export default { - register(app) { - app.createHook('My-PLUGIN/MY_HOOK'); - } -} - -``` - -```jsx title="my-other-plugin/admin/src/index.js" -// Use the hook in another plugin -export default { - bootstrap(app) { - app.registerHook('My-PLUGIN/MY_HOOK', (...args) => { - console.log(args) - - // important: return the mutated data - return args - }); - - app.registerPlugin({...}) - } -} -``` - -
- -#### Predefined hooks - -Strapi includes a predefined `Admin/CM/pages/ListView/inject-column-in-table` hook that can be used to add or mutate a column of the List View of the [Content Manager](/cms/intro): - -```jsx -runHookWaterfall(INJECT_COLUMN_IN_TABLE, { - displayedHeaders: ListFieldLayout[], - layout: ListFieldLayout, -}); -``` - -```tsx -interface ListFieldLayout { - /** - * The attribute data from the content-type's schema for the field - */ - attribute: Attribute.Any | { type: 'custom' }; - /** - * Typically used by plugins to render a custom cell - */ - cellFormatter?: ( - data: Document, - header: Omit, - { collectionType, model }: { collectionType: string; model: string } - ) => React.ReactNode; - label: string | MessageDescriptor; - /** - * the name of the attribute we use to display the actual name e.g. relations - * are just ids, so we use the mainField to display something meaninginful by - * looking at the target's schema - */ - mainField?: string; - name: string; - searchable?: boolean; - sortable?: boolean; -} - -interface ListLayout { - layout: ListFieldLayout[]; - components?: never; - metadatas: { - [K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['list']; - }; - options: LayoutOptions; - settings: LayoutSettings; -} - -type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object; - -interface LayoutSettings extends Contracts.ContentTypes.Settings { - displayName?: string; - icon?: never; -} -``` - -Strapi also includes a `Admin/CM/pages/EditView/mutate-edit-view-layout` hook that can be used to mutate the Edit View of the [Content Manager](/cms/intro): - -```tsx -interface EditLayout { - layout: Array>; - components: { - [uid: string]: { - layout: Array; - settings: Contracts.Components.ComponentConfiguration['settings'] & { - displayName?: string; - icon?: string; - }; - }; - }; - metadatas: { - [K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['edit']; - }; - options: LayoutOptions; - settings: LayoutSettings; -} - -interface EditFieldSharedProps extends Omit { - hint?: string; - mainField?: string; - size: number; - unique?: boolean; - visible?: boolean; -} - -/** - * Map over all the types in Attribute Types and use that to create a union of new types where the attribute type - * is under the property attribute and the type is under the property type. - */ -type EditFieldLayout = { - [K in Attribute.Kind]: EditFieldSharedProps & { - attribute: Extract; - type: K; - }; -}[Attribute.Kind]; - -type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object; - -interface LayoutSettings extends Contracts.ContentTypes.Settings { - displayName?: string; - icon?: never; -} -``` +:::info +The admin panel supports dotenv variables. -:::note -`EditViewLayout` and `ListViewLayout` are parts of the `useDocumentLayout` hook (see ). +All variables defined in a `.env` file and prefixed by `STRAPI_ADMIN_` are available while customizing the admin panel through `process.env`. ::: \ No newline at end of file From 509be54d6aef1a7cd0227edec0eae9daa8d62836 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:30:48 +0100 Subject: [PATCH 11/64] Add missing cards to guides in developing-plugins.md --- docusaurus/docs/cms/plugins-development/developing-plugins.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docusaurus/docs/cms/plugins-development/developing-plugins.md b/docusaurus/docs/cms/plugins-development/developing-plugins.md index ebfef00f3d..b2dcfc6e21 100644 --- a/docusaurus/docs/cms/plugins-development/developing-plugins.md +++ b/docusaurus/docs/cms/plugins-development/developing-plugins.md @@ -47,6 +47,8 @@ Plugins can also be used to add [custom fields](/cms/features/custom-fields) to + +
From 408b84c657c79964a9a49394ee004545f1572fd0 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:31:10 +0100 Subject: [PATCH 12/64] Rework again Admin Panel API --- .../plugins-development/admin-panel-api.md | 95 ++++++++++++++++--- 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-panel-api.md b/docusaurus/docs/cms/plugins-development/admin-panel-api.md index bacc69323a..bdf97a3a2f 100644 --- a/docusaurus/docs/cms/plugins-development/admin-panel-api.md +++ b/docusaurus/docs/cms/plugins-development/admin-panel-api.md @@ -1,7 +1,7 @@ --- -title: Admin Panel API reference +title: Admin Panel API overview pagination_prev: cms/plugins-development/plugin-sdk -pagination_next: cms/plugins-development/admin-configuration-customization +pagination_next: cms/plugins-development/admin-navigation-settings toc_max_heading_level: 4 tags: - admin panel @@ -52,9 +52,9 @@ This function is called to load the plugin, even before the app is actually [boo Within the register function, a plugin can: -* [register itself](/cms/plugins-development/admin-configuration-customization#base-configuration) so it's available to the admin panel -* add a new link to the main navigation (see [Admin configuration & customization](/cms/plugins-development/admin-configuration-customization#navigation-sidebar-menu-links)) -* [create a new settings section](/cms/plugins-development/admin-configuration-customization#creating-a-new-settings-section) +* register itself through the [`registerPlugin`](#registerplugin) function so the plugin is available in the admin panel +* add a new link to the main navigation (see [Admin navigation & settings](/cms/plugins-development/admin-navigation-settings#navigation-sidebar-menu-links)) +* [create a new settings section](/cms/plugins-development/admin-navigation-settings#creating-a-new-settings-section) * define [injection zones](/cms/plugins-development/admin-injection-zones) * [add reducers](/cms/plugins-development/admin-redux-store#adding-custom-reducers) @@ -64,7 +64,6 @@ Within the register function, a plugin can: ```js title="my-plugin/admin/src/index.js" - // Auto-generated component import PluginIcon from './components/PluginIcon'; import pluginId from './pluginId' @@ -128,6 +127,76 @@ export default {
+#### registerPlugin() {#registerplugin} + +**Type:** `Function` + +Registers the plugin to make it available in the admin panel. This function is called within the [`register()`](#register) lifecycle function and returns an object with the following parameters: + +| Parameter | Type | Description | +| ---------------- | ------------------------ | -------------------------------------------------------------------------------------------------- | +| `id` | String | Plugin id | +| `name` | String | Plugin name | +| `apis` | `Record` | APIs exposed to other plugins | +| `initializer` | `React.ComponentType` | Component for plugin initialization | +| `injectionZones` | Object | Declaration of available [injection zones](/cms/plugins-development/admin-injection-zones) | +| `isReady` | Boolean | Plugin readiness status (default: `true`) | + +:::note +Some parameters can be imported from the `package.json` file. +::: + +**Example:** + + + + +```js title="my-plugin/admin/src/index.js" +export default { + register(app) { + app.registerPlugin({ + id: 'my-plugin', + name: 'My Plugin', + apis: { + // APIs exposed to other plugins + }, + initializer: MyInitializerComponent, + injectionZones: { + // Areas where other plugins can inject components + }, + isReady: false, + }); + }, +}; +``` + + + + +```ts title="my-plugin/admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + register(app: StrapiApp) { + app.registerPlugin({ + id: 'my-plugin', + name: 'My Plugin', + apis: { + // APIs exposed to other plugins + }, + initializer: MyInitializerComponent, + injectionZones: { + // Areas where other plugins can inject components + }, + isReady: false, + }); + }, +}; +``` + + + + ### bootstrap() {#bootstrap} **Type**: `Function` @@ -138,7 +207,7 @@ Within the bootstrap function, a plugin can, for instance: * extend another plugin, using `getPlugin('plugin-name')`, * register hooks (see [Hooks](/cms/plugins-development/admin-hooks)), -* [add links to a settings section](/cms/plugins-development/admin-configuration-customization#adding-links-to-existing-settings-sections), +* [add links to a settings section](/cms/plugins-development/admin-navigation-settings#adding-links-to-existing-settings-sections), * add actions and options to the Content Manager's List view and Edit view (see details on the [Content Manager APIs page](/cms/plugins-development/content-manager-apis)). **Example:** @@ -268,13 +337,13 @@ Use the following table as a reference to know which API and function to use, an | Action | Function to use | Related lifecycle function | | ---------------------------------------- | ------------------------------------------------- | --------------------------- | -| Add a new link to the main navigation | [`addMenuLink()`](/cms/plugins-development/admin-configuration-customization#navigation-sidebar-menu-links) | [`register()`](#register) | -| Create a new settings section | [`createSettingSection()`](/cms/plugins-development/admin-configuration-customization#creating-a-new-settings-section) | [`register()`](#register) | -| Declare an injection zone | [`registerPlugin()`](/cms/plugins-development/admin-configuration-customization#base-configuration) | [`register()`](#register) | +| Add a new link to the main navigation | [`addMenuLink()`](/cms/plugins-development/admin-navigation-settings#navigation-sidebar-menu-links) | [`register()`](#register) | +| Create a new settings section | [`createSettingSection()`](/cms/plugins-development/admin-navigation-settings#creating-a-new-settings-section) | [`register()`](#register) | +| Declare an injection zone | [`registerPlugin()`](#registerplugin) | [`register()`](#register) | | Add a reducer | [`addReducers()`](/cms/plugins-development/admin-redux-store#adding-custom-reducers) | [`register()`](#register) | | Create a hook | [`createHook()`](/cms/plugins-development/admin-hooks) | [`register()`](#register) | -| Add a single link to a settings section | [`addSettingsLink()`](/cms/plugins-development/admin-configuration-customization#adding-links-to-existing-settings-sections) | [`bootstrap()`](#bootstrap) | -| Add multiple links to a settings section | [`addSettingsLinks()`](/cms/plugins-development/admin-configuration-customization#adding-links-to-existing-settings-sections) | [`bootstrap()`](#bootstrap) | +| Add a single link to a settings section | [`addSettingsLink()`](/cms/plugins-development/admin-navigation-settings#adding-links-to-existing-settings-sections) | [`bootstrap()`](#bootstrap) | +| Add multiple links to a settings section | [`addSettingsLinks()`](/cms/plugins-development/admin-navigation-settings#adding-links-to-existing-settings-sections) | [`bootstrap()`](#bootstrap) | | Inject a Component in an injection zone | [`injectComponent()`](/cms/plugins-development/admin-injection-zones) | [`bootstrap()`](#bootstrap) | | Add options and actions to the Content Manager's Edit view and List view |
  • `addEditViewSidePanel()`
  • `addDocumentAction`
  • `addDocumentHeaderAction`
  • `addBulkAction`
| [`bootstrap()`](#bootstrap) | | Register a hook | [`registerHook()`](/cms/plugins-development/admin-hooks) | [`bootstrap()`](#bootstrap) | @@ -283,7 +352,7 @@ Use the following table as a reference to know which API and function to use, an Click on any of the following cards to get more details about a specific topic: - + From f6fc0774a9963ae0bef549e0815b247db246ea94 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:31:32 +0100 Subject: [PATCH 13/64] Add admin localization page --- .../cms/plugins-development/admin-localization.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-localization.md b/docusaurus/docs/cms/plugins-development/admin-localization.md index 52e774742b..911d2c773b 100644 --- a/docusaurus/docs/cms/plugins-development/admin-localization.md +++ b/docusaurus/docs/cms/plugins-development/admin-localization.md @@ -1,7 +1,7 @@ --- title: Admin localization description: Provide translations for your Strapi plugin's admin panel interface using registerTrads and react-intl. -pagination_prev: cms/plugins-development/admin-configuration-customization +pagination_prev: cms/plugins-development/admin-navigation-settings pagination_next: cms/plugins-development/admin-injection-zones displayed_sidebar: cmsSidebar toc_max_heading_level: 4 @@ -14,7 +14,9 @@ tags: - plugins development --- -# Admin localization +import Prerequisite from '/docs/snippets/plugins-development-create-plugin-prerequisite-admin-panel.md' + +# Admin Panel API: Localization @@ -24,9 +26,7 @@ Register translation files with `registerTrads`, prefix keys with your plugin ID Plugins can provide translations for multiple languages to make the admin interface accessible to users worldwide. Strapi automatically loads and merges plugin translations with core translations, making them available throughout the admin panel. -:::prerequisites -You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin) and are familiar with the [admin entry file lifecycle](/cms/plugins-development/admin-configuration-customization#overview). -::: + ## Translation file structure @@ -405,7 +405,7 @@ export default { }; ``` -See [Customizing menu and settings](/cms/plugins-development/admin-configuration-customization#customizing-menu-and-settings) for more configuration examples. +See [Admin navigation & settings](/cms/plugins-development/admin-navigation-settings) for more configuration examples. ## How Strapi integrates plugin translations From b8a346e708abb86f6ef1192201f44bef98ba153c Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:31:52 +0100 Subject: [PATCH 14/64] Add reusable snippets for all plugins pages prerequisites --- ...ins-development-create-plugin-prerequisite-admin-panel.md | 5 +++++ .../plugins-development-create-plugin-prerequisite-server.md | 5 +++++ .../plugins-development-create-plugin-prerequisite.md | 3 +++ 3 files changed, 13 insertions(+) create mode 100644 docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite-admin-panel.md create mode 100644 docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite-server.md create mode 100644 docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite.md diff --git a/docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite-admin-panel.md b/docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite-admin-panel.md new file mode 100644 index 0000000000..49ae25c336 --- /dev/null +++ b/docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite-admin-panel.md @@ -0,0 +1,5 @@ +:::prerequisites +Before diving deeper into the concepts on this page, please ensure you have: +- [created a Strapi plugin](/cms/plugins-development/create-a-plugin), +- read and understood the basics of the [Admin Panel API](/cms/plugins-development/admin-panel-api) +::: \ No newline at end of file diff --git a/docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite-server.md b/docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite-server.md new file mode 100644 index 0000000000..0ea77c60ca --- /dev/null +++ b/docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite-server.md @@ -0,0 +1,5 @@ +:::prerequisites +Before diving deeper into the concepts on this page, please ensure you have: +- [created a Strapi plugin](/cms/plugins-development/create-a-plugin), +- read and understood the basics of the [Server API](/cms/plugins-development/server-api) +::: \ No newline at end of file diff --git a/docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite.md b/docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite.md new file mode 100644 index 0000000000..eeede95d09 --- /dev/null +++ b/docusaurus/docs/snippets/plugins-development-create-plugin-prerequisite.md @@ -0,0 +1,3 @@ +:::prerequisites +Before diving deeper into the concepts on this page, please ensure you [created a Strapi plugin](/cms/plugins-development/create-a-plugin). +::: \ No newline at end of file From 8f03597662e3ce0cc88bc84744a3f994cb601d51 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:32:48 +0100 Subject: [PATCH 15/64] Delete old "admin config & custom" page replaced by admin-navigation-settings.md --- .../admin-configuration-customization.md | 482 ------------------ 1 file changed, 482 deletions(-) delete mode 100644 docusaurus/docs/cms/plugins-development/admin-configuration-customization.md diff --git a/docusaurus/docs/cms/plugins-development/admin-configuration-customization.md b/docusaurus/docs/cms/plugins-development/admin-configuration-customization.md deleted file mode 100644 index 343f2a660e..0000000000 --- a/docusaurus/docs/cms/plugins-development/admin-configuration-customization.md +++ /dev/null @@ -1,482 +0,0 @@ ---- -title: Admin configuration & customization -description: Configure and customize Strapi plugins in the admin panel with registerPlugin, menu links, and settings sections. -pagination_prev: cms/plugins-development/admin-panel-api -pagination_next: cms/plugins-development/admin-localization -displayed_sidebar: cmsSidebar -toc_max_heading_level: 4 -tags: - - admin panel - - plugin configuration - - plugin customization - - registerPlugin - - plugin lifecycle - - plugins development ---- - -# Admin configuration & customization - - - -The admin entry file exports `register`, `bootstrap`, and `registerTrads`. Use `registerPlugin` for base setup, `addMenuLink` and `createSettingSection` for navigation. For localization, injection zones, and Redux store access, see the dedicated sub-pages. - - - -This section covers configuration and customization options for Strapi plugins in the admin panel. Learn how to configure plugins, manage initialization, and customize the admin interface. - -:::prerequisites -You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin). -::: - -## Overview - -The admin panel entry file (`admin/src/index.ts`) exports an object with lifecycle functions that control how your plugin integrates with Strapi's admin interface. The entry file can export 3 functions: - -| Function | Type | Description | -|---|---|---| -| `register` | Required | Called to load the plugin before the app is bootstrapped. Use it to register the plugin, add menu links, create settings sections, define [injection zones](/cms/plugins-development/admin-injection-zones), and [add reducers](/cms/plugins-development/admin-panel-api#reducers-api). | -| `bootstrap` | Optional | Executed after all plugins are registered. Use it to extend other plugins, [register hooks](/cms/plugins-development/admin-panel-api#hooks-api), add settings links, inject components into injection zones, and add [Content Manager actions](/cms/plugins-development/content-manager-apis). | -| `registerTrads` | Required | Async function to register translation files for all locales. See [Admin localization](/cms/plugins-development/admin-localization). | - -## Base configuration - -The `registerPlugin` method is the core function for registering a plugin in the admin panel. Call this method within the `register` lifecycle function of your plugin's entry file (`admin/src/index.ts`). It configures the main options to integrate the plugin with Strapi's admin interface. - - - - -```js title="admin/src/index.js" -export default { - register(app) { - app.registerPlugin({ - id: 'my-plugin', - name: 'My Plugin', - apis: { - // APIs exposed to other plugins - }, - initializer: MyInitializerComponent, - injectionZones: { - // Areas where other plugins can inject components - }, - isReady: false, // Plugin readiness status - }); - }, -}; -``` - - - - -```ts title="admin/src/index.ts" -import type { StrapiApp } from '@strapi/admin/strapi-admin'; - -export default { - register(app: StrapiApp) { - app.registerPlugin({ - id: 'my-plugin', - name: 'My Plugin', - apis: { - // APIs exposed to other plugins - }, - initializer: MyInitializerComponent, - injectionZones: { - // Areas where other plugins can inject components - }, - isReady: false, // Plugin readiness status - }); - }, -}; -``` - - - - -### Configuration options - -| Parameter | Type | Required | Description | -|---|---|---|---| -| `id` | `string` | ✅ | Unique plugin identifier | -| `name` | `string` | ✅ | Plugin display name | -| `apis` | `Record` | ❌ | APIs exposed to other plugins | -| `initializer` | `React.ComponentType` | ❌ | Component for plugin initialization | -| `injectionZones` | `object` | ❌ | Available injection zones (see [Injection zones](/cms/plugins-development/admin-injection-zones)) | -| `isReady` | `boolean` | ❌ | Plugin readiness status (default: `true`) | - -## Customizing menu and settings - -Plugins can customize the admin panel's navigation sidebar and settings pages to provide access to their features. - -### 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 - - - - -```jsx title="admin/src/index.js" -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: async () => { - const { App } = await import('./pages/App'); - return 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', - }); - }, -}; -``` - - - - -```ts title="admin/src/index.ts" -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: async () => { - const { App } = await import('./pages/App'); - return 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', - }); - }, -}; -``` - - - - -#### Menu link parameters - -| Parameter | Type | Required | Description | -|---|---|---|---| -| `to` | `string` | ✅ | Path the link should point to (relative to the admin panel root) | -| `icon` | `React.Component` | ✅ | React component for the icon to display in the navigation | -| `intlLabel` | `object` | ✅ | Localized label object with `id` (translation key) and `defaultMessage` (fallback label) | -| `Component` | `async function` | ✅ | Async function that returns a dynamic import of the plugin's main page component | -| `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | -| `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`) | - -:::note -The `intlLabel.id` values should correspond to keys in your translation files located at `admin/src/translations/[locale].json`. See [Admin localization](/cms/plugins-development/admin-localization) for details. -::: - -### 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 - -Use `createSettingSection()` in the `register` lifecycle function: - - - - -```jsx title="admin/src/index.js" -export default { - register(app) { - app.createSettingSection( - { - 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: async () => { - const { GeneralSettings } = - await import('./pages/Settings/General'); - return GeneralSettings; - }, - }, - { - intlLabel: { - id: 'my-plugin.settings.advanced', - defaultMessage: 'Advanced', - }, - id: 'advanced', - to: 'my-plugin/advanced', - Component: async () => { - const { AdvancedSettings } = - await import('./pages/Settings/Advanced'); - return AdvancedSettings; - }, - }, - ], - ); - - app.registerPlugin({ - id: 'my-plugin', - name: 'My Plugin', - }); - }, -}; -``` - - - - -```ts title="admin/src/index.ts" -import type { StrapiApp } from '@strapi/admin/strapi-admin'; - -export default { - register(app: StrapiApp) { - app.createSettingSection( - { - 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: async () => { - const { GeneralSettings } = - await import('./pages/Settings/General'); - return GeneralSettings; - }, - }, - { - intlLabel: { - id: 'my-plugin.settings.advanced', - defaultMessage: 'Advanced', - }, - id: 'advanced', - to: 'my-plugin/advanced', - Component: async () => { - const { AdvancedSettings } = - await import('./pages/Settings/Advanced'); - return AdvancedSettings; - }, - }, - ], - ); - - app.registerPlugin({ - id: 'my-plugin', - name: 'My Plugin', - }); - }, -}; -``` - - - - -#### `createSettingSection` parameters - -**First argument** (`sectionConfig`): - -| Parameter | Type | Required | Description | -|---|---|---|---| -| `id` | `string` | ✅ | Unique identifier for the settings section | -| `intlLabel` | `object` | ✅ | Localized label object with `id` (translation key) and `defaultMessage` (fallback label) | - -**Second argument** (`links`), an array of link objects: - -| Parameter | Type | Required | Description | -|---|---|---|---| -| `id` | `string` | ✅ | Unique identifier for the settings link | -| `to` | `string` | ✅ | Path relative to the settings route (do not include `settings/` prefix) | -| `intlLabel` | `object` | ✅ | Localized label object with `id` and `defaultMessage` | -| `Component` | `async function` | ✅ | Async function that returns a dynamic import of the settings page component | -| `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | -| `licenseOnly` | `boolean` | ❌ | If `true`, displays a ⚡ icon (default: `false`) | - -#### Adding links to existing settings sections - -To add a single link to an existing settings section, use `addSettingsLink()` in the `bootstrap` lifecycle function. To add multiple links at once, use `addSettingsLinks()`. - - - - -```jsx title="admin/src/index.js" -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: async () => { - const { DocumentationPage } = - await import('./pages/Settings/Documentation'); - return DocumentationPage; - }, - permissions: [], - licenseOnly: false, - }); - - // Add multiple links at once to the global settings section - app.addSettingsLinks('global', [ - { - intlLabel: { - id: 'my-plugin.settings.general', - defaultMessage: 'General', - }, - id: 'general', - to: 'my-plugin/general', - Component: async () => { - const { GeneralSettings } = - await import('./pages/Settings/General'); - return GeneralSettings; - }, - }, - { - intlLabel: { - id: 'my-plugin.settings.advanced', - defaultMessage: 'Advanced', - }, - id: 'advanced', - to: 'my-plugin/advanced', - Component: async () => { - const { AdvancedSettings } = - await import('./pages/Settings/Advanced'); - return AdvancedSettings; - }, - }, - ]); - }, -}; -``` - - - - -```ts title="admin/src/index.ts" -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: async () => { - const { DocumentationPage } = - await import('./pages/Settings/Documentation'); - return DocumentationPage; - }, - permissions: [], - licenseOnly: false, - }); - - // Add multiple links at once to the global settings section - app.addSettingsLinks('global', [ - { - intlLabel: { - id: 'my-plugin.settings.general', - defaultMessage: 'General', - }, - id: 'general', - to: 'my-plugin/general', - Component: async () => { - const { GeneralSettings } = - await import('./pages/Settings/General'); - return GeneralSettings; - }, - }, - { - intlLabel: { - id: 'my-plugin.settings.advanced', - defaultMessage: 'Advanced', - }, - id: 'advanced', - to: 'my-plugin/advanced', - Component: async () => { - const { AdvancedSettings } = - await import('./pages/Settings/Advanced'); - return AdvancedSettings; - }, - }, - ]); - }, -}; -``` - - - - -#### `addSettingsLink` and `addSettingsLinks` parameters - -Both functions take a `sectionId` string as the first argument (e.g., `'global'` or `'permissions'`). The second argument is a single link object for `addSettingsLink` or an array of link objects for `addSettingsLinks`, using the same properties as the `links` array in `createSettingSection` (see table above). - -#### Available settings sections - -Strapi provides built-in settings sections that plugins can extend: - -- `global`: General application settings -- `permissions`: Administration panel settings - -:::note -Creating a new settings section must be done in the `register` lifecycle function. Adding links to existing settings sections must be done in the `bootstrap` lifecycle function. The `to` path for settings links should be relative to the settings route (e.g., `'my-plugin/general'` not `'settings/my-plugin/general'`). -::: \ No newline at end of file From 678ff0eb61eeffbe401ddc8f301d7f47deb0df33 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:33:24 +0100 Subject: [PATCH 16/64] Add admin-navigation-settings.md page --- .../admin-navigation-settings.md | 406 ++++++++++++++++++ 1 file changed, 406 insertions(+) create mode 100644 docusaurus/docs/cms/plugins-development/admin-navigation-settings.md diff --git a/docusaurus/docs/cms/plugins-development/admin-navigation-settings.md b/docusaurus/docs/cms/plugins-development/admin-navigation-settings.md new file mode 100644 index 0000000000..7c4e41d3cb --- /dev/null +++ b/docusaurus/docs/cms/plugins-development/admin-navigation-settings.md @@ -0,0 +1,406 @@ +--- +title: Admin navigation & settings +description: Add menu links, create settings sections, and configure settings links for your Strapi plugin in the admin panel. +pagination_prev: cms/plugins-development/admin-panel-api +pagination_next: cms/plugins-development/admin-localization +displayed_sidebar: cmsSidebar +toc_max_heading_level: 4 +tags: + - admin panel + - menu links + - settings + - plugins development +--- + +import Prerequisite from '/docs/snippets/plugins-development-create-plugin-prerequisite-admin-panel.md' + +# Admin Panel API: Navigation & settings + + + +Use `addMenuLink` in `register` to add sidebar links, `createSettingSection` in `register` to create settings groups, and `addSettingsLink`/`addSettingsLinks` in `bootstrap` to extend existing settings sections. + + + +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`](/cms/plugins-development/admin-panel-api#register) or [`bootstrap`](/cms/plugins-development/admin-panel-api#bootstrap) lifecycle functions of your plugin's entry file. + + + +## 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) | +| `icon` | `React.Component` | ✅ | React component for the icon to display in the navigation | +| `intlLabel` | `object` | ✅ | Label for the link, following the convention, with:
  • `id`: id used to insert the localized label
  • `defaultMessage`: default label for the link
| +| `Component` | `async function` | ✅ | Async function that returns a dynamic import of the plugin's main page component | +| `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | +| `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`) | + +:::note +The `intlLabel.id` values should correspond to keys in your translation files located at `admin/src/translations/[locale].json`. See [Admin localization](/cms/plugins-development/admin-localization) for details. +::: + + + + +```jsx title="admin/src/index.js" +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: async () => { + const { App } = await import('./pages/App'); + return 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', + }); + }, +}; +``` + + + + +```ts title="admin/src/index.ts" +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: async () => { + const { App } = await import('./pages/App'); + return 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', + }); + }, +}; +``` + + + + +## 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 + +Use `createSettingSection()` in the `register` lifecycle function: + + + + +```jsx title="admin/src/index.js" +export default { + register(app) { + app.createSettingSection( + { + 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: async () => { + const { GeneralSettings } = + await import('./pages/Settings/General'); + return GeneralSettings; + }, + }, + { + intlLabel: { + id: 'my-plugin.settings.advanced', + defaultMessage: 'Advanced', + }, + id: 'advanced', + to: 'my-plugin/advanced', + Component: async () => { + const { AdvancedSettings } = + await import('./pages/Settings/Advanced'); + return AdvancedSettings; + }, + }, + ], + ); + + app.registerPlugin({ + id: 'my-plugin', + name: 'My Plugin', + }); + }, +}; +``` + + + + +```ts title="admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + register(app: StrapiApp) { + app.createSettingSection( + { + 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: async () => { + const { GeneralSettings } = + await import('./pages/Settings/General'); + return GeneralSettings; + }, + }, + { + intlLabel: { + id: 'my-plugin.settings.advanced', + defaultMessage: 'Advanced', + }, + id: 'advanced', + to: 'my-plugin/advanced', + Component: async () => { + const { AdvancedSettings } = + await import('./pages/Settings/Advanced'); + return AdvancedSettings; + }, + }, + ], + ); + + app.registerPlugin({ + id: 'my-plugin', + name: 'My Plugin', + }); + }, +}; +``` + + + + +The `createSettingSection()` function accepts the following parameters: + +* the first argument is the section configuration: + + | Parameter | Type | Required | Description | + |---|---|---|---| + | `id` | `string` | ✅ | Unique identifier for the settings section | + | `intlLabel` | `object` | ✅ | Localized label for the section, following the convention, with:
  • `id`: id used to insert the localized label
  • `defaultMessage`: default label for the section
| + +* the second argument is an array of link objects; each link object contains the following: + + | Parameter | Type | Required | Description | + |---|---|---|---| + | `id` | `string` | ✅ | Unique identifier for the settings link | + | `to` | `string` | ✅ | Path relative to the settings route (do not include `settings/` prefix) | + | `intlLabel` | `object` | ✅ | Localized label object with `id` and `defaultMessage` | + | `Component` | `async function` | ✅ | Async function that returns a dynamic import of the settings page component | + | `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | + | `licenseOnly` | `boolean` | ❌ | If `true`, displays a ⚡ icon (default: `false`) | + +### Adding links to existing settings sections + +To add a single link to an existing settings section, use `addSettingsLink()` in the `bootstrap()` lifecycle function. To add multiple links at once, use `addSettingsLinks()`. + + + + +```jsx title="admin/src/index.js" +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: async () => { + const { DocumentationPage } = + await import('./pages/Settings/Documentation'); + return DocumentationPage; + }, + permissions: [], + licenseOnly: false, + }); + + // Add multiple links at once to the global settings section + app.addSettingsLinks('global', [ + { + intlLabel: { + id: 'my-plugin.settings.general', + defaultMessage: 'General', + }, + id: 'general', + to: 'my-plugin/general', + Component: async () => { + const { GeneralSettings } = + await import('./pages/Settings/General'); + return GeneralSettings; + }, + }, + { + intlLabel: { + id: 'my-plugin.settings.advanced', + defaultMessage: 'Advanced', + }, + id: 'advanced', + to: 'my-plugin/advanced', + Component: async () => { + const { AdvancedSettings } = + await import('./pages/Settings/Advanced'); + return AdvancedSettings; + }, + }, + ]); + }, +}; +``` + + + + +```ts title="admin/src/index.ts" +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: async () => { + const { DocumentationPage } = + await import('./pages/Settings/Documentation'); + return DocumentationPage; + }, + permissions: [], + licenseOnly: false, + }); + + // Add multiple links at once to the global settings section + app.addSettingsLinks('global', [ + { + intlLabel: { + id: 'my-plugin.settings.general', + defaultMessage: 'General', + }, + id: 'general', + to: 'my-plugin/general', + Component: async () => { + const { GeneralSettings } = + await import('./pages/Settings/General'); + return GeneralSettings; + }, + }, + { + intlLabel: { + id: 'my-plugin.settings.advanced', + defaultMessage: 'Advanced', + }, + id: 'advanced', + to: 'my-plugin/advanced', + Component: async () => { + const { AdvancedSettings } = + await import('./pages/Settings/Advanced'); + return AdvancedSettings; + }, + }, + ]); + }, +}; +``` + + + + +`addSettingsLink` and `addSettingsLinks` both take a `sectionId` string as the first argument (e.g., `'global'` or `'permissions'`). The second argument is a single link object for `addSettingsLink` or an array of link objects for `addSettingsLinks`, using the same properties as the `links` array in [`createSettingSection()` (see table above)](#creating-a-new-settings-section). + +### Available settings sections + +Strapi provides built-in settings sections that plugins can extend: + +- `global`: General application settings +- `permissions`: Administration panel settings + +:::note +Creating a new settings section must be done in the `register` lifecycle function. Adding links to existing settings sections must be done in the `bootstrap` lifecycle function. The `to` path for settings links should be relative to the settings route (e.g., `'my-plugin/general'` not `'settings/my-plugin/general'`). +::: \ No newline at end of file From d080ad4ee7943c2588fb077bc5ebfb115b2ab73c Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:34:12 +0100 Subject: [PATCH 17/64] Rename some page titles + improve intros --- docusaurus/docs/cms/api/content-api.md | 2 +- docusaurus/docs/cms/plugins-development/plugin-sdk.md | 2 +- docusaurus/docs/cms/plugins-development/server-api.md | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docusaurus/docs/cms/api/content-api.md b/docusaurus/docs/cms/api/content-api.md index 600b430e62..68f477aa86 100644 --- a/docusaurus/docs/cms/api/content-api.md +++ b/docusaurus/docs/cms/api/content-api.md @@ -2,7 +2,7 @@ title: Content API description: Learn more about Strapi 5's Content API displayed_sidebar: cmsSidebar -sidebar_label: APIs Introduction +sidebar_label: Content APIs Introduction pagination_prev: cms/setup-deployment pagination_next: cms/api/document tags: diff --git a/docusaurus/docs/cms/plugins-development/plugin-sdk.md b/docusaurus/docs/cms/plugins-development/plugin-sdk.md index 9b8ea97e1c..68ddd1f17d 100644 --- a/docusaurus/docs/cms/plugins-development/plugin-sdk.md +++ b/docusaurus/docs/cms/plugins-development/plugin-sdk.md @@ -1,5 +1,5 @@ --- -title: Plugin SDK +title: Plugin SDK reference description: Reference documentation for Strapi's Plugin SDK commands displayed_sidebar: cmsSidebar tags: diff --git a/docusaurus/docs/cms/plugins-development/server-api.md b/docusaurus/docs/cms/plugins-development/server-api.md index df6a24bcb6..69bb00a9b4 100644 --- a/docusaurus/docs/cms/plugins-development/server-api.md +++ b/docusaurus/docs/cms/plugins-development/server-api.md @@ -1,6 +1,6 @@ --- title: Server API for plugins -sidebar_label: Server API +sidebar_label: Server API reference displayed_sidebar: cmsSidebar description: Strapi's Server API for plugins allows a Strapi plugin to customize the back end part (i.e. the server) of your application. tags: @@ -21,7 +21,9 @@ tags: # Server API for plugins -A Strapi plugin can interact with both the back end and the [front end](/cms/plugins-development/admin-panel-api) of a Strapi application. The Server API is about the back-end part, i.e. how the plugin interacts with the server part of a Strapi application. +A Strapi plugin can interact with both the back end and the of a Strapi application. The Server API is about the back-end part, i.e. how the plugin interacts with the server part of a Strapi application. + +For more information about the front end part, see [front end](/cms/plugins-development/admin-panel-api). :::prerequisites You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin). From 6383e2888cddf31bb9848b06850d5c3d17aaa687 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:34:27 +0100 Subject: [PATCH 18/64] Optimize sidebar --- docusaurus/sidebars.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docusaurus/sidebars.js b/docusaurus/sidebars.js index dec8d8292f..d4363f4d59 100644 --- a/docusaurus/sidebars.js +++ b/docusaurus/sidebars.js @@ -162,7 +162,7 @@ const sidebars = { { // APIs type: 'category', - label: 'APIs', + label: 'Content APIs', className: 'category-cms-api', link: { type: 'doc', id: 'cms/api/content-api' }, collapsible: false, @@ -505,7 +505,7 @@ const sidebars = { { type: 'category', label: 'Basics', - collapsed: false, + collapsed: true, items: [ 'cms/plugins-development/create-a-plugin', 'cms/plugins-development/plugin-sdk', @@ -514,20 +514,21 @@ const sidebars = { { type: 'category', label: 'Admin Panel', - collapsed: false, + collapsed: true, items: [ 'cms/plugins-development/admin-panel-api', - 'cms/plugins-development/admin-configuration-customization', + 'cms/plugins-development/admin-navigation-settings', 'cms/plugins-development/admin-localization', 'cms/plugins-development/admin-injection-zones', 'cms/plugins-development/admin-redux-store', + 'cms/plugins-development/admin-hooks', 'cms/plugins-development/content-manager-apis', ], }, { type: 'category', label: 'Server', - collapsed: false, + collapsed: true, items: [ 'cms/plugins-development/server-api', 'cms/plugins-development/plugins-extension', @@ -536,7 +537,7 @@ const sidebars = { { type: 'category', label: 'Guides', - collapsed: false, + collapsed: true, items: [ 'cms/plugins-development/guides/pass-data-from-server-to-admin', 'cms/plugins-development/guides/admin-permissions-for-plugins', From f542c856ee039e8584ef3b336447fac3c0d191ac Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:43:47 +0100 Subject: [PATCH 19/64] Fix parallel naming for headings in admin-localization --- .../docs/cms/plugins-development/admin-localization.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-localization.md b/docusaurus/docs/cms/plugins-development/admin-localization.md index 911d2c773b..f231def9fa 100644 --- a/docusaurus/docs/cms/plugins-development/admin-localization.md +++ b/docusaurus/docs/cms/plugins-development/admin-localization.md @@ -237,7 +237,7 @@ The `registerTrads` function should gracefully handle missing translation files This allows plugins to provide translations for only some locales (e.g., only English) without breaking the admin panel for other locales. -## Using translations in components +## Translations in components To use translations in your React components, use the `useIntl` hook from `react-intl`: @@ -382,7 +382,7 @@ const HomePage = () => { -## Using translations in configuration +## Translations in configuration Translation keys are also used when configuring menu links, settings sections, and other admin panel elements: @@ -407,7 +407,7 @@ export default { See [Admin navigation & settings](/cms/plugins-development/admin-navigation-settings) for more configuration examples. -## How Strapi integrates plugin translations +## Plugin translation lifecycle Strapi's admin panel automatically: From 822f0798aef317b66d581dcb6c6f50f4c04067af Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:57:00 +0100 Subject: [PATCH 20/64] Add admin-localization page --- .../plugins-development/admin-localization.md | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-localization.md b/docusaurus/docs/cms/plugins-development/admin-localization.md index f231def9fa..3cc7bfcb2e 100644 --- a/docusaurus/docs/cms/plugins-development/admin-localization.md +++ b/docusaurus/docs/cms/plugins-development/admin-localization.md @@ -151,7 +151,7 @@ The `registerTrads` function receives an object with the following property: The function must return a `Promise` that resolves to an array of translation objects. Each object has the following structure: -```typescript +```ts { data: Record; // Translation key-value pairs locale: string; // Locale code (e.g., 'en', 'fr') @@ -222,7 +222,7 @@ After prefixing with plugin ID `my-plugin`, these become: - `my-plugin.plugin.name` - `my-plugin.settings.title` -### Handling missing translation files +### Missing translation files The `registerTrads` function should gracefully handle missing translation files by returning an empty object for that locale. The `.catch()` handler in the example above ensures that if a translation file does not exist, the plugin still returns a valid translation object: @@ -386,6 +386,9 @@ const HomePage = () => { Translation keys are also used when configuring menu links, settings sections, and other admin panel elements: + + + ```js title="admin/src/index.js" export default { register(app) { @@ -405,7 +408,34 @@ export default { }; ``` -See [Admin navigation & settings](/cms/plugins-development/admin-navigation-settings) for more configuration examples. + + + +```ts title="admin/src/index.ts" +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', // Prefixed translation key + defaultMessage: 'My Plugin', // Fallback if translation missing + }, + Component: async () => { + const { App } = await import('./pages/App'); + return App; + }, + }); + }, +}; +``` + + + + +See [Admin navigation & settings](/cms/plugins-development/admin-configuration-customization) for more configuration examples. ## Plugin translation lifecycle From 8b5e8041a3990ba724bc4564759ab42ab9b321b5 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:57:19 +0100 Subject: [PATCH 21/64] Use prerequisite snippet in Admin Panel API overview --- docusaurus/docs/cms/plugins-development/admin-panel-api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-panel-api.md b/docusaurus/docs/cms/plugins-development/admin-panel-api.md index bdf97a3a2f..331fd3a92d 100644 --- a/docusaurus/docs/cms/plugins-development/admin-panel-api.md +++ b/docusaurus/docs/cms/plugins-development/admin-panel-api.md @@ -11,6 +11,8 @@ tags: - plugins development --- +import Prerequisite from '/docs/snippets/plugins-development-create-plugin-prerequisite.md' + # Admin Panel API for plugins: An overview @@ -27,9 +29,7 @@ The admin panel of Strapi is a Date: Fri, 6 Mar 2026 12:07:29 +0100 Subject: [PATCH 26/64] Improve Admin Panel API overview --- .../cms/plugins-development/admin-panel-api.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-panel-api.md b/docusaurus/docs/cms/plugins-development/admin-panel-api.md index df7c36c5d9..ff7f197954 100644 --- a/docusaurus/docs/cms/plugins-development/admin-panel-api.md +++ b/docusaurus/docs/cms/plugins-development/admin-panel-api.md @@ -23,7 +23,7 @@ The Admin Panel API exposes `register`, `bootstrap`, and `registerTrads` hooks t A Strapi plugin can interact with both the back end and the front end of a Strapi application. The Admin Panel API is about the front end part, i.e. it allows a plugin to customize Strapi's [admin panel](/cms/intro). -For more information about the back end part, see [Server API](/cms/plugins-development/server-api). +For more information on how plugins can interact with the back end part of Strapi, see [Server API](/cms/plugins-development/server-api). ## General considerations @@ -341,25 +341,26 @@ Use the following table as a reference to know which API and function to use, an | ---------------------------------------- | ------------------------------------------------- | --------------------------- | | Add a new link to the main navigation | [`addMenuLink()`](/cms/plugins-development/admin-navigation-settings#navigation-sidebar-menu-links) | [`register()`](#register) | | Create a new settings section | [`createSettingSection()`](/cms/plugins-development/admin-navigation-settings#creating-a-new-settings-section) | [`register()`](#register) | +| Add a single link to a settings section | [`addSettingsLink()`](/cms/plugins-development/admin-navigation-settings#adding-links-to-existing-settings-sections) | [`bootstrap()`](#bootstrap) | +| Add multiple links to a settings section | [`addSettingsLinks()`](/cms/plugins-development/admin-navigation-settings#adding-links-to-existing-settings-sections) | [`bootstrap()`](#bootstrap) | +| Add panels, options, and actions to the Content Manager's Edit view and List view |
  • [`addEditViewSidePanel()`](/cms/plugins-development/content-manager-apis#addeditviewsidepanel)
  • [`addDocumentAction()`](/cms/plugins-development/content-manager-apis#adddocumentaction)
  • [`addDocumentHeaderAction()`](/cms/plugins-development/content-manager-apis#adddocumentheaderaction)
  • [`addBulkAction()`](/cms/plugins-development/content-manager-apis#addbulkaction)
| [`bootstrap()`](#bootstrap) | | Declare an injection zone | [`registerPlugin()`](#registerplugin) | [`register()`](#register) | +| Inject a component in an injection zone | [`injectComponent()`](/cms/plugins-development/admin-injection-zones) | [`bootstrap()`](#bootstrap) | | Add a reducer | [`addReducers()`](/cms/plugins-development/admin-redux-store#adding-custom-reducers) | [`register()`](#register) | | Create a hook | [`createHook()`](/cms/plugins-development/admin-hooks) | [`register()`](#register) | -| Add a single link to a settings section | [`addSettingsLink()`](/cms/plugins-development/admin-navigation-settings#adding-links-to-existing-settings-sections) | [`bootstrap()`](#bootstrap) | -| Add multiple links to a settings section | [`addSettingsLinks()`](/cms/plugins-development/admin-navigation-settings#adding-links-to-existing-settings-sections) | [`bootstrap()`](#bootstrap) | -| Inject a Component in an injection zone | [`injectComponent()`](/cms/plugins-development/admin-injection-zones) | [`bootstrap()`](#bootstrap) | -| Add options and actions to the Content Manager's Edit view and List view |
  • `addEditViewSidePanel()`
  • `addDocumentAction`
  • `addDocumentHeaderAction`
  • `addBulkAction`
| [`bootstrap()`](#bootstrap) | | Register a hook | [`registerHook()`](/cms/plugins-development/admin-hooks) | [`bootstrap()`](#bootstrap) | +| Provide translations for your plugin's admin interface | [`registerTrads()`](/cms/plugins-development/admin-localization#registertrads) | _(Handled in the async `registerTrads()` function itself)_ |
Click on any of the following cards to get more details about a specific topic: - + - + :::tip Replacing the WYSIWYG From 84b7d107f1df93afdc9dbdf24b8f8bc888fcd045 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:07:50 +0100 Subject: [PATCH 27/64] Improve Content Manager APIs --- .../content-manager-apis.md | 105 ++++++++++++++---- 1 file changed, 86 insertions(+), 19 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/content-manager-apis.md b/docusaurus/docs/cms/plugins-development/content-manager-apis.md index 8d3b72b9a7..a2eeb23981 100644 --- a/docusaurus/docs/cms/plugins-development/content-manager-apis.md +++ b/docusaurus/docs/cms/plugins-development/content-manager-apis.md @@ -1,6 +1,8 @@ --- title: Content Manager APIs description: The Content Manager APIs reference lists the APIs available to plugins for adding actions and options to the Content Manager List view and Edit view. +pagination_prev: cms/plugins-development/admin-hooks +pagination_next: cms/plugins-development/server-api displayed_sidebar: cmsSidebar toc_max_heading_level: 4 tags: @@ -9,15 +11,19 @@ tags: - plugins --- +import Prerequisite from '/docs/snippets/plugins-development-create-plugin-prerequisite-admin-panel.md' + # Content Manager APIs Content Manager APIs add panels and actions to list or edit views through `addEditViewSidePanel`, `addDocumentAction`, `addDocumentHeaderAction`, or `addBulkAction`. Each API accepts component functions with typed contexts, enabling precise control over document-aware UI injections. -Content-Manager APIs are part of the [Admin Panel API](/cms/plugins-development/admin-panel-api). They are a way to add content or options from plugins to the [Content-Manager](/cms/features/content-manager), so you can extend the Content-Manager by adding functionality from your own plugin, just like you can do it with [Injection Zones](/cms/plugins-development/admin-injection-zones). +Content Manager APIs are part of the [Admin Panel API](/cms/plugins-development/admin-panel-api). They are a way for Strapi plugins to add content or options to the [Content Manager](/cms/features/content-manager). The Content Manager APIs allow you to extend the Content Manager by adding functionality from your own plugin, just like you can do it with [Injection zones](/cms/plugins-development/admin-injection-zones). + + -Strapi 5 provides 4 Content-Manager APIs, all accessible through `app.getPlugin('content-manager').apis`: +Strapi 5 provides 4 Content Manager APIs, all accessible through `app.getPlugin('content-manager').apis`: - [`addEditViewSidePanel`](#addeditviewsidepanel), - [`addDocumentAction`](#adddocumentaction), @@ -30,33 +36,69 @@ All the Content Manager APIs share the same API shape and must use components. ### API shape -All Content Manager APIs works in the same way: to use them, call them on your plugin’s [bootstrap](/cms/plugins-development/admin-panel-api#bootstrap) function, in 2 possible ways. +All Content Manager APIs works in the same way: to use them, call them on your plugin's [`bootstrap()`](/cms/plugins-development/admin-panel-api#bootstrap) function, in 2 possible ways: -When using TypeScript, the `apis` property returned by `app.getPlugin()` is typed as `unknown`. Cast it to `ContentManagerPlugin['config']['apis']` before calling the APIs: +:::note +When using TypeScript, the `apis` property returned by `app.getPlugin()` is typed as `unknown`. Cast it to `ContentManagerPlugin['config']['apis']` before calling the APIs. +::: - Passing an array with what you want to add. For example, the following code would add the ReleasesPanel at the end of the current EditViewSidePanels: - ```tsx - import type { ContentManagerPlugin } from '@strapi/content-manager/strapi-admin'; + + + + ```js + const apis = app.getPlugin('content-manager').apis; + + apis.addEditViewSidePanel([ReleasesPanel]); + ``` + + + + + ```tsx + import type { ContentManagerPlugin } from '@strapi/content-manager/strapi-admin'; + + const apis = + app.getPlugin('content-manager').apis as ContentManagerPlugin['config']['apis']; - const apis = - app.getPlugin('content-manager').apis as ContentManagerPlugin['config']['apis']; + apis.addEditViewSidePanel([ReleasesPanel]); + ``` - apis.addEditViewSidePanel([ReleasesPanel]); - ``` + + - Passing a function that receives the current elements and return the new ones. This is useful if, for example, you want to add something in a specific position in the list, like in the following code: - ```tsx - const apis = - app.getPlugin('content-manager').apis as ContentManagerPlugin['config']['apis']; + + - apis.addEditViewSidePanel((panels) => [SuperImportantPanel, ...panels]); - ``` + ```js + const apis = app.getPlugin('content-manager').apis; + + apis.addEditViewSidePanel((panels) => [SuperImportantPanel, ...panels]); + ``` + + + + + ```tsx + const apis = + app.getPlugin('content-manager').apis as ContentManagerPlugin['config']['apis']; + + apis.addEditViewSidePanel((panels) => [SuperImportantPanel, ...panels]); + ``` + + + ### Components -You need to pass components to the API in order to add things to the Content Manager. These components are basically functions that receive some properties and return and object with some shape (depending on the function). Each component’s return object is different based on the function you’re using, but they receive similar properties, depending on whether you use a ListView or EditView API. These properties include important information about the document(s) you are viewing or editing. +You need to pass components to the API in order to add things to the Content Manager. + +Components are functions that receive some properties and return an object with some shape (depending on the function). Each component's return object is different based on the function you're using, but they receive similar properties, depending on whether you use a ListView or EditView API. + +Properties include important information about the document(s) you are viewing or editing. #### ListViewContext @@ -115,9 +157,31 @@ More information about types and APIs can be found in + -```jsx title="my-plugin/components/my-panel.ts" +```jsx title="my-plugin/components/my-panel.js" +const Panel = ({ + activeTab, + collectionType, + document, + documentId, + meta, + model +}) => { + return { + title: 'My Panel', + content:

I'm on {activeTab}

+ } +} +``` + +
+ + +```tsx title="my-plugin/components/my-panel.ts" import type { PanelComponent, PanelComponentProps } from '@strapi/content-manager/strapi-admin'; const Panel: PanelComponent = ({ @@ -135,6 +199,9 @@ const Panel: PanelComponent = ({ } ``` + + + ## Available APIs
@@ -312,4 +379,4 @@ interface BulkActionDescription { */ variant?: ButtonProps['variant']; } -``` +``` \ No newline at end of file From fd710944244cb099338dc117bcbd5cd495531c8d Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:08:05 +0100 Subject: [PATCH 28/64] Update sidebar to match order in Admin Panel overview --- docusaurus/sidebars.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/sidebars.js b/docusaurus/sidebars.js index d4363f4d59..cb59afb864 100644 --- a/docusaurus/sidebars.js +++ b/docusaurus/sidebars.js @@ -518,11 +518,11 @@ const sidebars = { items: [ 'cms/plugins-development/admin-panel-api', 'cms/plugins-development/admin-navigation-settings', - 'cms/plugins-development/admin-localization', + 'cms/plugins-development/content-manager-apis', 'cms/plugins-development/admin-injection-zones', 'cms/plugins-development/admin-redux-store', 'cms/plugins-development/admin-hooks', - 'cms/plugins-development/content-manager-apis', + 'cms/plugins-development/admin-localization', ], }, { From df0f04463f11ae1f1c4ab815190a5cb43975723a Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:08:28 +0100 Subject: [PATCH 29/64] Hide old tip from admin panel custom. section in sidebar --- docusaurus/sidebars.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docusaurus/sidebars.js b/docusaurus/sidebars.js index cb59afb864..bd139bc17b 100644 --- a/docusaurus/sidebars.js +++ b/docusaurus/sidebars.js @@ -394,23 +394,23 @@ const sidebars = { collapsed: true, customProps: { updated: false, - tooltip: 'This section has been reorganized, see details below.', + // tooltip: 'This section has been reorganized, see details below.', }, items: [ - { - type: 'html', - value: 'placeholder', // a value is required for the HTML type, but it is not rendered - customProps: { - tooltipTitle: `The section has been reorganized`, - tooltipContent: `We have reorganized the admin panel customization section to make it easier to navigate and find what you need. -

-
The new structure groups customizations by their purpose, making it more intuitive to locate specific settings.
-
-
Note: - Deployment-related configuration, including host, port, and path configuration, has been moved to the Configurations > Admin panel > Admin panel server page. -
`, - }, - }, + // { + // type: 'html', + // value: 'placeholder', // a value is required for the HTML type, but it is not rendered + // customProps: { + // tooltipTitle: `The section has been reorganized`, + // tooltipContent: `We have reorganized the admin panel customization section to make it easier to navigate and find what you need. + //

+ //
The new structure groups customizations by their purpose, making it more intuitive to locate specific settings.
+ //
+ //
Note: + // Deployment-related configuration, including host, port, and path configuration, has been moved to the Configurations > Admin panel > Admin panel server page. + //
`, + // }, + // }, { type: 'doc', id: 'cms/admin-panel-customization', From 0c168f4e8aacb7bc051332318a0aa92946a6d56e Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 13:34:09 +0100 Subject: [PATCH 30/64] Add missing loading property to a CM API example --- .../docs/cms/plugins-development/content-manager-apis.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/plugins-development/content-manager-apis.md b/docusaurus/docs/cms/plugins-development/content-manager-apis.md index a2eeb23981..297652e221 100644 --- a/docusaurus/docs/cms/plugins-development/content-manager-apis.md +++ b/docusaurus/docs/cms/plugins-development/content-manager-apis.md @@ -270,9 +270,10 @@ interface DocumentActionDescription { * @default 'secondary' */ variant?: ButtonProps['variant']; + loading?: ButtonProps['loading']; } -type DocumentActionPosition = 'panel' | 'header' | 'table-row'; +type DocumentActionPosition = 'panel' | 'header' | 'table-row' | 'preview' | 'relation-modal'; interface DialogOptions { type: 'dialog'; From a3cb906125ef4eeed44a04388ac14c7d06aa41ae Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 14:10:49 +0100 Subject: [PATCH 31/64] Improve further admin-navigation-settings with considerations about `to` and security caution and `permissions --- .../admin-navigation-settings.md | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-navigation-settings.md b/docusaurus/docs/cms/plugins-development/admin-navigation-settings.md index 7a0ba354a8..85ccafec12 100644 --- a/docusaurus/docs/cms/plugins-development/admin-navigation-settings.md +++ b/docusaurus/docs/cms/plugins-development/admin-navigation-settings.md @@ -42,7 +42,7 @@ 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) | +| `to` | `string` | ✅ | Path the link should point to (relative to the admin panel root) (see [additional information](#path-conventions-for-to)) | | `icon` | `React.Component` | ✅ | React component for the icon to display in the navigation | | `intlLabel` | `object` | ✅ | Label for the link, following the convention, with:
  • `id`: id used to insert the localized label
  • `defaultMessage`: default label for the link
| | `Component` | `async function` | ✅ | Async function that returns a dynamic import of the plugin's main page component | @@ -54,6 +54,10 @@ A menu link accepts the following parameters: The `intlLabel.id` values should correspond to keys in your translation files located at `admin/src/translations/[locale].json`. See [Admin localization](/cms/plugins-development/admin-localization) for details. ::: +:::caution +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](/cms/plugins-development/guides/admin-permissions-for-plugins) guide for a complete walkthrough. +::: + @@ -263,7 +267,7 @@ The `createSettingSection()` function accepts the following parameters: | Parameter | Type | Required | Description | |---|---|---|---| | `id` | `string` | ✅ | Unique identifier for the settings link | - | `to` | `string` | ✅ | Path relative to the settings route (do not include `settings/` prefix) | + | `to` | `string` | ✅ | Path relative to the settings route (do not include `settings/` prefix) (see [additional information](#path-conventions-for-to)) | | `intlLabel` | `object` | ✅ | Localized label object with `id` and `defaultMessage` | | `Component` | `async function` | ✅ | Async function that returns a dynamic import of the settings page component | | `permissions` | `Array` | ❌ | Array of permission objects that control access to the link | @@ -420,5 +424,21 @@ Strapi provides built-in settings sections that plugins can extend: - `permissions`: Administration panel settings :::note -Creating a new settings section must be done in the `register` lifecycle function. Adding links to existing settings sections must be done in the `bootstrap` lifecycle function. The `to` path for settings links should be relative to the settings route (e.g., `'my-plugin/general'` not `'settings/my-plugin/general'`). +Creating a new settings section must be done in the `register` lifecycle function. Adding links to existing settings sections must be done in the `bootstrap` lifecycle function. +::: + +### 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` | +| `createSettingSection` link | `my-plugin/general` | `http://localhost:1337/admin/settings/my-plugin/general` | +| `addSettingsLink` | `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. + +:::strapi Securing plugin routes +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](/cms/plugins-development/guides/admin-permissions-for-plugins) guide. ::: \ No newline at end of file From cb973e6f565e3809ddbf0e5eee84a5c2a0871eb3 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 14:58:00 +0100 Subject: [PATCH 32/64] Rework injection zones page --- .../admin-injection-zones.md | 146 +++++++++++++++--- ...injection-zones-vs-content-manager-apis.md | 54 +++++++ 2 files changed, 177 insertions(+), 23 deletions(-) create mode 100644 docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md diff --git a/docusaurus/docs/cms/plugins-development/admin-injection-zones.md b/docusaurus/docs/cms/plugins-development/admin-injection-zones.md index 60ff82eb98..4b7d0a99e1 100644 --- a/docusaurus/docs/cms/plugins-development/admin-injection-zones.md +++ b/docusaurus/docs/cms/plugins-development/admin-injection-zones.md @@ -1,7 +1,7 @@ --- title: Admin injection zones description: Extend and customize the Strapi admin panel by injecting React components into predefined or custom injection zones. -pagination_prev: cms/plugins-development/admin-localization +pagination_prev: cms/plugins-development/content-manager-apis pagination_next: cms/plugins-development/admin-redux-store displayed_sidebar: cmsSidebar toc_max_heading_level: 4 @@ -15,7 +15,10 @@ tags: - plugins development --- -# Admin injection zones +import Prerequisite from '/docs/snippets/plugins-development-create-plugin-prerequisite-admin-panel.md' +import InjectionVsCmApis from '/docs/snippets/injection-zones-vs-content-manager-apis.md' + +# Admin Panel API: Injection zones @@ -25,13 +28,17 @@ Injection zones are predefined areas in the admin UI where plugins can inject Re 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. -:::prerequisites -You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin) and are familiar with the [admin entry file lifecycle](/cms/plugins-development/admin-configuration-customization#overview). +:::note +Injection zones are defined in the [`register`](/cms/plugins-development/admin-panel-api#register) lifecycle function, but components are injected in the [`bootstrap`](/cms/plugins-development/admin-panel-api#bootstrap) lifecycle function. +::: + +:::tip +For adding panels, actions, or buttons to the Content Manager, the [Content Manager APIs](/cms/plugins-development/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. ::: -## What are injection zones? + -Injection zones are predefined areas in a plugin's UI where other plugins can inject custom React components. They are defined in the `register` lifecycle function, but components are injected in the `bootstrap` lifecycle function. + ## Predefined injection zones @@ -40,9 +47,19 @@ Strapi's Content Manager provides predefined injection zones that plugins can us | 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. + +:::caution +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: @@ -53,6 +70,7 @@ To inject a component into a Content Manager injection zone, use `getPlugin('con ```jsx title="admin/src/index.js" import { MyCustomButton } from './components/MyCustomButton'; import { PreviewAction } from './components/PreviewAction'; +import { PublishModalInfo } from './components/PublishModalInfo'; export default { register(app) { @@ -63,24 +81,44 @@ export default { }, bootstrap(app) { // Inject a button into the Edit view's right-links zone + // highlight-start app .getPlugin('content-manager') .injectComponent('editView', 'right-links', { name: 'my-plugin-custom-button', Component: MyCustomButton, }); + // highlight-end // Inject a component into the List view's actions zone - app.getPlugin('content-manager').injectComponent('listView', 'actions', { - name: 'my-plugin-list-action', - Component: () => , + // highlight-start + app + .getPlugin('content-manager') + .injectComponent('listView', 'actions', { + name: 'my-plugin-list-action', + Component: () => , }); + // highlight-end + + // Inject additional information into the publish modal + // highlight-start + app + .getPlugin('content-manager') + .injectComponent('listView', 'publishModalAdditionalInfos', { + name: 'my-plugin-publish-modal-info', + Component: PublishModalInfo, + }); + // highlight-end // Inject a component into the Preview view's actions zone - app.getPlugin('content-manager').injectComponent('preview', 'actions', { - name: 'my-plugin-preview-action', - Component: PreviewAction, + // highlight-start + app + .getPlugin('content-manager') + .injectComponent('preview', 'actions', { + name: 'my-plugin-preview-action', + Component: PreviewAction, }); + // highlight-end }, }; ``` @@ -92,6 +130,7 @@ export default { 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) { @@ -102,24 +141,44 @@ export default { }, bootstrap(app: StrapiApp) { // Inject a button into the Edit view's right-links zone + // highlight-start app .getPlugin('content-manager') .injectComponent('editView', 'right-links', { name: 'my-plugin-custom-button', Component: MyCustomButton, }); + // highlight-end // Inject a component into the List view's actions zone - app.getPlugin('content-manager').injectComponent('listView', 'actions', { - name: 'my-plugin-list-action', - Component: () => , + app + // highlight-start + .getPlugin('content-manager') + .injectComponent('listView', 'actions', { + name: 'my-plugin-list-action', + Component: () => , }); + // highlight-end + + // Inject additional information into the publish modal + app + // highlight-start + .getPlugin('content-manager') + .injectComponent('listView', 'publishModalAdditionalInfos', { + name: 'my-plugin-publish-modal-info', + Component: PublishModalInfo, + }); + // highlight-end // Inject a component into the Preview view's actions zone - app.getPlugin('content-manager').injectComponent('preview', 'actions', { - name: 'my-plugin-preview-action', - Component: PreviewAction, + app + // highlight-start + .getPlugin('content-manager') + .injectComponent('preview', 'actions', { + name: 'my-plugin-preview-action', + Component: PreviewAction, }); + // highlight-end }, }; ``` @@ -127,7 +186,7 @@ export default { -## Creating custom injection zones +## 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: @@ -140,6 +199,7 @@ export default { app.registerPlugin({ id: 'dashboard', name: 'Dashboard', + // highlight-start injectionZones: { homePage: { top: [], @@ -152,6 +212,7 @@ export default { }, }, }); + // highlight-end }, }; ``` @@ -167,6 +228,7 @@ export default { app.registerPlugin({ id: 'dashboard', name: 'Dashboard', + // highlight-start injectionZones: { homePage: { top: [], @@ -178,6 +240,7 @@ export default { after: [], }, }, + // highlight-end }); }, }; @@ -186,10 +249,15 @@ export default { -### Using injection zones in components +### Rendering injection zones in components + + To render injected components in your plugin's UI, use the `` React component with an `area` prop following the naming convention `plugin-name.viewName.injectionZoneName`: + + + ```jsx title="admin/src/pages/Dashboard.jsx" import { InjectionZone } from '@strapi/helper-plugin'; @@ -212,6 +280,34 @@ const Dashboard = () => { export default Dashboard; ``` + + + +```tsx title="admin/src/pages/Dashboard.tsx" +import { InjectionZone } from '@strapi/helper-plugin'; + +const Dashboard = () => { + return ( +
+

Dashboard

+ + {/* Render components injected into the top zone */} + + +
{/* Main dashboard content */}
+ + {/* Render components injected into the bottom zone */} + +
+ ); +}; + +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: @@ -229,6 +325,7 @@ export default { name: 'Widget Plugin', }); }, + // highlight-start bootstrap(app) { const dashboardPlugin = app.getPlugin('dashboard'); @@ -239,6 +336,7 @@ export default { }); } }, + // highlight-end }; ``` @@ -256,6 +354,7 @@ export default { name: 'Widget Plugin', }); }, + // highlight-start bootstrap(app: StrapiApp) { const dashboardPlugin = app.getPlugin('dashboard'); @@ -266,6 +365,7 @@ export default { }); } }, + // highlight-end }; ``` @@ -282,7 +382,7 @@ The `injectComponent()` method accepts the following parameters: | `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) | -## Accessing Content Manager data +## Content Manager data access When injecting components into Content Manager injection zones, you can access the Edit View data using the `useContentManagerContext` hook: @@ -355,8 +455,8 @@ export const MyCustomButton = () => { -:::note -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. +:::caution +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`](/cms/migration/v4-to-v5/additional-resources/helper-plugin#usecmeditviewdatamanager) from `@strapi/helper-plugin` which is not available in Strapi 5. ::: ## Best practices diff --git a/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md b/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md new file mode 100644 index 0000000000..87f310b815 --- /dev/null +++ b/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md @@ -0,0 +1,54 @@ + +### Injection zones vs. 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`](/cms/plugins-development/content-manager-apis#addeditviewsidepanel)) | Best for contextual information or controls that stay visible while editing. | +| Add actions in a document action menu | Content Manager API ([`addDocumentAction`](/cms/plugins-development/content-manager-apis#adddocumentaction)) | Best for document-level actions in the Edit View actions menu. | +| Add actions in the Edit View header | Content Manager API ([`addDocumentHeaderAction`](/cms/plugins-development/content-manager-apis#adddocumentheaderaction)) | Best for quick, prominent actions next to the document title. | +| Add actions for selected entries in List View | Content Manager API ([`addBulkAction`](/cms/plugins-development/content-manager-apis#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`](/cms/plugins-development/admin-injection-zones#injecting-into-content-manager-zones)) | 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](https://github.com/strapi/strapi/blob/develop/packages/core/content-manager/admin/src/content-manager.ts). + + + +**Mini examples (inside `bootstrap(app)`)** + +```js +// Document action menu item +app.getPlugin('content-manager').apis.addDocumentAction(() => ({ + label: 'Run custom action', + onClick: ({ documentId }) => runCustomAction(documentId), +})); + +// Edit View header action +app.getPlugin('content-manager').apis.addDocumentHeaderAction(() => ({ + label: 'Open preview', + onClick: ({ document }) => openPreview(document), +})); + +// List View bulk action +app.getPlugin('content-manager').apis.addBulkAction(() => ({ + label: 'Bulk publish', + onClick: ({ documentIds }) => bulkPublish(documentIds), +})); + +// Edit View side panel +app.getPlugin('content-manager').apis.addEditViewSidePanel([ + { + name: 'my-plugin.side-panel', + Component: MySidePanel, + }, +]); + +// Injection zone (plugin-defined zone) +app.injectContentManagerComponent('editView', 'right-links', { + name: 'my-plugin.custom-link', + Component: MyCustomLink, +}); +``` + + \ No newline at end of file From 364a19649a776d0ae415ce9a2199e73fc1299854 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:00:37 +0100 Subject: [PATCH 33/64] Improve injection zones further and fix deprecated hook example --- .../admin-injection-zones.md | 77 ++++++++++++++----- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-injection-zones.md b/docusaurus/docs/cms/plugins-development/admin-injection-zones.md index 4b7d0a99e1..6fe8e66e96 100644 --- a/docusaurus/docs/cms/plugins-development/admin-injection-zones.md +++ b/docusaurus/docs/cms/plugins-development/admin-injection-zones.md @@ -97,7 +97,7 @@ export default { .injectComponent('listView', 'actions', { name: 'my-plugin-list-action', Component: () => , - }); + }); // highlight-end // Inject additional information into the publish modal @@ -117,7 +117,7 @@ export default { .injectComponent('preview', 'actions', { name: 'my-plugin-preview-action', Component: PreviewAction, - }); + }); // highlight-end }, }; @@ -151,18 +151,18 @@ export default { // highlight-end // Inject a component into the List view's actions zone - app // highlight-start - .getPlugin('content-manager') + app + .getPlugin('content-manager') .injectComponent('listView', 'actions', { name: 'my-plugin-list-action', Component: () => , - }); + }); // highlight-end // Inject additional information into the publish modal - app // highlight-start + app .getPlugin('content-manager') .injectComponent('listView', 'publishModalAdditionalInfos', { name: 'my-plugin-publish-modal-info', @@ -171,13 +171,13 @@ export default { // highlight-end // Inject a component into the Preview view's actions zone - app // highlight-start + app .getPlugin('content-manager') .injectComponent('preview', 'actions', { name: 'my-plugin-preview-action', Component: PreviewAction, - }); + }); // highlight-end }, }; @@ -211,8 +211,8 @@ export default { after: [], }, }, + // highlight-end }); - // highlight-end }, }; ``` @@ -251,15 +251,31 @@ export default { ### Rendering injection zones in components - - -To render injected components in your plugin's UI, use the `` React component with an `area` prop following the naming convention `plugin-name.viewName.injectionZoneName`: +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`. +```jsx title="admin/src/components/CustomInjectionZone.jsx" +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 }) => ); +}; +``` + ```jsx title="admin/src/pages/Dashboard.jsx" -import { InjectionZone } from '@strapi/helper-plugin'; +import { CustomInjectionZone } from '../components/CustomInjectionZone'; const Dashboard = () => { return ( @@ -267,12 +283,12 @@ const Dashboard = () => {

Dashboard

{/* Render components injected into the top zone */} - +
{/* Main dashboard content */}
{/* Render components injected into the bottom zone */} - + ); }; @@ -283,8 +299,31 @@ export default Dashboard;
+```tsx title="admin/src/components/CustomInjectionZone.tsx" +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 }) => ); +}; +``` + ```tsx title="admin/src/pages/Dashboard.tsx" -import { InjectionZone } from '@strapi/helper-plugin'; +import { CustomInjectionZone } from '../components/CustomInjectionZone'; const Dashboard = () => { return ( @@ -292,12 +331,12 @@ const Dashboard = () => {

Dashboard

{/* Render components injected into the top zone */} - +
{/* Main dashboard content */}
{/* Render components injected into the bottom zone */} - + ); }; @@ -456,7 +495,7 @@ export const MyCustomButton = () => {
:::caution -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`](/cms/migration/v4-to-v5/additional-resources/helper-plugin#usecmeditviewdatamanager) from `@strapi/helper-plugin` which is not available in Strapi 5. +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`](/cms/migration/v4-to-v5/additional-resources/helper-plugin#usecmeditviewdatamanager) from `@strapi/helper-plugin` which is not available in Strapi 5. ::: ## Best practices From 4dff3ad0c6d6ab5d10dfa2ddc60ceec131415e4b Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:16:11 +0100 Subject: [PATCH 34/64] Improve Redux store & reducers page --- .../plugins-development/admin-redux-store.md | 70 ++++++++++++++----- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-redux-store.md b/docusaurus/docs/cms/plugins-development/admin-redux-store.md index f8df4add06..3153cff5ee 100644 --- a/docusaurus/docs/cms/plugins-development/admin-redux-store.md +++ b/docusaurus/docs/cms/plugins-development/admin-redux-store.md @@ -1,31 +1,30 @@ --- -title: Accessing the Redux store -description: Read state, dispatch actions, and subscribe to changes in Strapi's admin panel Redux store from your plugin. +title: Redux store & reducers +description: Add custom reducers, read state, dispatch actions, and subscribe to changes in Strapi's admin panel Redux store from your plugin. pagination_prev: cms/plugins-development/admin-injection-zones -pagination_next: cms/plugins-development/content-manager-apis +pagination_next: cms/plugins-development/admin-hooks displayed_sidebar: cmsSidebar toc_max_heading_level: 4 tags: - admin panel - - Redux store - - useSelector - - useDispatch + - admin panel customization + - admin panel API - plugins development --- -# Accessing the Redux store +import Prerequisite from '/docs/snippets/plugins-development-create-plugin-prerequisite-admin-panel.md' + +# Admin Panel API: Redux store & reducers -Strapi's admin panel uses a global Redux store. Plugins can read state with `useSelector`, update state with `useDispatch`, and subscribe to changes with `useStore`. The `admin_app` slice exposes theme, locale, permissions, and authentication data. +Use `addReducers()` during `register` to add custom state to the Redux store. Then read state with `useSelector`, update it with `useDispatch`, and subscribe to changes with `useStore`. The `admin_app` slice exposes theme, locale, permissions, and authentication data. Strapi's admin panel uses a global Redux store to manage application state. Plugins can access this store to read state, dispatch actions, and subscribe to state changes. This enables plugins to interact with core admin functionality like theme settings, language preferences, and authentication state. -:::prerequisites -You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin) and are familiar with the [admin entry file lifecycle](/cms/plugins-development/admin-configuration-customization#overview). -::: + ## Store overview @@ -35,6 +34,34 @@ The Redux store is automatically provided to all plugin components through React - `adminApi`: RTK Query API state for admin endpoints - Plugin-specific slices: additional reducers added by plugins +## Adding custom reducers + +Reducers are reducers that can be used to share state between components. Reducers can be useful when: + +* Large amounts of application state are needed in many places in the application. +* The application state is updated frequently. +* The logic to update that state may be complex. + +Reducers can be added to a plugin interface with the `addReducers()` function during the [`register`](/cms/plugins-development/admin-panel-api#register) lifecycle. + +A reducer is declared as an object with this syntax: + +```js title="my-plugin/admin/src/index.js" +import { exampleReducer } from './reducers' + +const reducers = { + // Reducer Syntax + [`${pluginId}_exampleReducer`]: exampleReducer +} + +export default { + register(app) { + app.addReducers(reducers) + }, + bootstrap() {}, +}; +``` + ## Reading state with `useSelector` The most common way to access Redux state in your plugin components is using the `useSelector` hook from `react-redux`: @@ -307,12 +334,13 @@ const App = () => { ## Complete example -
-Example combining all 3 patterns (useSelector, useDispatch, useStore) +The following example combines all 3 patterns (useSelector, useDispatch, useStore) described on the present page: + + ```jsx title="admin/src/pages/HomePage.jsx" import { Main } from '@strapi/design-system'; import { Button, Box, Typography, Flex } from '@strapi/design-system'; @@ -434,9 +462,15 @@ const HomePage = () => { export { HomePage }; ``` + + +
+
+ + ```tsx title="admin/src/pages/HomePage.tsx" import { Main } from '@strapi/design-system'; import { Button, Box, Typography, Flex } from '@strapi/design-system'; @@ -558,19 +592,21 @@ const HomePage = () => { export { HomePage }; ``` + + +
+
-
- ## Best practices -- **Use `useSelector` for reading state.** Prefer `useSelector` over direct store access. It automatically subscribes to updates and re-renders components when the selected state changes. +- **Use `useSelector` for reading state.** Prefer [`useSelector`](#reading-state-with-useselector) over direct store access. It automatically subscribes to updates and re-renders components when the selected state changes. - **Clean up subscriptions.** Always unsubscribe from store subscriptions in `useEffect` cleanup functions to prevent memory leaks. - **Consider type safety.** For better TypeScript support, create typed selectors or use typed hooks from `@strapi/admin` if available. - **Avoid unnecessary dispatches.** Only dispatch actions when you need to update state. Reading state does not require dispatching actions. - **Respect core state.** Be careful when modifying core admin state (like theme or locale) as it affects the entire admin panel. Consider whether your plugin should modify global state or maintain its own local state. :::tip -For plugins that need to add their own state to the Redux store, use the `addReducers` method in the `register` lifecycle function to inject custom reducers. See the [Admin Panel API documentation](/cms/plugins-development/admin-panel-api) for details. +To add your own state to the Redux store, see [Adding custom reducers](#adding-custom-reducers) above. ::: \ No newline at end of file From 3d1d0c03164c437a5730a446e318af50bd1673bb Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:21:47 +0100 Subject: [PATCH 35/64] Improve Redux store page further --- .../plugins-development/admin-redux-store.md | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-redux-store.md b/docusaurus/docs/cms/plugins-development/admin-redux-store.md index 3153cff5ee..2dde9e0045 100644 --- a/docusaurus/docs/cms/plugins-development/admin-redux-store.md +++ b/docusaurus/docs/cms/plugins-development/admin-redux-store.md @@ -46,6 +46,9 @@ Reducers can be added to a plugin interface with the `addReducers()` function du A reducer is declared as an object with this syntax: + + + ```js title="my-plugin/admin/src/index.js" import { exampleReducer } from './reducers' @@ -62,6 +65,28 @@ export default { }; ``` + + + +```ts title="my-plugin/admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; +import { exampleReducer } from './reducers'; + +const reducers = { + [`${pluginId}_exampleReducer`]: exampleReducer, +}; + +export default { + register(app: StrapiApp) { + app.addReducers(reducers); + }, + bootstrap() {}, +}; +``` + + + + ## Reading state with `useSelector` The most common way to access Redux state in your plugin components is using the `useSelector` hook from `react-redux`: @@ -156,6 +181,10 @@ The `admin_app` slice contains the following state properties: To update the Redux store, use the `useDispatch` hook: +:::note +The examples below dispatch actions to core admin state (theme, locale) for illustration purposes. In practice, most plugins should dispatch actions to their own custom reducers rather than modifying global admin state. +::: + @@ -247,6 +276,8 @@ const HomePage = () => { ### Available actions + + The `admin_app` slice provides the following actions: | Action type | Payload type | Description | @@ -603,7 +634,7 @@ export { HomePage }; - **Use `useSelector` for reading state.** Prefer [`useSelector`](#reading-state-with-useselector) over direct store access. It automatically subscribes to updates and re-renders components when the selected state changes. - **Clean up subscriptions.** Always unsubscribe from store subscriptions in `useEffect` cleanup functions to prevent memory leaks. -- **Consider type safety.** For better TypeScript support, create typed selectors or use typed hooks from `@strapi/admin` if available. +- **Consider type safety.** For better TypeScript support, create typed selectors using the store's `RootState` type rather than casting to `any`. - **Avoid unnecessary dispatches.** Only dispatch actions when you need to update state. Reading state does not require dispatching actions. - **Respect core state.** Be careful when modifying core admin state (like theme or locale) as it affects the entire admin panel. Consider whether your plugin should modify global state or maintain its own local state. From 8a244ad58f92e99111f843c4971c2fa5230a9310 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:22:47 +0100 Subject: [PATCH 36/64] Improve Redux store page further based on Codex recommendations --- docusaurus/docs/cms/plugins-development/admin-redux-store.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-redux-store.md b/docusaurus/docs/cms/plugins-development/admin-redux-store.md index 2dde9e0045..17ad08918a 100644 --- a/docusaurus/docs/cms/plugins-development/admin-redux-store.md +++ b/docusaurus/docs/cms/plugins-development/admin-redux-store.md @@ -51,6 +51,7 @@ A reducer is declared as an object with this syntax: ```js title="my-plugin/admin/src/index.js" import { exampleReducer } from './reducers' +import pluginId from './pluginId' const reducers = { // Reducer Syntax @@ -71,6 +72,7 @@ export default { ```ts title="my-plugin/admin/src/index.ts" import type { StrapiApp } from '@strapi/admin/strapi-admin'; import { exampleReducer } from './reducers'; +import pluginId from './pluginId'; const reducers = { [`${pluginId}_exampleReducer`]: exampleReducer, @@ -283,6 +285,7 @@ The `admin_app` slice provides the following actions: | Action type | Payload type | Description | |---|---|---| | `admin/setAppTheme` | `string` | Set the theme (`'light'`, `'dark'`, or `'system'`) | +| `admin/setAvailableThemes` | `string[]` | Updates `theme.availableThemes` in `admin_app` | | `admin/setLocale` | `string` | Set the locale (e.g., `'en'`, `'fr'`) | | `admin/setToken` | `string \| null` | Set the authentication token | | `admin/login` | `{ token: string, persist?: boolean }` | Login action with token and persistence option | @@ -634,7 +637,7 @@ export { HomePage }; - **Use `useSelector` for reading state.** Prefer [`useSelector`](#reading-state-with-useselector) over direct store access. It automatically subscribes to updates and re-renders components when the selected state changes. - **Clean up subscriptions.** Always unsubscribe from store subscriptions in `useEffect` cleanup functions to prevent memory leaks. -- **Consider type safety.** For better TypeScript support, create typed selectors using the store's `RootState` type rather than casting to `any`. +- **Consider type safety.** For Redux state access in plugins, use `react-redux` hooks (`useSelector`, `useDispatch`) with plugin-local typing (for example `RootState` and `AppDispatch`). If you use Strapi admin utilities, import them from `@strapi/admin/strapi-admin` (not `@strapi/admin`). Avoid relying on undocumented typed Redux hooks as part of Strapi's public API until they are explicitly documented as stable. - **Avoid unnecessary dispatches.** Only dispatch actions when you need to update state. Reading state does not require dispatching actions. - **Respect core state.** Be careful when modifying core admin state (like theme or locale) as it affects the entire admin panel. Consider whether your plugin should modify global state or maintain its own local state. From 511c546862cbd687f4bc52b755a444fc2bda82fe Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:23:06 +0100 Subject: [PATCH 37/64] Add tl;dr to injection zones vs. CM APIs snippet --- .../docs/snippets/injection-zones-vs-content-manager-apis.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md b/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md index 87f310b815..9e851a2ade 100644 --- a/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md +++ b/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md @@ -1,6 +1,10 @@ ### Injection zones vs. Content Manager APIs +:::tip tl;dr +For adding panels, actions, or buttons to the Content Manager, the [Content Manager APIs](/cms/plugins-development/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 | From 96bfcc7c6f2840263353ae45c87f56b208e0ca14 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:23:49 +0100 Subject: [PATCH 38/64] Rework the admin-injection-zones prereq. based on prev. snippet --- .../docs/cms/plugins-development/admin-injection-zones.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-injection-zones.md b/docusaurus/docs/cms/plugins-development/admin-injection-zones.md index 6fe8e66e96..8de3391d29 100644 --- a/docusaurus/docs/cms/plugins-development/admin-injection-zones.md +++ b/docusaurus/docs/cms/plugins-development/admin-injection-zones.md @@ -28,16 +28,12 @@ Injection zones are predefined areas in the admin UI where plugins can inject Re 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. + + :::note Injection zones are defined in the [`register`](/cms/plugins-development/admin-panel-api#register) lifecycle function, but components are injected in the [`bootstrap`](/cms/plugins-development/admin-panel-api#bootstrap) lifecycle function. ::: -:::tip -For adding panels, actions, or buttons to the Content Manager, the [Content Manager APIs](/cms/plugins-development/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. -::: - - - ## Predefined injection zones From 1d5b8920fc2f83ed3469871d0b636d527d03fea0 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:24:12 +0100 Subject: [PATCH 39/64] Improve tags & intro. for CM APIs page --- .../plugins-development/content-manager-apis.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/content-manager-apis.md b/docusaurus/docs/cms/plugins-development/content-manager-apis.md index 297652e221..abb1548839 100644 --- a/docusaurus/docs/cms/plugins-development/content-manager-apis.md +++ b/docusaurus/docs/cms/plugins-development/content-manager-apis.md @@ -2,16 +2,18 @@ title: Content Manager APIs description: The Content Manager APIs reference lists the APIs available to plugins for adding actions and options to the Content Manager List view and Edit view. pagination_prev: cms/plugins-development/admin-hooks -pagination_next: cms/plugins-development/server-api +pagination_next: cms/plugins-development/admin-injection-zones displayed_sidebar: cmsSidebar toc_max_heading_level: 4 tags: +- admin panel - admin panel API +- admin panel customization - plugins development -- plugins --- import Prerequisite from '/docs/snippets/plugins-development-create-plugin-prerequisite-admin-panel.md' +import InjectionVsCmApis from '/docs/snippets/injection-zones-vs-content-manager-apis.md' # Content Manager APIs @@ -23,17 +25,13 @@ Content Manager APIs are part of the [Admin Panel API](/cms/plugins-development/ -Strapi 5 provides 4 Content Manager APIs, all accessible through `app.getPlugin('content-manager').apis`: - -- [`addEditViewSidePanel`](#addeditviewsidepanel), -- [`addDocumentAction`](#adddocumentaction), -- [`addDocumentHeaderAction`](#adddocumentheaderaction), -- and [`addBulkAction`](#addbulkaction). - ## General information +Strapi 5 provides 4 Content Manager APIs, all accessible through `app.getPlugin('content-manager').apis`. All the Content Manager APIs share the same API shape and must use components. + + ### API shape All Content Manager APIs works in the same way: to use them, call them on your plugin's [`bootstrap()`](/cms/plugins-development/admin-panel-api#bootstrap) function, in 2 possible ways: From f871611607ecd6b1cae6eaae62bdab81de9ea05e Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:44:57 +0100 Subject: [PATCH 40/64] Fix prev. pagination for CM APIs page --- docusaurus/docs/cms/plugins-development/content-manager-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/plugins-development/content-manager-apis.md b/docusaurus/docs/cms/plugins-development/content-manager-apis.md index abb1548839..4cd04e0cc4 100644 --- a/docusaurus/docs/cms/plugins-development/content-manager-apis.md +++ b/docusaurus/docs/cms/plugins-development/content-manager-apis.md @@ -1,7 +1,7 @@ --- title: Content Manager APIs description: The Content Manager APIs reference lists the APIs available to plugins for adding actions and options to the Content Manager List view and Edit view. -pagination_prev: cms/plugins-development/admin-hooks +pagination_prev: cms/plugins-development/admin-navigation-settings pagination_next: cms/plugins-development/admin-injection-zones displayed_sidebar: cmsSidebar toc_max_heading_level: 4 From e5e079a42b639b9d515239dd6440d26fc77ad0fe Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:45:07 +0100 Subject: [PATCH 41/64] Rework admin hooks page --- .../cms/plugins-development/admin-hooks.md | 399 ++++++++++++++++++ 1 file changed, 399 insertions(+) create mode 100644 docusaurus/docs/cms/plugins-development/admin-hooks.md diff --git a/docusaurus/docs/cms/plugins-development/admin-hooks.md b/docusaurus/docs/cms/plugins-development/admin-hooks.md new file mode 100644 index 0000000000..ca1c982a30 --- /dev/null +++ b/docusaurus/docs/cms/plugins-development/admin-hooks.md @@ -0,0 +1,399 @@ +--- +title: Admin hooks +description: Create and register hooks in Strapi plugins to let other plugins add personalized behavior to your application. +pagination_prev: cms/plugins-development/admin-redux-store +pagination_next: cms/plugins-development/content-manager-apis +displayed_sidebar: cmsSidebar +tags: +- admin panel +- admin panel customization +- admin panel API +- hooks API +- plugins development +--- + +import Prerequisite from '/docs/snippets/plugins-development-create-plugin-prerequisite-admin-panel.md' + +# Admin Panel API: Hooks + + + +The Hooks API lets plugins create extension points (`createHook` in `register`) and subscribe to them (`registerHook` in `bootstrap`). Hooks run in series, waterfall, or parallel. Strapi includes predefined hooks for the Content Manager's List and Edit views. + + + +The Hooks API allows a plugin to create and register hooks, i.e. places in the application where plugins can add personalized behavior. + + + +## Creating hooks + +Create hook extension points with `createHook()` during the [`register`](/cms/plugins-development/admin-panel-api#register) lifecycle. This declares that your plugin provides an extension point that other plugins can subscribe to. + + + + +```js title="my-plugin/admin/src/index.js" +export default { + register(app) { + app.createHook('My-PLUGIN/MY_HOOK'); + }, +}; +``` + + + + +```ts title="my-plugin/admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + register(app: StrapiApp) { + app.createHook('My-PLUGIN/MY_HOOK'); + }, +}; +``` + + + + +:::note +For predictable interoperability between plugins, use stable namespaced hook IDs such as `my-plugin/my-hook`. +::: + +## Subscribing to hooks + +Subscribe to hooks with `registerHook()` during the [`bootstrap`](/cms/plugins-development/admin-panel-api#bootstrap) lifecycle, once all plugins are loaded. The callback receives arguments from the hook caller and should return the (optionally mutated) data. + + + + +```js title="my-other-plugin/admin/src/index.js" +export default { + bootstrap(app) { + app.registerHook('My-PLUGIN/MY_HOOK', (...args) => { + console.log(args); + + // important: return the mutated data + return args; + }); + }, +}; +``` + + + + +```ts title="my-other-plugin/admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + bootstrap(app: StrapiApp) { + app.registerHook('My-PLUGIN/MY_HOOK', (...args: unknown[]) => { + console.log(args); + + // important: return the mutated data + return args; + }); + }, +}; +``` + + + + +Async callbacks are also supported: + + + + +```js title="my-other-plugin/admin/src/index.js" +export default { + bootstrap(app) { + app.registerHook('My-PLUGIN/MY_HOOK', async (data) => { + const enrichedData = await fetchExternalData(data); + + // always return data for waterfall hooks + return enrichedData; + }); + }, +}; +``` + + + + +```ts title="my-other-plugin/admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + bootstrap(app: StrapiApp) { + app.registerHook('My-PLUGIN/MY_HOOK', async (data: unknown) => { + const enrichedData = await fetchExternalData(data); + + // always return data for waterfall hooks + return enrichedData; + }); + }, +}; +``` + + + + +## Hook execution modes + +Hooks can be run in 3 modes: + +| Mode | Function | Return value | +|---|---|---| +| Series | `runHookSeries` | Array of results from each function, in order | +| Parallel | `runHookParallel` | Array of resolved promise results, in order | +| Waterfall | `runHookWaterfall` | Single value after all transformations applied sequentially | + +:::caution +For `runHookWaterfall`, each subscriber must return the transformed value so that the next subscriber in the chain receives it. Failing to return a value will break the chain. +::: + +## Predefined hooks + +Strapi includes predefined hooks for the Content Manager's List and Edit views. + +### inject-column-in-table + +The `Admin/CM/pages/ListView/inject-column-in-table` hook can add or mutate columns in the List View of the [Content Manager](/cms/intro): + +```tsx +runHookWaterfall(INJECT_COLUMN_IN_TABLE, { + displayedHeaders: ListFieldLayout[], + layout: ListFieldLayout, +}); +``` + +The following example subscribes to this hook to add a custom "External id" column: + + + + +```js title="my-plugin/admin/src/index.js" +export default { + bootstrap(app) { + app.registerHook( + 'Admin/CM/pages/ListView/inject-column-in-table', + ({ displayedHeaders, layout }) => { + return { + displayedHeaders: [ + ...displayedHeaders, + { + attribute: { type: 'custom' }, + label: 'External id', + name: 'externalId', + searchable: false, + sortable: false, + cellFormatter: (document) => document.externalId, + }, + ], + layout, + }; + } + ); + }, +}; +``` + + + + +```ts title="my-plugin/admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + bootstrap(app: StrapiApp) { + app.registerHook( + 'Admin/CM/pages/ListView/inject-column-in-table', + ({ displayedHeaders, layout }) => { + return { + displayedHeaders: [ + ...displayedHeaders, + { + attribute: { type: 'custom' }, + label: 'External id', + name: 'externalId', + searchable: false, + sortable: false, + cellFormatter: (document) => document.externalId, + }, + ], + layout, + }; + } + ); + }, +}; +``` + + + + +
+ListFieldLayout and ListLayout type definitions + +```tsx +interface ListFieldLayout { + /** + * The attribute data from the content-type's schema for the field + */ + attribute: Attribute.Any | { type: 'custom' }; + /** + * Typically used by plugins to render a custom cell + */ + cellFormatter?: ( + data: Document, + header: Omit, + { collectionType, model }: { collectionType: string; model: string } + ) => React.ReactNode; + label: string | MessageDescriptor; + /** + * the name of the attribute we use to display the actual name e.g. relations + * are just ids, so we use the mainField to display something meaninginful by + * looking at the target's schema + */ + mainField?: string; + name: string; + searchable?: boolean; + sortable?: boolean; +} + +interface ListLayout { + layout: ListFieldLayout[]; + components?: never; + metadatas: { + [K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['list']; + }; + options: LayoutOptions; + settings: LayoutSettings; +} + +type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object; + +interface LayoutSettings extends Contracts.ContentTypes.Settings { + displayName?: string; + icon?: never; +} +``` + +
+ +### mutate-edit-view-layout + +The `Admin/CM/pages/EditView/mutate-edit-view-layout` hook can mutate the Edit View layout of the [Content Manager](/cms/intro). + +The following example subscribes to this hook to force all fields to full width: + + + + +```js title="my-plugin/admin/src/index.js" +export default { + bootstrap(app) { + app.registerHook( + 'Admin/CM/pages/EditView/mutate-edit-view-layout', + ({ layout, ...rest }) => { + // Force all fields to full width in the default edit layout + const updatedLayout = layout.map((rowGroup) => + rowGroup.map((row) => row.map((field) => ({ ...field, size: 12 }))) + ); + + return { + ...rest, + layout: updatedLayout, + }; + } + ); + }, +}; +``` + + + + +```ts title="my-plugin/admin/src/index.ts" +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + bootstrap(app: StrapiApp) { + app.registerHook( + 'Admin/CM/pages/EditView/mutate-edit-view-layout', + ({ layout, ...rest }) => { + // Force all fields to full width in the default edit layout + const updatedLayout = layout.map((rowGroup) => + rowGroup.map((row) => row.map((field) => ({ ...field, size: 12 }))) + ); + + return { + ...rest, + layout: updatedLayout, + }; + } + ); + }, +}; +``` + + + + +
+EditLayout and EditFieldLayout type definitions + +```tsx +interface EditLayout { + layout: Array>; + components: { + [uid: string]: { + layout: Array; + settings: Contracts.Components.ComponentConfiguration['settings'] & { + displayName?: string; + icon?: string; + }; + }; + }; + metadatas: { + [K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['edit']; + }; + options: LayoutOptions; + settings: LayoutSettings; +} + +interface EditFieldSharedProps extends Omit { + hint?: string; + mainField?: string; + size: number; + unique?: boolean; + visible?: boolean; +} + +/** + * Map over all the types in Attribute Types and use that to create a union of new types where the attribute type + * is under the property attribute and the type is under the property type. + */ +type EditFieldLayout = { + [K in Attribute.Kind]: EditFieldSharedProps & { + attribute: Extract; + type: K; + }; +}[Attribute.Kind]; + +type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object; + +interface LayoutSettings extends Contracts.ContentTypes.Settings { + displayName?: string; + icon?: never; +} +``` + +
+ +:::note +`EditViewLayout` and `ListViewLayout` are parts of the `useDocumentLayout` hook (see ). +::: \ No newline at end of file From c8f3c1a10ffcc56483be395c24a69a4a25d4fecf Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:55:41 +0100 Subject: [PATCH 42/64] Improve formatting in hooks page --- .../cms/plugins-development/admin-hooks.md | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-hooks.md b/docusaurus/docs/cms/plugins-development/admin-hooks.md index ca1c982a30..5cc8cbca4b 100644 --- a/docusaurus/docs/cms/plugins-development/admin-hooks.md +++ b/docusaurus/docs/cms/plugins-development/admin-hooks.md @@ -141,7 +141,7 @@ export default {
-## Hook execution modes +## Running hooks Hooks can be run in 3 modes: @@ -155,11 +155,11 @@ Hooks can be run in 3 modes: For `runHookWaterfall`, each subscriber must return the transformed value so that the next subscriber in the chain receives it. Failing to return a value will break the chain. ::: -## Predefined hooks +## Using predefined hooks Strapi includes predefined hooks for the Content Manager's List and Edit views. -### inject-column-in-table +### `INJECT-COLUMN-IN-TABLE` The `Admin/CM/pages/ListView/inject-column-in-table` hook can add or mutate columns in the List View of the [Content Manager](/cms/intro): @@ -235,8 +235,9 @@ export default { -
-ListFieldLayout and ListLayout type definitions +**ListFieldLayout and ListLayout type definitions**: + + ```tsx interface ListFieldLayout { @@ -282,9 +283,9 @@ interface LayoutSettings extends Contracts.ContentTypes.Settings { } ``` -
+ -### mutate-edit-view-layout +### `MUTATE-EDIT-VIEW-LAYOUT` The `Admin/CM/pages/EditView/mutate-edit-view-layout` hook can mutate the Edit View layout of the [Content Manager](/cms/intro). @@ -343,8 +344,9 @@ export default { -
-EditLayout and EditFieldLayout type definitions +**EditLayout and EditFieldLayout type definitions:** + + ```tsx interface EditLayout { @@ -392,7 +394,7 @@ interface LayoutSettings extends Contracts.ContentTypes.Settings { } ``` -
+ :::note `EditViewLayout` and `ListViewLayout` are parts of the `useDocumentLayout` hook (see ). From 10f666286ddb8e0d131c544cdda2ac87ab93482d Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:55:58 +0100 Subject: [PATCH 43/64] Use ExternalLink in injection zones vs. CM APIs snippet --- .../docs/snippets/injection-zones-vs-content-manager-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md b/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md index 9e851a2ade..6b8a8d88a5 100644 --- a/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md +++ b/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md @@ -15,7 +15,7 @@ Content Manager APIs and injection zones are both extension points to customize | Add actions for selected entries in List View | Content Manager API ([`addBulkAction`](/cms/plugins-development/content-manager-apis#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`](/cms/plugins-development/admin-injection-zones#injecting-into-content-manager-zones)) | 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](https://github.com/strapi/strapi/blob/develop/packages/core/content-manager/admin/src/content-manager.ts). +For implementation details and up-to-date API signatures, please refer to the file in the Strapi codebase](). From 79318672e8095d1f4ca81a035924e0ff5a1f50bc Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:09:58 +0100 Subject: [PATCH 44/64] Refine hooks end-of-page note --- docusaurus/docs/cms/plugins-development/admin-hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-hooks.md b/docusaurus/docs/cms/plugins-development/admin-hooks.md index 5cc8cbca4b..82b1ac4ef9 100644 --- a/docusaurus/docs/cms/plugins-development/admin-hooks.md +++ b/docusaurus/docs/cms/plugins-development/admin-hooks.md @@ -397,5 +397,5 @@ interface LayoutSettings extends Contracts.ContentTypes.Settings { :::note -`EditViewLayout` and `ListViewLayout` are parts of the `useDocumentLayout` hook (see ). +The `EditLayout` and `ListLayout` shapes documented here come from the `useDocumentLayout` hook (see ). Internal package naming can vary, but plugin authors should rely on the `EditLayout` and `ListLayout` shapes exposed in this page. ::: \ No newline at end of file From 637a850137a07a3e5f259913188091b7dfdca5b8 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:10:09 +0100 Subject: [PATCH 45/64] Fix tags for admin localization --- .../docs/cms/plugins-development/admin-localization.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-localization.md b/docusaurus/docs/cms/plugins-development/admin-localization.md index 287c92c119..e0481dda0e 100644 --- a/docusaurus/docs/cms/plugins-development/admin-localization.md +++ b/docusaurus/docs/cms/plugins-development/admin-localization.md @@ -1,18 +1,15 @@ --- title: Admin localization description: Provide translations for your Strapi plugin's admin panel interface using registerTrads and react-intl. -pagination_prev: cms/plugins-development/admin-navigation-settings -pagination_next: cms/plugins-development/admin-injection-zones +pagination_prev: cms/plugins-development/admin-hooks +pagination_next: cms/plugins-development/server-api displayed_sidebar: cmsSidebar toc_max_heading_level: 4 tags: - admin panel - admin panel customization - admin panel API - - localization - - translations - - registerTrads - - react-intl + - internationalization - plugins development --- From 521714e1eaa95ac21b3c7f495e26062df4a18ce8 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:10:31 +0100 Subject: [PATCH 46/64] Refine Server API cross link with Admin Panel API --- docusaurus/docs/cms/plugins-development/server-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/plugins-development/server-api.md b/docusaurus/docs/cms/plugins-development/server-api.md index 69bb00a9b4..33d601c823 100644 --- a/docusaurus/docs/cms/plugins-development/server-api.md +++ b/docusaurus/docs/cms/plugins-development/server-api.md @@ -23,7 +23,7 @@ tags: A Strapi plugin can interact with both the back end and the of a Strapi application. The Server API is about the back-end part, i.e. how the plugin interacts with the server part of a Strapi application. -For more information about the front end part, see [front end](/cms/plugins-development/admin-panel-api). +For more information on how plugins can modify the front end part of Strapi, see [front end](/cms/plugins-development/admin-panel-api). :::prerequisites You have [created a Strapi plugin](/cms/plugins-development/create-a-plugin). From f66357bb7078e331df6f0307965683fc7de663f3 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:15:26 +0100 Subject: [PATCH 47/64] Further refine some admin-localization notes (merge order, programmatic access) --- .../docs/cms/plugins-development/admin-localization.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-localization.md b/docusaurus/docs/cms/plugins-development/admin-localization.md index e0481dda0e..8401a2447f 100644 --- a/docusaurus/docs/cms/plugins-development/admin-localization.md +++ b/docusaurus/docs/cms/plugins-development/admin-localization.md @@ -54,7 +54,7 @@ Each translation file contains key-value pairs where keys are translation identi ## The `registerTrads` function {#registertrads} -In Strapi plugins that have an admin part, `registerTrads()` is used to load translations for your plugin’s UI labels and messages. If you don’t need localization, you can omit it and the plugin can still run. +In Strapi plugins that have an admin part, `registerTrads()` is used to load translations for your plugin's UI labels and messages. If you don't need localization, you can omit it and the plugin can still run. The `registerTrads()` function is an async function that loads translation files for all configured locales. Strapi calls this function during admin panel initialization to collect translations from all plugins. @@ -445,7 +445,7 @@ Strapi's admin panel automatically: 3. Applies custom translations from the admin configuration (if any) 4. Makes translations available via `react-intl` throughout the admin panel -The merge order ensures that core Strapi translations are loaded first, plugin translations are merged on top, and custom translations from admin config override both (allowing users to customize translations). +In practice, core admin translations are loaded first, plugin translations are merged on top, and project-level overrides in `config.translations` let you customize the labels displayed in the admin panel. ## Best practices @@ -461,5 +461,5 @@ The `en` locale is always available in Strapi and serves as the fallback locale. ::: :::tip -To see which locales are available in your Strapi instance, check the `config.locales` array in your `src/admin/app.ts` or `src/admin/app.js` file. You can also access available locales from the Redux store using `useSelector((state) => state.admin_app?.language?.localeNames)` (see [Accessing the Redux store](/cms/plugins-development/admin-redux-store)). +To see which locales are available in your Strapi instance, check the `config.locales` array in your `src/admin/app.ts` or `src/admin/app.js` file. For programmatic access at runtime, see [Accessing the Redux store](/cms/plugins-development/admin-redux-store) (note that internal store structure may change between versions). ::: \ No newline at end of file From a78660398bf0ea6928194d98a1722870afb22d58 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:25:48 +0100 Subject: [PATCH 48/64] Fix invalid comment in JSON example for admin-localization --- docusaurus/docs/cms/plugins-development/admin-localization.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-localization.md b/docusaurus/docs/cms/plugins-development/admin-localization.md index 8401a2447f..cbcd2a965f 100644 --- a/docusaurus/docs/cms/plugins-development/admin-localization.md +++ b/docusaurus/docs/cms/plugins-development/admin-localization.md @@ -41,8 +41,7 @@ admin/src/translations/ Each translation file contains key-value pairs where keys are translation identifiers and values are the translated strings: -```json -// admin/src/translations/en.json +```json title="admin/src/translations/en.json" { "plugin.name": "My Plugin", "plugin.description": "A custom Strapi plugin", From 3e7799a9a5fffdafc5784cf74f6ca839f8e21bbd Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:26:28 +0100 Subject: [PATCH 49/64] Fix broken links & anchors --- .../edit-view-layout-and-list-view-layout-rewritten.md | 2 +- .../breaking-changes/inject-content-manager-component.md | 2 +- .../cms/migration/v4-to-v5/breaking-changes/license-only.md | 2 +- .../migration/v4-to-v5/breaking-changes/react-router-dom-6.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/edit-view-layout-and-list-view-layout-rewritten.md b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/edit-view-layout-and-list-view-layout-rewritten.md index 3ebdb9a368..a31d12f58c 100644 --- a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/edit-view-layout-and-list-view-layout-rewritten.md +++ b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/edit-view-layout-and-list-view-layout-rewritten.md @@ -48,7 +48,7 @@ The `content-manager_editViewLayoutManager` and `content-manager_listViewLayoutM ### Notes -Additional information about hooks can be found in the [Admin Panel API for plugins](/cms/plugins-development/admin-panel-api#hooks-api) documentation. +Additional information about hooks can be found in the [Admin Panel API for plugins](/cms/plugins-development/admin-hooks) documentation. ### Manual procedure diff --git a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/inject-content-manager-component.md b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/inject-content-manager-component.md index e5033be6ac..28089f6cc7 100644 --- a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/inject-content-manager-component.md +++ b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/inject-content-manager-component.md @@ -15,7 +15,7 @@ import MigrationIntro from '/docs/snippets/breaking-change-page-migration-intro. # `injectContentManagerComponent()` removed -In Strapi 5, the `injectContentManagerComponent` method is removed because the Content Manager is now a plugin. The [Admin Panel API](/cms/plugins-development/admin-panel-api#injecting-components) method is replaced by `getPlugin('content-manager').injectComponent()`. +In Strapi 5, the `injectContentManagerComponent` method is removed because the Content Manager is now a plugin. The [Admin Panel API](/cms/plugins-development/admin-injection-zones) method is replaced by `getPlugin('content-manager').injectComponent()`. diff --git a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/license-only.md b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/license-only.md index ae0cca1b3e..4f1f4c7662 100644 --- a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/license-only.md +++ b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/license-only.md @@ -58,7 +58,7 @@ A similar result can be achieved in Strapi v4 by adding the `lockIcon` property. - a settings menu item will look like the following: ![](/img/assets/plugins/lightning-icon-settings.png) -* Additional information and examples on how to use the `licenseOnly` property can be found in the [`addMenuLink()`](/cms/plugins-development/admin-panel-api#menu-api), [`addSettingsLink()`](/cms/plugins-development/admin-panel-api#addsettingslink), and [`addSettingsLinks()`](/cms/plugins-development/admin-panel-api#addsettingslinks) methods sections of the Admin Panel API documentation. +* Additional information and examples on how to use the `licenseOnly` property can be found in the [`addMenuLink()`](/cms/plugins-development/admin-configuration-customization#navigation-sidebar-menu-links), [`addSettingsLink()`](/cms/plugins-development/admin-configuration-customization#adding-links-to-existing-settings-sections), and [`addSettingsLinks()`](/cms/plugins-development/admin-configuration-customization#adding-links-to-existing-settings-sections) methods sections of the Admin Panel API documentation. ### Manual migration diff --git a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/react-router-dom-6.md b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/react-router-dom-6.md index e0e1928842..540375a414 100644 --- a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/react-router-dom-6.md +++ b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/react-router-dom-6.md @@ -15,7 +15,7 @@ import MigrationIntro from '/docs/snippets/breaking-change-page-migration-intro. # Strapi users `react-router-dom` v6 -Strapi 5 uses v6. This impacts the links added to [settings](/cms/plugins-development/admin-panel-api#settings-api) or to the [menu](/cms/plugins-development/admin-panel-api#menu-api) using the Admin Panel API. +Strapi 5 uses v6. This impacts the links added to [settings](/cms/plugins-development/admin-configuration-customization#settings) or to the [menu](/cms/plugins-development/admin-configuration-customization#navigation-sidebar-menu-links) using the Admin Panel API. @@ -61,4 +61,4 @@ Strapi 5 uses ). -Ensure that links added to [settings](/cms/plugins-development/admin-panel-api#settings-api) or to the [menu](/cms/plugins-development/admin-panel-api#menu-api) links using the Admin Panel API use relative paths. +Ensure that links added to [settings](/cms/plugins-development/admin-configuration-customization#settings) or to the [menu](/cms/plugins-development/admin-configuration-customization#navigation-sidebar-menu-links) links using the Admin Panel API use relative paths. From 2e4fbf928d8a8bde6b82b6069ce1ea68fffdc7fd Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:41:05 +0100 Subject: [PATCH 50/64] Fix more broken links & anchors --- .../cms/migration/v4-to-v5/breaking-changes/license-only.md | 2 +- .../migration/v4-to-v5/breaking-changes/react-router-dom-6.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/license-only.md b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/license-only.md index 4f1f4c7662..9a22ed53fc 100644 --- a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/license-only.md +++ b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/license-only.md @@ -58,7 +58,7 @@ A similar result can be achieved in Strapi v4 by adding the `lockIcon` property. - a settings menu item will look like the following: ![](/img/assets/plugins/lightning-icon-settings.png) -* Additional information and examples on how to use the `licenseOnly` property can be found in the [`addMenuLink()`](/cms/plugins-development/admin-configuration-customization#navigation-sidebar-menu-links), [`addSettingsLink()`](/cms/plugins-development/admin-configuration-customization#adding-links-to-existing-settings-sections), and [`addSettingsLinks()`](/cms/plugins-development/admin-configuration-customization#adding-links-to-existing-settings-sections) methods sections of the Admin Panel API documentation. +* Additional information and examples on how to use the `licenseOnly` property can be found in the [`addMenuLink()`](/cms/plugins-development/admin-navigation-settings#navigation-sidebar-menu-links), [`addSettingsLink()`](/cms/plugins-development/admin-navigation-settings#adding-links-to-existing-settings-sections), and [`addSettingsLinks()`](/cms/plugins-development/admin-navigation-settings#adding-links-to-existing-settings-sections) methods sections of the Admin Panel API documentation. ### Manual migration diff --git a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/react-router-dom-6.md b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/react-router-dom-6.md index 540375a414..08ae849da8 100644 --- a/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/react-router-dom-6.md +++ b/docusaurus/docs/cms/migration/v4-to-v5/breaking-changes/react-router-dom-6.md @@ -15,7 +15,7 @@ import MigrationIntro from '/docs/snippets/breaking-change-page-migration-intro. # Strapi users `react-router-dom` v6 -Strapi 5 uses v6. This impacts the links added to [settings](/cms/plugins-development/admin-configuration-customization#settings) or to the [menu](/cms/plugins-development/admin-configuration-customization#navigation-sidebar-menu-links) using the Admin Panel API. +Strapi 5 uses v6. This impacts the links added to [settings](/cms/plugins-development/admin-navigation-settings#settings) or to the [menu](/cms/plugins-development/admin-navigation-settings#navigation-sidebar-menu-links) using the Admin Panel API. @@ -61,4 +61,4 @@ Strapi 5 uses ). -Ensure that links added to [settings](/cms/plugins-development/admin-configuration-customization#settings) or to the [menu](/cms/plugins-development/admin-configuration-customization#navigation-sidebar-menu-links) links using the Admin Panel API use relative paths. +Ensure that links added to [settings](/cms/plugins-development/admin-navigation-settings#settings) or to the [menu](/cms/plugins-development/admin-navigation-settings#navigation-sidebar-menu-links) links using the Admin Panel API use relative paths. From ef1a7c33945836b86139adbd917d7af8d67083d4 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:41:15 +0100 Subject: [PATCH 51/64] Fix pagination for admin hooks --- docusaurus/docs/cms/plugins-development/admin-hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-hooks.md b/docusaurus/docs/cms/plugins-development/admin-hooks.md index 82b1ac4ef9..a45ef8d308 100644 --- a/docusaurus/docs/cms/plugins-development/admin-hooks.md +++ b/docusaurus/docs/cms/plugins-development/admin-hooks.md @@ -2,7 +2,7 @@ title: Admin hooks description: Create and register hooks in Strapi plugins to let other plugins add personalized behavior to your application. pagination_prev: cms/plugins-development/admin-redux-store -pagination_next: cms/plugins-development/content-manager-apis +pagination_next: cms/plugins-development/admin-localization displayed_sidebar: cmsSidebar tags: - admin panel From 6d24c9c0575f954c4ec852235cd8fcb6b9b0ed52 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:41:42 +0100 Subject: [PATCH 52/64] Fix snippet mismatching heading levels --- .../docs/cms/plugins-development/admin-injection-zones.md | 2 ++ .../docs/cms/plugins-development/content-manager-apis.md | 2 ++ .../docs/snippets/injection-zones-vs-content-manager-apis.md | 5 +---- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-injection-zones.md b/docusaurus/docs/cms/plugins-development/admin-injection-zones.md index 8de3391d29..a663b09967 100644 --- a/docusaurus/docs/cms/plugins-development/admin-injection-zones.md +++ b/docusaurus/docs/cms/plugins-development/admin-injection-zones.md @@ -34,6 +34,8 @@ Plugins can extend and customize existing admin panel sections by injecting cust Injection zones are defined in the [`register`](/cms/plugins-development/admin-panel-api#register) lifecycle function, but components are injected in the [`bootstrap`](/cms/plugins-development/admin-panel-api#bootstrap) lifecycle function. ::: +## Injection zones vs. Content Manager APIs + ## Predefined injection zones diff --git a/docusaurus/docs/cms/plugins-development/content-manager-apis.md b/docusaurus/docs/cms/plugins-development/content-manager-apis.md index 4cd04e0cc4..ef718e64fc 100644 --- a/docusaurus/docs/cms/plugins-development/content-manager-apis.md +++ b/docusaurus/docs/cms/plugins-development/content-manager-apis.md @@ -30,6 +30,8 @@ Content Manager APIs are part of the [Admin Panel API](/cms/plugins-development/ Strapi 5 provides 4 Content Manager APIs, all accessible through `app.getPlugin('content-manager').apis`. All the Content Manager APIs share the same API shape and must use components. +### Injection zones vs. Content Manager APIs + ### API shape diff --git a/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md b/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md index 6b8a8d88a5..3349d96e5b 100644 --- a/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md +++ b/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md @@ -1,6 +1,3 @@ - -### Injection zones vs. Content Manager APIs - :::tip tl;dr For adding panels, actions, or buttons to the Content Manager, the [Content Manager APIs](/cms/plugins-development/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. ::: @@ -15,7 +12,7 @@ Content Manager APIs and injection zones are both extension points to customize | Add actions for selected entries in List View | Content Manager API ([`addBulkAction`](/cms/plugins-development/content-manager-apis#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`](/cms/plugins-development/admin-injection-zones#injecting-into-content-manager-zones)) | Best when you target a specific zone exposed by a plugin. | -For implementation details and up-to-date API signatures, please refer to the file in the Strapi codebase](). +For implementation details and up-to-date API signatures, please refer to the file in the Strapi codebase. From 8d3827af5193f4e87723c4c7380847d31b6f6cda Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:41:47 +0100 Subject: [PATCH 53/64] Fix sidebar --- docusaurus/sidebars.js | 1 - 1 file changed, 1 deletion(-) diff --git a/docusaurus/sidebars.js b/docusaurus/sidebars.js index bd139bc17b..446dc5bce2 100644 --- a/docusaurus/sidebars.js +++ b/docusaurus/sidebars.js @@ -531,7 +531,6 @@ const sidebars = { collapsed: true, items: [ 'cms/plugins-development/server-api', - 'cms/plugins-development/plugins-extension', ], }, { From ec9ac300ab5b6a2dbec3ca218a197aa6ba0f2067 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:42:06 +0100 Subject: [PATCH 54/64] Remove registerTrads from admin panel API overview --- .../plugins-development/admin-panel-api.md | 84 ------------------- 1 file changed, 84 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/admin-panel-api.md b/docusaurus/docs/cms/plugins-development/admin-panel-api.md index ff7f197954..1eb29b6ad6 100644 --- a/docusaurus/docs/cms/plugins-development/admin-panel-api.md +++ b/docusaurus/docs/cms/plugins-development/admin-panel-api.md @@ -247,90 +247,6 @@ export default { - - ## Available actions The Admin Panel API allows a plugin to take advantage of several small APIs to perform actions that will modify the user interface, user experience, or behavior of the admin panel. From 224a7fb6f82763f75112b515eeb5aa23e65440ee Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:42:20 +0100 Subject: [PATCH 55/64] Fix wrong link in developing plugins guides cards --- docusaurus/docs/cms/plugins-development/developing-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/plugins-development/developing-plugins.md b/docusaurus/docs/cms/plugins-development/developing-plugins.md index b2dcfc6e21..37d59d389d 100644 --- a/docusaurus/docs/cms/plugins-development/developing-plugins.md +++ b/docusaurus/docs/cms/plugins-development/developing-plugins.md @@ -48,7 +48,7 @@ Plugins can also be used to add [custom fields](/cms/features/custom-fields) to - +
From 918d32577d309759daa9fcc42875f3fe0178c5c8 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:42:30 +0100 Subject: [PATCH 56/64] Fix missing word in Server API intro --- docusaurus/docs/cms/plugins-development/server-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/plugins-development/server-api.md b/docusaurus/docs/cms/plugins-development/server-api.md index 33d601c823..e43adbbbd0 100644 --- a/docusaurus/docs/cms/plugins-development/server-api.md +++ b/docusaurus/docs/cms/plugins-development/server-api.md @@ -21,7 +21,7 @@ tags: # Server API for plugins -A Strapi plugin can interact with both the back end and the of a Strapi application. The Server API is about the back-end part, i.e. how the plugin interacts with the server part of a Strapi application. +A Strapi plugin can interact with both the back end and the front-end of a Strapi application. The Server API is about the back-end part, i.e. how the plugin interacts with the server part of a Strapi application. For more information on how plugins can modify the front end part of Strapi, see [front end](/cms/plugins-development/admin-panel-api). From aa8016d15951399d3edaf0d5a84584bee71c18cc Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:48:10 +0100 Subject: [PATCH 57/64] Fix hyphenation in Server API intro. --- docusaurus/docs/cms/plugins-development/server-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/cms/plugins-development/server-api.md b/docusaurus/docs/cms/plugins-development/server-api.md index e43adbbbd0..3b84194e62 100644 --- a/docusaurus/docs/cms/plugins-development/server-api.md +++ b/docusaurus/docs/cms/plugins-development/server-api.md @@ -21,7 +21,7 @@ tags: # Server API for plugins -A Strapi plugin can interact with both the back end and the front-end of a Strapi application. The Server API is about the back-end part, i.e. how the plugin interacts with the server part of a Strapi application. +A Strapi plugin can interact with both the back end and the front end of a Strapi application. The Server API is about the back-end part, i.e. how the plugin interacts with the server part of a Strapi application. For more information on how plugins can modify the front end part of Strapi, see [front end](/cms/plugins-development/admin-panel-api). From fa95a0add1ddb839829ba94ed3e721b2b66b8ce4 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:55:21 +0100 Subject: [PATCH 58/64] Fix code example in snippet for injection zones vs. CM APIs --- .../docs/snippets/injection-zones-vs-content-manager-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md b/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md index 3349d96e5b..4de4f1fb4f 100644 --- a/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md +++ b/docusaurus/docs/snippets/injection-zones-vs-content-manager-apis.md @@ -46,7 +46,7 @@ app.getPlugin('content-manager').apis.addEditViewSidePanel([ ]); // Injection zone (plugin-defined zone) -app.injectContentManagerComponent('editView', 'right-links', { +app.getPlugin('content-manager').injectComponent('editView', 'right-links', { name: 'my-plugin.custom-link', Component: MyCustomLink, }); From 23878558cbed1f1d88eb3a16482332e382b6f5a9 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:07:10 +0100 Subject: [PATCH 59/64] Fix sidebar (missing plugin structure page) --- docusaurus/sidebars.js | 1 + 1 file changed, 1 insertion(+) diff --git a/docusaurus/sidebars.js b/docusaurus/sidebars.js index 446dc5bce2..a16b32c273 100644 --- a/docusaurus/sidebars.js +++ b/docusaurus/sidebars.js @@ -508,6 +508,7 @@ const sidebars = { collapsed: true, items: [ 'cms/plugins-development/create-a-plugin', + 'cms/plugins-development/plugin-structure', 'cms/plugins-development/plugin-sdk', ], }, From 1bbdf0a7ace2e79f397cddaf63a80c5a3caebd59 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:07:16 +0100 Subject: [PATCH 60/64] Fix paginations --- docusaurus/docs/cms/plugins-development/plugin-sdk.md | 1 + docusaurus/docs/cms/plugins-development/plugin-structure.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docusaurus/docs/cms/plugins-development/plugin-sdk.md b/docusaurus/docs/cms/plugins-development/plugin-sdk.md index 68ddd1f17d..7698616eb4 100644 --- a/docusaurus/docs/cms/plugins-development/plugin-sdk.md +++ b/docusaurus/docs/cms/plugins-development/plugin-sdk.md @@ -1,6 +1,7 @@ --- title: Plugin SDK reference description: Reference documentation for Strapi's Plugin SDK commands +pagination_prev: cms/plugins-development/plugin-structure displayed_sidebar: cmsSidebar tags: - backend server diff --git a/docusaurus/docs/cms/plugins-development/plugin-structure.md b/docusaurus/docs/cms/plugins-development/plugin-structure.md index 75e60236f7..facf29c7ed 100644 --- a/docusaurus/docs/cms/plugins-development/plugin-structure.md +++ b/docusaurus/docs/cms/plugins-development/plugin-structure.md @@ -2,6 +2,8 @@ title: Plugin structure description: Learn more about the structure of a Strapi plugin displayed_sidebar: cmsSidebar +pagination_prev: cms/plugins-development/developing-plugins +pagination_next: cms/plugins-development/plugin-sdk tags: - admin panel - Command Line Interface (CLI) From 7a0e6ffef2fee77c8e038fd3f88309177eccf6e8 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:14:33 +0100 Subject: [PATCH 61/64] Fix admin permissions for plugins example --- .../guides/admin-permissions-for-plugins.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/guides/admin-permissions-for-plugins.md b/docusaurus/docs/cms/plugins-development/guides/admin-permissions-for-plugins.md index 0c176a3c93..5c8bded9b8 100644 --- a/docusaurus/docs/cms/plugins-development/guides/admin-permissions-for-plugins.md +++ b/docusaurus/docs/cms/plugins-development/guides/admin-permissions-for-plugins.md @@ -36,13 +36,13 @@ const bootstrap = ({ strapi }) => { { section: 'plugins', displayName: 'Access the overview page', - uid: 'overview', + uid: 'overview.access', pluginName: 'my-plugin', }, { section: 'plugins', displayName: 'Access the content manager sidebar', - uid: 'sidebar', + uid: 'sidebar.access', pluginName: 'my-plugin', }, ]; @@ -146,7 +146,7 @@ import pluginPermissions from './permissions'; export default { register(app) { app.addMenuLink({ - to: `plugins/${PluginIcon}`, + to: `plugins/${PLUGIN_ID}`, icon: PluginIcon, intlLabel: { id: `${PLUGIN_ID}.plugin.name`, @@ -198,4 +198,4 @@ const Sidebar = () => { }; export default Sidebar; -``` +``` \ No newline at end of file From 6802fa64b153aef2dec6f13e88e2b47adccdbbf0 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:15:03 +0100 Subject: [PATCH 62/64] Add small fixes for Store and access data guide --- .../guides/store-and-access-data.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/guides/store-and-access-data.md b/docusaurus/docs/cms/plugins-development/guides/store-and-access-data.md index 48593324be..b205fd1594 100644 --- a/docusaurus/docs/cms/plugins-development/guides/store-and-access-data.md +++ b/docusaurus/docs/cms/plugins-development/guides/store-and-access-data.md @@ -11,12 +11,8 @@ tags: - plugins development guides --- -import NotV5 from '/docs/snippets/_not-updated-to-v5.md' - # How to store and access data from a Strapi plugin - - To store data with a Strapi [plugin](/cms/plugins-development/developing-plugins), use a plugin content-type. Plugin content-types work exactly like other [content-types](/cms/backend-customization/models). Once the content-type is [created](#create-a-content-type-for-your-plugin), you can start [interacting with the data](#interact-with-data-from-the-plugin). ## Create a content-type for your plugin @@ -165,9 +161,9 @@ Once you have created a content-type for your plugin, you can create, read, upda A plugin can only interact with data from the `/server` folder. If you need to update data from the admin panel, please refer to the [passing data guide](/cms/plugins-development/guides/pass-data-from-server-to-admin). ::: -To create, read, update, and delete data, you can use either the [Entity Service API](/cms/api/entity-service) or the [Query Engine API](/cms/api/query-engine). While it's recommended to use the Entity Service API, especially if you need access to components or dynamic zones, the Query Engine API is useful if you need unrestricted access to the underlying database. +To create, read, update, and delete data, you can use either the [Document Service API](/cms/api/document-service) or the [Query Engine API](/cms/api/query-engine). While it's recommended to use the Document Service API, especially if you need access to components or dynamic zones, the Query Engine API is useful if you need unrestricted access to the underlying database. -Use the `plugin::your-plugin-slug.the-plugin-content-type-name` syntax for content-type identifiers in Entity Service and Query Engine API queries. +Use the `plugin::your-plugin-slug.the-plugin-content-type-name` syntax for content-type identifiers in Document Service and Query Engine API queries. **Example:** @@ -183,4 +179,4 @@ let data = await strapi.db.query('plugin::my-plugin.my-plugin-content-type').fin :::tip You can access the database via the `strapi` object which can be found in `middlewares`, `policies`, `controllers`, `services`, as well as from the `register`, `boostrap`, `destroy` lifecycle functions. -::: +::: \ No newline at end of file From 14e274cafcaad3ae9b1f8d9f72c74eafd890b1a9 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:15:31 +0100 Subject: [PATCH 63/64] Add small fix for Pass data from server to admin guide --- .../guides/pass-data-from-server-to-admin.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/guides/pass-data-from-server-to-admin.md b/docusaurus/docs/cms/plugins-development/guides/pass-data-from-server-to-admin.md index e5d4532979..9fb41b5a3b 100644 --- a/docusaurus/docs/cms/plugins-development/guides/pass-data-from-server-to-admin.md +++ b/docusaurus/docs/cms/plugins-development/guides/pass-data-from-server-to-admin.md @@ -99,15 +99,15 @@ For instance, within a React component, you could use `useEffect` to get the dat ```js title="/my-plugin/admin/src/components/MyComponent/index.js" import foobarRequests from "../../api/foobar"; -const [foobar, setFoobar] = useState([]); +const [foobar, setFoobar] = useState([]); // … useEffect(() => { foobarRequests.getFoobar().then(res => { - setSchemas(res.data); + setFoobar(res.data); }); }, [setFoobar]); // … ``` -This would set the `You are in the my-plugin-content-type controller!` text within the `foobar` data of the component's state. +This would set the `You are in the my-plugin-content-type controller!` text within the `foobar` data of the component's state. \ No newline at end of file From 8523a0b97a02ccc37d9cb5f35b419694e91c7437 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:16:11 +0100 Subject: [PATCH 64/64] Add small fixes for create components for plugins guides --- .../guides/create-components-for-plugins.md | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/docusaurus/docs/cms/plugins-development/guides/create-components-for-plugins.md b/docusaurus/docs/cms/plugins-development/guides/create-components-for-plugins.md index e570cb71fa..ca4c2edbcb 100644 --- a/docusaurus/docs/cms/plugins-development/guides/create-components-for-plugins.md +++ b/docusaurus/docs/cms/plugins-development/guides/create-components-for-plugins.md @@ -25,6 +25,7 @@ You can create components for your plugins in 2 different ways: using the Conten ### Using the Content-Type Builder The recommended way to create components for your plugin is through the Content-Type Builder in the admin panel. + The [Content-Type Builder documentation](/cms/features/content-type-builder#new-component) provides more details on this process. ### Creating components manually @@ -54,33 +55,27 @@ Components in Strapi follow the following format in their definition: } ``` -## Making components visible in the admin panel +## Component schema example -To ensure your plugin's components are visible in the admin panel, you need to set the appropriate `pluginOptions` in your component schema: +A component schema defines the structure of a reusable data fragment. Here is an example of a component schema for a plugin: -```javascript {9-16} +```json title="my-plugin/server/components/my-category/my-component.json" { - "kind": "collectionType", - "collectionName": "my_plugin_components", + "collectionName": "components_my_category_my_components", "info": { - "singularName": "my-plugin-component", - "pluralName": "my-plugin-components", - "displayName": "My Plugin Component" - }, - "pluginOptions": { - "content-manager": { - "visible": true - }, - "content-type-builder": { - "visible": true - } + "displayName": "My Component", + "icon": "align-justify" }, "attributes": { "name": { - "type": "string" + "type": "string", + "required": true + }, + "description": { + "type": "text" } } } ``` -This configuration ensures your components will be visible and editable in both the Content-Type Builder and Content Manager. +This configuration ensures your components will be available in both the Content-Type Builder and Content Manager when used in a content-type that has `pluginOptions` visibility enabled. \ No newline at end of file