v4 code migration: Updating content-type schemas
This guide is part of the v4 code migration guide designed to help you migrate the code of a Strapi application from v3.6.x to v4.0.x
Models in Strapi v4 have been completely overhauled: model files are located in /content-types/
folders, various keys and settings have been removed, and the relation syntax has completely changed.
Migrating to Strapi v4 requires:
Convert models to content-types
Strapi v3 declares models in <model-name>.settings.json
files found in a models
folder.
In Strapi v4, content-types are declared in schema.json
files found in ./src/api/<apiName>/content-types/<contentTypeName>
folder. The schema.json
files introduce some new properties (see schema documentation).
Content-types can be created automatically with the interactive CLI command strapi generate
.
To convert Strapi v3 models to v4 content-types:
Move the
./api
folder at the root of your project into./src
:mkdir src # Only if you haven't created the `./src` folder
mv api/ src/api/Move/rename each content-types
models
folder to./src/api/<apiName>/content-types/
:mv src/api/<apiName>/models/ src/api/<apiName>/content-types/
Strapi codemods can be used to convert v3 models to v4 content-types.
Move/rename each model's
<modelName>.settings.json
file to./src/api/<apiName>/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:./src/api/<apiName>/content-types/<contentTypeName>/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",
};
// ...
Updating content-type relations
Strapi v3 defines relations between content-types with the via
, model
and collection
properties in the model settings.
In Strapi v4, relations should be explicitly described in the schema.json
file of the content-types (see relations documentation).
If the content-type has relations, it's required to manually migrate them to Strapi v4, by updating the schema of the content-types.
To update content-type relations, update the ./src/api/<apiName>/content-types/<contentTypeName>/schema.json
file for each content-type with the following procedure:
Declare the relation explicitly by setting the
type
attribute value to"relation"
.Define the type of relation with the
relation
property.
The value should be a string among the following possible options:"oneToOne"
,"oneToMany"
,"manyToOne"
or"manyToMany"
.Define the content-type target with the
target
property.
The value should be a string following theapi::api-name.content-type-name
orplugin::plugin-name.content-type-name
syntax convention.(optional) In bidirectional relations, define
mappedBy
andinversedBy
properties on each content-type.
Example of all possible relations between an article and an author content-types:
// Attributes for the Article content-type
// oneWay relation
"articleHasOneAuthor": {
"type": "relation",
"relation": "oneToOne",
"target": "api::author.author"
},
// oneToOne relation
"articleHasAndBelongsToOneAuthor": {
"type": "relation",
"relation": "oneToOne",
"target": "api::author.author",
"inversedBy": "article"
},
// oneToMany relation
"articleBelongsToManyAuthors": {
"type": "relation",
"relation": "oneToMany",
"target": "api::author.author",
"mappedBy": "article"
},
// manyToOne relation
"authorHasManyArticles": {
"type": "relation",
"relation": "manyToOne",
"target": "api::author.author",
"inversedBy": "articles"
},
// manyToMany relation
"articlesHasAndBelongsToManyAuthors": {
"type": "relation",
"relation": "manyToMany",
"target": "api::author.author",
"inversedBy": "articles"
},
// manyWay relation
"articleHasManyAuthors": {
"type": "relation",
"relation": "oneToMany",
"target": "api::author.author"
}
// Attributes for the Author content-type
// inversed oneToMany relation
"article": {
"type": "relation",
"relation": "manyToOne",
"target": "api::article.article",
"inversedBy": "articleBelongsToManyAuthors"
},
// inversed manyToOne or manyToMany relation
"articles": {
"type": "relation",
"relation": "manyToMany",
"target": "api::article.article",
"inversedBy": "articlesHasAndBelongsToManyAuthors"
}
Updating lifecycle hooks
Strapi v3 declares model lifecycle hooks in <model-name>.js
files found in a models
folder.
In Strapi v4, lifecycle hooks are declared in a lifecycles.js
file found in ./src/api/<apiName>/content-types/<contentTypeName>/
folder. The lifecycles.js
file is similar in structure but no longer needs lifecycles to be wrapped in a lifecycles: {}
object, and new parameters are passed to the hooks (see lifecycle hooks documentation).
To convert Strapi v3 model lifecycle hooks to v4 lifecycle hooks:
Move/rename the
<modelName>.js
in./src/api/<apiName>/content-types/
to the proper content-type folder you created in step 3 of the content-type migration, while changing its name tolifecyles.js
:cd src/api/<apiName>
mv content-types/<modelName>.js content-types/<contentTypeName>/lifecycles.jsIn each
lifecycles.js
file, adjust the structure and move each lifecycle outside of the legacylifecycles: {}
object, like in the following examples:Example of a Strapi v3 lifecycles file:
module.exports = {
lifecycles: {
async beforeCreate() {
// ...
},
},
};Example of a Strapi v4 lifecycles file:
module.exports = {
async beforeCreate() {
// ...
},
};Refactor the model lifecycle hooks to use the new input variables (see hook
event
object documentation):
All Strapi v3
params
are placed in anevent
object in Strapi v4 (e.g.event.params
).Nested inside of this params object, you have access to
data
,select
(also known as fields),where
(also known as filters),orderBy
(also known as sort),limit
,offset
, andpopulate
.Optionally, for all
after*
events, you have access toevent.result
that contains the result response from the database.Example of a Strapi v3 lifecycle:
module.exports = {
lifecycles: {
async beforeCreate(data) {
data.isTableFull = data.numOfPeople === 4;
},
async afterCreate(result, data) {
// do something with result
}
},
};Example of a Strapi v4 lifecycle:
module.exports = {
beforeCreate(event) {
let { data, where, select, populate } = event.params;
data.isTableFull = data.numOfPeople === 4;
},
afterCreate(event) {
const { result, params } = event;
// do something to the result
},
};
Migrating the backend code of Strapi to v4 also requires to at least migrate the core features of the Strapi server, such as the configuration, dependencies, routes, controllers, and services.