# v4 plugin migration: Migrating the front end
This guide is part of the v4 plugin migration guide designed to help you migrate a plugin from Strapi v3.6.x to v4.0.x.
Migrating the front end of a plugin to Strapi v4 might require:
- updating how the plugin's front-end is registered
- updating how the plugin is added to the amin panel menu
- updating how the plugin adds settings to the admin panel
- updating how the plugin adds reducers
- updating how the plugin injects components to the Content Manager
- updating how the plugin uses the admin panel
Initializer
component - registering translations
Migrating the front end of a plugin to Strapi v4 should be done entirely manually.
🤓 Going further with the new Admin Panel APIs
Following this guide should help you migrate a basic plugin with a single view. However, the Admin Panel APIs introduced in Strapi v4 allow for further customization.
In addition to the register()
lifecycle function, which is executed as soon as the plugin is loaded, a bootstrap()
lifecycle function executes after all plugins are loaded.
To add a settings link or section, use Redux reducers, hook into other plugins, and modify the user interface with injection zones, consult the "available actions" table for all available APIs and their associated lifecycle functions.
# Registering the plugin with the admin panel
🤓 v3/v4 comparison
A Strapi v3 plugin is registered with the admin panel by using the strapi.registerPlugin()
function in the <my-plugin-name>/admin/src/index.js
file.
In Strapi v4, the plugin is registered within the register()
lifecycle function.
To update the front-end registration of a plugin to Strapi v4:
If it does not already exist, create an
admin/src/index.js
file at the root of the plugin folder.In the
<plugin-name>/admin/src/index.js
file, export a function that calls theregister()
lifecycle function, passing the current Strapi application instance as an argument.Inside the
register()
lifecycle function body, call theregisterPlugin()
function on the application instance, grabbing thename
andid
keys from the Strapi v3 configuration object.Make sure that Strapi is aware of the plugin's front-end interface exported from
admin/src/index.js
by adding the following line to the<plugin-name>/strapi-admin.js
entry file:module.exports = require('./admin/src').default;
Example of a Strapi v4 plugin registration
// path: ./src/plugins/my-plugin/admin/src/index.js
import pluginId from './pluginId';
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const { name } = pluginPkg.strapi;
export default {
register(app) {
// executes as soon as the plugin is loaded
app.registerPlugin({
id: pluginId
name,
})
}
}
// path: .src/plugins/my-plugin/strapi-admin.js
module.exports = require('./admin/src').default;
# Adding a menu link
🤓 v3/v4 comparison
A Strapi v3 plugin adds a link to the menu in the admin panel by exporting a menu
object during the plugin registration.
In Strapi v4, a plugin adds a link to the menu programmatically with the addMenuLink()
function called in the register
lifecycle.
To migrate to Strapi v4, pass the menu
key from the Strapi v3 configuration object to app.addMenuLink()
with the following properties updated:
Property name and type in v3 | Equivalent in v4 |
---|---|
destination (String) | to (String) |
label (String) | intlLabel (String) |
icon (String)mainComponent (String) | <Icon /> (React component) |
The React-based icon component can be created in a separate file.
Example of an PluginIcon component
// path: ./src/plugins/my-plugin/admin/src/components/PluginIcon/index.js
import React from "react";
import { Icon } from "@strapi/parts/Icon";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const PluginIcon = () => (
<Icon as={() => <FontAwesomeIcon icon="paint-brush" />} width="16px" />
);
export default PluginIcon;
In Strapi v3 the icon component is specified on the mainComponent
key, in Strapi v4 the component is passed as a dynamic import to the app.addMenuLink()
function.
Example of adding a menu link with a custom plugin icon component
// path: ./src/plugins/my-plugin/admin/src/index.js
import pluginId from './pluginId';
import pluginPermissions from './permissions';
import PluginIcon from './PluginIcon'
const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
const { name } = pluginPkg.strapi;
export default {
register(app) {
app.addMenuLink({
to: `/plugins/${pluginId}`,
icon: PluginIcon,
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'My Plugin',
},
permissions: pluginPermissions.main,
Component: async () => {
const component = await import(/* webpackChunkName: "my-plugin-page" */ './pages/PluginPage');
return component;
},
});
app.registerPlugin({
description: pluginDescription,
id: pluginId
name
});
}
}
# Adding settings
🤓 v3/v4 comparison
A Strapi v3 plugin adds a settings section by exporting a settings
property during the plugin registration.
In Strapi v4, a plugin adds a settings section programmatically using the Settings API.
To migrate to Strapi v4, depending on what your Strapi v3 plugin does, use the following table to find the appopriate Settings API method to use, and click on the method name to go to its dedicated documentation:
Action | Method |
---|---|
Create a new settings section and define new links to include in this section | createSettingsSection() |
Add link(s) to an existing settings section:
|
Example of creating a new settings section
// path: ./src/plugins/my-plugin/admin/src/index.js
import getTrad from './utils/getTrad';
register(app) {
// Create the plugin's settings section
app.createSettingSection(
// created section
{
id: pluginId,
intlLabel: {
id: getTrad('Settings.section-label'),
defaultMessage: 'My plugin settings',
},
},
// links
[
{
intlLabel: {
id: 'settings.page',
defaultMessage: 'Setting page 1',
},
id: 'settings',
to: `/settings/my-plugin/`,
Component: async () => {
const component = await import(
/* webpackChunkName: "my-plugin-settings-page" */ './pages/Settings'
);
return component;
},
permissions: [],
},
]
);
app.registerPlugin({
id: pluginId,
name,
});
},
# Adding reducers
🤓 v3/v4 comparison
A Strapi v3 plugin adds reducers by exporting a reducers
property during the plugin registration.
In Strapi v4, a plugin adds reducers programmatically using the Reducers API.
To migrate to Strapi v4, make sure reducers are added programmatically with the addReducers()
method.
Example of adding reducers
// path: ./src/plugins/my-plugin/admin/src/index.js
import myReducer from './components/MyCompo/reducer';
import myReducer1 from './components/MyCompo1/reducer';
import pluginId from './pluginId';
const reducers = {
[`${pluginId}_reducer`]: myReducer,
[`${pluginId}_reducer1`]: myReducer1,
};
export default {
register(app) {
app.addReducers(reducers);
app.registerPlugin({
id: pluginId,
name,
});
},
}
}
# Adding injection zones
🤓 v3/v4 comparison
A Strapi v3 plugin can inject components into the Content Manager's Edit view, using the registerField()
method or the useStrapi
hook within the Initializer
component.
In Strapi v4, a plugin can inject components into several locations of the Content Manager using the Injection Zones API.
To migrate to Strapi v4, make sure components are injected using Strapi v4 Injection Zones API. Depending on where the component should be injected, use:
- either the
injectContentManagerComponent()
method to inject into predefined injection zones of the Content Manager - or the
injectComponent()
method to inject into custom zones
Example of injecting a component into the Content Manager's Edit view
// path: ./src/plugins/my-plugin/admin/src/index.js
import pluginId from './pluginId;
import Link from './components/Link'
export default {
bootstrap(app){
// insert a link in the 'right-links' zone of the Content Manager's edit view
app.injectContentManagerComponent('editView', 'right-links', {
name: `${pluginId}-link`,
Component: Link,
});
}
}
# Using the Initializer
component
🤓 v3/v4 comparison
In both Strapi v3 and v4, the Initializer
component is generated by default when a plugin is created. Initializer
could be used to execute some logic when the application is loading, for instance to dispatch an action after the user logs in into the admin panel.
In Strapi v4, the Initializer
component code has changed and uses the useRef
and useEffect
React hooks to dispatch actions.
The Initializer component is useful to store a global state in the application, which will be loaded before the application is rendered using Redux. To make sure the Initializer component is mounted in the application, set the isReady
key to false
when registering the plugin with registerPlugin()
.
To migrate to Strapi v4, make sure the plugin uses the latest Initializer
code, which can be copied and pasted from the following code example:
// path: ./src/plugins/my-plugin/admin/src/components/Initializer/index.js
import { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import pluginId from '../../pluginId';
const Initializer = ({ setPlugin }) => {
const ref = useRef();
ref.current = setPlugin;
useEffect(() => {
ref.current(pluginId);
}, []);
return null;
};
Initializer.propTypes = {
setPlugin: PropTypes.func.isRequired,
};
export default Initializer;
Example of registering a plugin that uses the new Initializer component
// path: ./src/-plugins/my-plugin/admin/src/index.js
export default {
register(app) {
app.registerPlugin({
id: pluginId,
initializer: Initializer,
isReady: false, // ensures the Initializer component is mounted in the application
name,
});
},
}
}
# Registering translations
In Strapi v4, the front-end plugin interface can export an asynchronous registerTrads()
function for registering translation files.
Example of translation registration
import { prefixPluginTranslations } from "@strapi/helper-plugin";
export default {
register(app) {
// register code...
},
bootstrap(app) {
// bootstrap code...
},
async registerTrads({ locales }) {
const importedTrads = await Promise.all(
locales.map((locale) => {
return import(
/* webpackChunkName: "[pluginId]-[request]" */ `./translations/${locale}.json`
)
.then(({ default: data }) => {
return {
data: prefixPluginTranslations(data, pluginId),
locale,
};
})
.catch(() => {
return {
data: {},
locale,
};
});
})
);
return Promise.resolve(importedTrads);
},
};