# Migration Guide

Upgrading your Strapi application to v3.0.0-beta.x.

To upgrade a project to the beta version of Strapi follow the instructions below.

# Updating global Strapi installation

If you have installed Strapi globally. You will need to upgrade its version:

npm install -g strapi@beta
# or
yarn global add strapi@beta

# Updating dependencies

First, let's clean your project's dependencies and update its package.json file.

# Clean your node_modules

Start by deleting the package-lock.json or yarn.lock file. Then remove all your current node_modules:

rm package-lock.json # OR rm yarn.lock
rm -rf node_modules

# Update your package.json

Start by upgrading all your strapi dependencies to the current beta.

To find the current beta version to use in package.json run:

npm view strapi@beta version
# 3.0.0-beta.x

# Dependencies

You must upgrade the versions and add strapi-admin as a dependency.

Before

{
  "dependencies": {
    //...
    "strapi": "3.0.0-alpha.26.2",
    "strapi-hook-bookshelf": "3.0.0-alpha.26.2",
    "strapi-hook-knex": "3.0.0-alpha.26.2",
    "strapi-utils": "3.0.0-alpha.26.2"
  }
}

After

{
  "dependencies": {
    //...
    "strapi": "3.0.0-beta.x",
    "strapi-admin": "3.0.0-beta.x",
    "strapi-hook-bookshelf": "3.0.0-beta.x",
    "strapi-hook-knex": "3.0.0-beta.x",
    "strapi-utils": "3.0.0-beta.x"
  }
}

Starting from this new release, plugins are now npm dependencies so, you need to install them as well.

Here are the default ones. If you have installed other plugins you will also need to add them here (e.g strapi-plugin-graphql).

{
  "dependencies": {
    //...
    "strapi-plugin-content-manager": "3.0.0-beta.x",
    "strapi-plugin-content-type-builder": "3.0.0-beta.x",
    "strapi-plugin-email": "3.0.0-beta.x",
    "strapi-plugin-upload": "3.0.0-beta.x",
    "strapi-plugin-users-permissions": "3.0.0-beta.x"
  }
}

# Scripts

We have completely refreshed the scripts of a project, so update them as follows:

Before

{
  "scripts": {
    "setup": "cd admin && npm run setup",
    "start": "node server.js",
    "strapi": "node_modules/strapi/bin/strapi.js",
    "postinstall": "node node_modules/strapi/lib/utils/post-install.js"
    //...
  }
}

After

{
  "scripts": {
    "develop": "strapi develop",
    "start": "strapi start",
    "build": "strapi build",
    "strapi": "strapi"
    //...
  }
}

️❗️ WARNING

To avoid confusions we have decided to give the same behavior to the npm run start and strapi start commands. To do so we had to introduce a new strapi develop command to run your project in watch mode.

# Strapi

We removed the need for the packageManager key under strapi.

Before

{
  "strapi": {
    //...
    "packageManager": "yarn"
  }
}

After

{
  "strapi": {
    //...
  }
}

# Installing your new dependencies

You can now run

npm install
# or
yarn

# Migrating server.js

As of now, strapi can start without a server.js file. You can now delete it.

If you need a server.js file to be able to run node server.js instead of npm run start then create a ./server.js file as follows:

const strapi = require('strapi');

strapi(/* {...} */).start();

# Migrating .gitignore

You need to add a new section to the .gitignore file.

############################
# Strapi
############################

exports
.cache
build

# Migrating config

# Remove the server.autoReload key

You need to remove the server.autoReload key in ./config/environments/**/server.json.

# Bootstrap function

The function exported in config/functions/bootstrap.js previously received a callback. This is not the case anymore. You can either use an async function, return a promise or simply run a synchronous function.

Before

module.exports = cb => {
  cb();
};

After

Async

module.exports = async () => {
  await someOperation();
};

Promise

module.exports = () => {
  return new Promise(/* ... */);
};

Sync

module.exports = () => {
  someSyncCode();
};

No Function

module.exports = () => {};

# Migrating plugins

One of our main objectives for the beta is to make it easier and quicker to upgrade to more recent versions of Strapi. This is why moving forward, plugins will be located in the node_modules folder.

