# v4 plugin migration: Updating the folder structure
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.
🤓 v3/v4 comparison
Strapi v3 plugins required a specific folder structure.
In Strapi v4, plugins are developed using a programmatic API, which gives flexibility in the folder structure.
The folder structure of a Strapi v4 plugin should meet the following requirements:
The root of the plugin folder should include:
- a
strapi-server.js
entry file, if the plugin interacts with Strapi's back-end (see Server API) - a
strapi-admin.js
entry file, if the plugin interacts with Strapi's admin panel (see Admin Panel API).
- a
strapi-admin.js
andstrapi-server.js
should export the plugin's interface.
As long as these requirements are met, the rest of the folder structure is up to you.
Example of a Strapi v4 plugin structure
my-plugin
├─ admin
│ └─ src
│ ├─ components
│ ├─ pages
│ ├─ // more folders and files
│ └─ index.js
├─ server
│ ├─ config
│ ├─ content-types
│ ├─ controllers
│ ├─ middlewares
│ ├─ policies
│ ├─ routes
│ ├─ services
│ ├─ bootstrap.js
│ ├─ destroy.js
│ ├─ register.js
│ ├─ // more folders and files
│ └─ index.js
├─ strapi-admin.js // require('./admin')
└─ strapi-server.js // require('./server')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
The folder structure of a Strapi v3 plugin can be migrated to a v4 plugin either automatically or manually.
# Updating folder structure automatically
✋ CAUTION
The codemod creates a new Strapi v4 plugin, leaving the Strapi v3 plugin in place. We recommend confirming the v4 version of the plugin is working properly before deleting the v3 version.
A codemod can be used to automatically update the folder structure of a plugin for Strapi v4. The codemod performs the following actions:
- creation of 2 entry files:
strapi-server.js
andstrapi-admin.js
, - organization of files and folders into a
server
and anadmin
folders, respectively, - conversion of
models
tocontentTypes
, - and export of
services
as functions.
To execute the codemod, run the following commands in a terminal:
npx @strapi/codemods migrate:plugin <path-to-v3-plugin> [path-for-v4-plugin]
# Updating folder structure manually
Manually updating the folder structure requires moving and updating the content of multiple files and folders. These steps are described in the following subsections.
✏️ NOTE
The folder structure is given as an example, and files and folders can be organized freely as long as strapi-server.js
or strapi-admin.js
exist and export the plugin interface.
# Creating a server
folder
The server
folder includes all the code for the back end of the plugin. To create it at the root of the plugin folder, run the following command in a terminal:
cd <my-plugin-folder-name>
mkdir server
2
# Moving controllers, services, and middlewares
🤓 v3/v4 comparison
In Strapi v3, controllers, services, and middlewares of a plugin must follow a strict folder structure convention.
In Strapi v4, the organization of files and folders for plugins is flexible. However, it is recommended to create dedicated folders for every type of back-end element (e.g. controllers, services, and middlewares) inside a server
folder (see project structure).
To update the controllers, services, and middlewares of a plugin to Strapi v4, create specific sub-folders in a server
folder.
Plugin files and folders in Strapi v4 should meet 2 requirements:
- Each file in the
server/<subfolder-name>/<element-name>
(e.g.server/controllers/my-controller.js
) should:- export a function taking the
strapi
instance (object) as a parameter - and return an object.
- export a function taking the
- Each of the
server/<subfolder-name>
folders should include anindex.js
file that exports all files in the folder.
Example of files and folder for Strapi v4 plugin controllers
// path: ./src/plugins/my-plugin/server/controllers/my-controllerA.js
module.exports = ({ strapi }) => ({
doSomething(ctx) {
ctx.body = { message: "HelloWorld" };
},
});
2
3
4
5
6
7
// path: ./src/plugins/my-plugin/server/controllers/index.js
"use strict";
const myControllerA = require("./my-controllerA");
const myControllerB = require("./my-controllerB");
module.exports = {
myControllerA,
myControllerB,
};
2
3
4
5
6
7
8
9
10
11
12
# Moving the bootstrap
function
🤓 v3/v4 comparison
Strapi v3 has a dedicated config/functions
folder for each plugin.
In Strapi v4, the config
folder does not necessarily exist for a plugin and the bootstrap
function and other lifecycle functions can be declared elsewhere.
To update the plugin's bootstrap
function to Strapi v4:
- move the
bootstrap()
function fromserver/config/functions/bootstrap.js
toserver/bootstrap.js
- pass the
strapi
instance (object) as a parameter
// path: ./src/plugins/my-plugin/server/bootstrap.js
"use strict";
module.exports = ({ strapi }) => ({
// bootstrap the plugin
});
2
3
4
5
6
7
# Moving routes
🤓 v3/v4 comparison
Strapi v3 declares routes for a plugin in a specific config/routes.json
file.
In Strapi v4, the config
folder does not necessarily exist for a plugin and plugin routes can be declared in a server/routes/index.json
file.
To update plugin routes to Strapi v4, move routes from config/routes.json
to server/routes/index.json
.
Routes in Strapi v4 should meet 2 requirements:
- Routes should return an array or an object specifying
admin
orcontent-api
routes. - Routes handler names should match the same casing as the controller exports.
Example of controllers export and routes in a Strapi v4 plugin
// path: ./src/plugins/my-plugin/server/controllers/index.js
"use strict";
const myControllerA = require("./my-controllerA");
const myControllerB = require("./my-controllerB");
module.exports = {
myControllerA,
myControllerB,
};
2
3
4
5
6
7
8
9
10
11
// path: ./src/plugins/my-plugin/server/routes/index.js
module.exports = [
{
method: "GET",
path: "/my-controller-a",
// Camel case handler to match export in server/controllers/index.js
handler: "myControllerA.doSomething",
config: { policies: [] },
},
];
2
3
4
5
6
7
8
9
10
11
# Moving policies
🤓 v3/v4 comparison
Strapi v3 declares policies for a plugin in a specific config/policies
folder.
In Strapi v4, the config
folder does not necessarily exist for a plugin and plugin policies can be declared in dedicated files found under server/policies
.
To update plugin policies to Strapi v4:
Move policies from
config/policies
toserver/policies/<policy-name>.js
Add an
index.js
file to theserver/policies
folder and make sure it exports all files in the folder.
# Converting models to content-types
🤓 v3/v4 comparison
Strapi v3 declares plugin models in <model-name>.settings.json
files found in a models
folder.
In Strapi v4, plugin content-types are declared in schema.json
files found in a server/content-types/<contentTypeName>
folder. The schema.json
files introduce some new properties (see schema documentation).
To convert Strapi v3 models to v4 content-types:
Move the
models
folder under theserver
folder and renamemodels
tocontent-types
:mv models/ server/content-types/
1Move/rename each model's
<modelName>.settings.json
file toserver/content-types/<contentTypeName>/schema.json
files.In each
<contentTypeName>/schema.json
file, update theinfo
object, which now requires declaring the 3 newsingularName
,pluralName
anddisplayName
keys and respecting some case-formatting conventions:// path: ./src/plugins/my-plugin/content-types/<content-type-name>/schema.json // ... "info": { "singularName": "content-type-name", // kebab-case required "pluralName": "content-type-names", // kebab-case required "displayName": "Content-type name", "name": "Content-type name", }; // ...
1
2
3
4
5
6
7
8
9
10(optional) If the Strapi v3 model uses lifecycle hooks found in
<model-name>.js
, move/rename the file toserver/content-types/<contentTypeName>/lifecycle.js
, otherwise delete the file.Create an
index.js
file for each content-type to export the schema and, optionally, lifecycle hooks:// path: ./src/plugins/my-plugin/server/content-types/<content-type-name>/index.js const schema = require("./schema.json"); const lifecycles = require("./lifecycles.js"); // optional module.exports = { schema, lifecycles, // optional };
1
2
3
4
5
6
7
8
9Create a
server/content-types/index.js
file.In
server/content-types/index.js
, export all content-types and make sure the key of each content-type matches thesingularName
found in theinfo
object of the content-type’sschema.json
file:// path: ./src/plugins/my-plugin/server/content-types/content-type-a/schema.json "info": { "singularName": "content-type-a", // kebab-case required "pluralName": "content-type-as", // kebab-case required "displayName": "Content-Type A", "name": "Content-Type A", };
1
2
3
4
5
6
7
8// path: ./src/plugins/my-plugin/server/content-types/index.js "use strict"; const contentTypeA = require("./content-type-a"); const contentTypeB = require("./content-type-b"); module.exports = { // Key names should match info.singularName key values found in corresponding schema.json files "content-type-a": contentTypeA, "content-type-b": contentTypeB, };
1
2
3
4
5
6
7
8
9
10
11
12
✏️ NOTE
Converting Strapi v3 models to v4 content-types also requires updating getters and, optionally, relations (see plugin back end migration documentation).
# Creating entry files
🤓 v3/v4 comparison
Strapi v3 plugins use a strict folder structure convention.
In Strapi v4, the folder structure for plugins is flexible. However, each plugin needs at least a strapi-server.js
entry file or a strapi-admin.js
entry file. The 2 entry files are used to take advantage of, respectively, the Server API for the back end of the plugin and the Admin Panel API for the front end of the plugin.
To update the plugin to Strapi v4:
If the plugin interacts with Strapi's backend, create the
strapi-server.js
back-end entry file at the root of the plugin folder. The file should require all necessary files and export the back-end plugin interface (see migrating the back end of a plugin).Example strapi-server.js and server/index.js entry files
// path: ./src/plugins/my-plugin/strapi-server.js "use strict"; module.exports = require('./server');
1
2
3
4
5// path: ./src/plugins/my-plugin/server/index.js: "use strict"; const register = require('./register'); const bootstrap = require('./bootstrap'); const destroy = require('./destroy'); const config = require('./config'); const contentTypes = require('./content-types'); const controllers = require('./controllers'); const routes = require('./routes'); const middlewares = require('./middlewares'); const policies = require('./policies'); const services = require('./services'); module.exports = { register, bootstrap, destroy, config, controllers, routes, services, contentTypes, policies, middlewares, };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27If the plugin interacts with Strapi's admin panel, create the
strapi-admin.js
front-end entry file at the root of the plugin folder. The file should require all necessary files and export the front-end plugin interface (see migrating the front end of a plugin).Example strapi-admin.js entry file
// path: ./src/plugins/my-plugin/strapi-admin.js "use strict"; module.exports = require("./admin/src").default;
1
2
3
4
5