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 - ./apifolder 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 - modelsfolder 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.jsonfile to- ./src/api/<apiName>/content-types/<contentTypeName>/schema.jsonfiles.
- In each - <contentTypeName>/schema.jsonfile, update the- infoobject, which now requires declaring the 3 new- singularName,- pluralNameand- displayNamekeys 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 - typeattribute value to- "relation".
- Define the type of relation with the - relationproperty.
 The value should be a string among the following possible options:- "oneToOne",- "oneToMany",- "manyToOne"or- "manyToMany".
- Define the content-type target with the - targetproperty.
 The value should be a string following the- api::api-name.content-type-nameor- plugin::plugin-name.content-type-namesyntax convention.
- (optional) In bidirectional relations, define - mappedByand- inversedByproperties 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>.jsin- ./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 to- lifecyles.js:- cd src/api/<apiName>
 mv content-types/<modelName>.js content-types/<contentTypeName>/lifecycles.js
- In each - lifecycles.jsfile, adjust the structure and move each lifecycle outside of the legacy- lifecycles: {}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 - eventobject documentation):
- All Strapi v3 - paramsare placed in an- eventobject 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, and- populate.
- Optionally, for all - after*events, you have access to- event.resultthat 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.