Read more

Let's start by creating a new folder called ./extensions. This folder needs to exist even if it's empty. You may use a .gitkeep file to ensure the folder isn't deleted from the repository (if it's empty) when cloning. More details (opens new window).

# Migrating non customized plugin

If you installed a plugin but never modified any files inside ./plugins/pluginName/**/*, you can remove the ./plugins/pluginName folder. You may also remove the default installed plugins. This may mean that there are no plugins inside the ./plugins folder, so you can delete the ./plugins folder.

Note: If you have created a custom plugin leave the plugin in the ./plugins folder. Newly created custom plugins are placed in the ./plugins folder.

# Migrating customized plugin

If you have made some modifications to one of your plugins, you will have to do some manual migrations:

The main principle is to keep only the files you modified and move them to the ./extensions/pluginName folder.

Read the following instructions for more details.

# Config

When customizing configurations you only need to move the modified files.

Strapi merges the plugin's configurations with the ones in ./extensions/pluginName/config. Therefore, you should also only add the fields that you modified in your custom configurations files.

Before

./plugins/graphql/config/settings.json

{
  "endpoint": "/graphql", // default
  "tracing": false, // default
  "shadowCRUD": true, // default
  "playgroundAlways": false, // default
  "depthLimit": 2,
  "amountLimit": 25
}

After

./extensions/graphql/config/settings.json

{
  "depthLimit": 2,
  "amountLimit": 25
}

# Routes

If you modified ./plugins/pluginName/config/routes.json you will have to copy the file to ./extentions/pluginName/config/routes.json and only keep the routes you customized or added.

All the unchanged routes must be removed from this file.

Before

./plugins/users-permissions/config/routes.json

{
  "routes": [
    {
      "method": "GET",
      "path": "/",
      "handler": "UsersPermissions.customIndex",
      "config": {
        "policies": []
      }
    },
    {
      "method": "GET",
      "path": "/init",
      "handler": "UsersPermissions.init",
      "config": {
        "policies": [],
        "description": "Check if the first admin user has already been registered",
        "tag": {
          "plugin": "users-permissions",
          "name": "Role"
        }
      }
    }
    //...
  ]
}

After

./extensions/users-permissions/config/routes.json

{
  "routes": [
    {
      "method": "GET",
      "path": "/",
      "handler": "UsersPermissions.customIndex",
      "config": {
        "policies": []
      }
    }
  ]
}

# Controllers & Services

To migrate controllers and services you must move your customized controllers and services to the ./extensions/pluginName/**/*, delete all the non customized ones and keep only the methods you modified in your customized files·

For example, if you have a customIndex action in the User controller you only have to create the ./extensions/users-permissions/controllers/User.js file and keep your customIndex action in it. You can delete the rest of the files and methods.

Before

./plugins/users-permissions/controllers/User.js

module.exports = {
  customIndex: async ctx => {},

  find: async (ctx, next, { populate } = {}) => {
    //...
  },

  me: async ctx => {
    //...
  },

  //...
};

After

./extensions/users-permissions/controllers/User.js

module.exports = {
  customIndex: async ctx => {},
};

The same goes for services

# Models

If you have modified (or created relations) with a plugin's model, you will have to move your Model file to ./extensions/pluginName/models/Model.settings.json

Here you need to keep the entire model. It will replace the one in the plugin rather than being merged with it.

Before

./plugins/users-permissions/models/User.settings.json

{
  "connection": "default",
  "collectionName": "users-permissions_user",
  "info": {
    "name": "user",
    "description": ""
  },
  "attributes": {
    "username": {
      "type": "string",
      "minLength": 3,
      "unique": true,
      "configurable": false,
      "required": true
    },
    "email": {
      "type": "email",
      "minLength": 6,
      "configurable": false,
      "required": true
    },
    "provider": {
      "type": "string",
      "configurable": false
    },
    "password": {
      "type": "password",
      "minLength": 6,
      "configurable": false,
      "private": true
    },
    "resetPasswordToken": {
      "type": "string",
      "configurable": false,
      "private": true
    },
    "confirmed": {
      "type": "boolean",
      "default": false,
      "configurable": false
    },
    "blocked": {
      "type": "boolean",
      "default": false,
      "configurable": false
    },
    "role": {
      "model": "role",
      "via": "users",
      "plugin": "users-permissions",
      "configurable": false
    },
    "products": {
      "collection": "product",
      "via": "owner"
    }
  }
}

After

./extensions/users-permissions/models/User.settings.json

{
  "connection": "default",
  "collectionName": "users-permissions_user",
  "info": {
    "name": "user",
    "description": ""
  },
  "attributes": {
    "username": {
      "type": "string",
      "minLength": 3,
      "unique": true,
      "configurable": false,
      "required": true
    },
    "email": {
      "type": "email",
      "minLength": 6,
      "configurable": false,
      "required": true
    },
    "provider": {
      "type": "string",
      "configurable": false
    },
    "password": {
      "type": "password",
      "minLength": 6,
      "configurable": false,
      "private": true
    },
    "resetPasswordToken": {
      "type": "string",
      "configurable": false,
      "private": true
    },
    "confirmed": {
      "type": "boolean",
      "default": false,
      "configurable": false
    },
    "blocked": {
      "type": "boolean",
      "default": false,
      "configurable": false
    },
    "role": {
      "model": "role",
      "via": "users",
      "plugin": "users-permissions",
      "configurable": false
    },
    "products": {
      "collection": "product",
      "via": "owner"
    }
  }
}

# Admin

For now you won't be able to customize a plugin's admin. This feature will come soon so keep an eye out for this feature.

# Migrating local plugins

If you have local plugins (plugins in the ./plugins that don't exist on npm) you can leave them in the plugins folder.

The only difference is that the admin of a local plugin is ignored for the moment.

# Migrating api

# Migrating controllers and services

In the beta, we are introducing the Core API, which is replacing the templates that were generated before.

Now when you create a new model your controller and service will be empty modules and will be used to override the default behaviors. Read more about controllers or services

To migrate, you will only have to delete the methods you haven't modified or created from your controllers and services

Before

./api/product/controllers/Product.js

module.exports = {
  find: async (ctx) => {
	  myCustomImplementation(ctx);
  },

  customAction: async(ctx) => {
    ctx.send({ ok: true });
  }

  findOne: async (ctx) => {
    return strapi.services.product.fetch(ctx.params);
  },

  count: async (ctx) => {
    return strapi.services.product.count(ctx.query);
  },

  create: async (ctx) => {
    return strapi.services.product.add(ctx.request.body);
  },

  update: async (ctx) => {
    return strapi.services.product.edit(ctx.params, ctx.request.body) ;
  },

  destroy: async (ctx) => {
    return strapi.services.product.remove(ctx.params);
  }
};

After

./api/product/controllers/Product.js

module.exports = {
  // this function overrides the default one
  find: async ctx => {
    myCustomImplementation(ctx);
  },

  // this one is added to the default controller
  customAction: async ctx => {
    ctx.send({ ok: true });
  },
};

The same goes for services

For custom controllers and services (the ones without a model) you can leave them untouched.

# Migrating routes

In the file ./api/apiName/config/routes.json we renamed the destroy action to delete you will have to update your routes and controller accordingly.

If you haven't customized the destroy action, then remove it from your controller rather than renaming it.

Before

./api/product/config/routes.json

{
  "routes": [
    //...
    {
      "method": "DELETE",
      "path": "/products/:id",
      "handler": "Product.destroy",
      "config": {
        "policies": []
      }
    }
  ]
}

./api/product/controllers/Product.js

module.exports = {
  find: async ctx => {
    return strapi.services.product.fetchAll(ctx.query);
  },

  findOne: async ctx => {
    return strapi.services.product.fetch(ctx.params);
  },

  count: async ctx => {
    return strapi.services.product.count(ctx.query);
  },

  create: async ctx => {
    return strapi.services.product.add(ctx.request.body);
  },

  update: async ctx => {
    return strapi.services.product.edit(ctx.params, ctx.request.body);
  },

  destroy: async ctx => {
    customImplementation(ctx);
  },
};

After

./api/product/config/routes.json

{
  "routes": [
    //...
    {
      "method": "DELETE",
      "path": "/products/:id",
      "handler": "Product.delete",
      "config": {
        "policies": []
      }
    }
  ]
}

./api/product/controllers/Product.js

module.exports = {
  delete: async ctx => {
    customImplementation(ctx);
  },
};

# Migrating admin

Numerous changes have been made to the admin with the release of beta:

  • If you have not customized anything in ./admin folder, then simply delete the ./admin folder and it's contents.
  • If you have customized any part of the ./admin folder, then keep only those modified files, locate their new location in the directory structure (source code (opens new window)), and then move the files to their new locations.

Customizing the admin is as simple as creating a file in the ./admin folder of your app. You need to make sure the file you want to customize is at the same location in your ./admin folder as it is in the strapi-admin package. For a reference you can look at the source code (opens new window).

# Example

You can modify the logo of the app by creating a file ./admin/src/assets/images/logo-strapi.png to replace the admin logo.

You can do the same with any file found in the source code (opens new window) of the admin panel

# Running your migrated project

To run your migrated project you will now need to run strapi develop or npm run develop to run the project in watch mode (e.g auto reloading on content-type creation).

If you haven't run strapi develop or npm run develop (as above) and would like to run strapi without watch mode then you need to first run strapi build or npm run build as a first step, and then run strapi start or npm run start.

Finally, if you want to run your project in different environments use NODE_ENV=env npm run start, eg. NODE_ENV=production npm run start or NODE_ENV=development npm run start.

# Migrating your database

The beta introduces a new Administrator model created solely to allow user access to the Strapi administration panel (at this time this model is not editable). In this way, the Administrator model replaces the previous User model from the users-permissions plugin.

With this new model, you now have a clear distinction between the people that are allowed to access the administration panel, and the users of the application you built with Strapi.

More practically, it means that a new strapi_administrator collection will be created automatically in your database. On startup, the strapi_administrator table is empty, therefore when migrating from alpha to beta, you may either create a new administrator user with the registration page OR you may manually migrate your previous users with administrator role.

# Cleaning up the users-permissions.users collection

If you only used the administrator role to give access to the admin panel to certain users, and never used this role for your application business logic, you can delete the role from within the admin panel.

If you haven't created any relation with the User model in your Content Types and don't use those users in your application business logic; you can remove every user you have migrated to the strapi_administrator collection.

Finally, if you have chosen to migrate your previous admin users in the new strapi_administrator collection but your User model has at least one relation with another model, then you may need to keep both the strapi_administrator and users-pemrissions_user collection manually in sync.

Example: Some of your application users can edit their profile and access the admin panel. If they change their email you need to make sure their administrator entity also changes email.

️❗️ WARNING

We really recommend separating you users from your administrators to avoid this situation which is not a good practice.

# Updating Deployments

# Building your admin panel

If you are deploying your Strapi project with the Admin panel together, you will have to make sure the build script runs before running the project. (e.g npm run build)

Depending on your deployment environment you might have to run the build script with NODE_ENV=production npm run build if the environment doesn't include it by default.

For example, Heroku will run the build script for you and set NODE_ENV=production before running the scripts so you won't have to think about it.

# Running the project

Previously, you could run your project by running node server.js. The beta version removes the server.js file, and so you will have to either run npm run start or manually create a server.js file (read more here)

# PM2

if you are using pm2 to run your application in production you can update your script like so

Before

NODE_ENV=production pm2 start server.js

After

NODE_ENV=production pm2 start npm -- start

If you are using an ecosystem.config.js you can do the following:

Before

module.exports = {
  apps: [
    {
      name: 'your-app-name',
      script: './path-to/your-strapi-app/server.js',
      env: {
        NODE_ENV: 'production',
      },
    },
  ],
};

After

module.exports = {
  apps: [
    {
      name: 'your-app-name',
      cwd: './path-to/your-strapi-app',
      script: 'npm',
      args: 'start',
      env: {
        NODE_ENV: 'production',
      },
    },
  ],
};

# Conclusion

The beta release of Strapi marks an important milestone in the long-term development of the project. Going forward the Strapi Community, their users and clients can continue to use and deploy Strapi with even greater confidence. Thank you for all the contributions that made this beta release possible.