Update Strapi Beta to Strapi Stable Community Edition

June 15th 2020

At Hash Interactive we’ve been using Strapi since the alpha release years ago. The Headless CMS has made a lot of changes throughout the versions from alpha to beta and now from beta to the stable community release. We have had to do a lot of upgrades and migrations for our Strapi projects and compiled a list of migration steps from an older version of Strapi to the community edition.

The migration guides recommend making the updates in the current working project directory but we have found more success starting with a new strapi project and migrating our custom changes to the new stable project version.

In this tutorial we’ll walk through:

Create a new Mongo Database and Migrating the Mongo DB Data

In order to update to a new strapi version we’re going to copy over our Mongo Database to a new database installation which will create a database backup and a new database that we can manipulate if needed. First, we’ll use the mongodump utility to dump the current database to an archive file:

mongodump --username <username> --db <database> --host <host> --port <port> --authenticationDatabase <authentication_database> --archive="<archive_file_name>"

We are dumping our database to an archive file. Now we can create a new database for our stable strapi project. We can login to our mongo shell and we’ll run:

use stable

Now we’ll create a database user:

db.createUser({ user: "YOUR_USER", roles: [{ "role": "dbOwner", "db": "stable" }], pwd: "YOUR_PASSWORD" })

We’re creating a mongo database called stable and we’re defining a dbOwner user on our database. Now we can import our database dump archive file by running:

mongorestore --username <username> --authenticationDatabase stable --archive="<archive_file_name>" --nsFrom="<old_databse_name>.*" --nsTo="stable.*"

Include additional options as necessary, such as to specify the uri or host, username, password and authentication database.

Now we should have a new database with all of our old data migrated over to it. We can now create a new Strapi project with our database definition.

Create a new Strapi Project and Moving API Files

We can use the yarn create utility to create a new strapi project with the most updated 3.x stable release:

yarn create strapi-app strapi-stable

We can select the manual configuration option and we’ll select the mongo database option. When prompt we can input our new mongo database credentials we just created. After we finished install we can review the database configuration file found at strapi-stable/config/database.js

module.exports = ({ env }) => ({
  defaultConnection: 'default',
  connections: {
    default: {
      connector: 'mongoose',
      settings: {
        host: env('DATABASE_HOST', '127.0.0.1'),
        srv: env.bool('DATABASE_SRV', false),
        port: env.int('DATABASE_PORT', 27017),
        database: env('DATABASE_NAME', 'stable'),
        username: env('DATABASE_USERNAME', 'YOUR_USERNAME'),
        password: env('DATABASE_PASSWORD', 'YOUR_PASSWORD'),
      },
      options: {
        authenticationDatabase: env('AUTHENTICATION_DATABASE', 'stable'),
        ssl: env.bool('DATABASE_SSL', false),
      },
    },
  },
});

Updating Environment Configuration

As you can see the new strapi version is using environment variables to define the database options and if those are not defined it falls back to the options that we input when configuring the project. Let’s create a .env file and define these values so we can use this strategy in our production or staging environment. Create a file in the root called .env

DATABASE_HOST=127.0.0.1
DATABASE_SRV=false
DATABASE_PORT=27017
DATABASE_NAME=stable
DATABASE_USERNAME=<username>
DATABASE_PASSWORD=<password>
AUTHENTICATION_DATABASE=stable
DATABASE_SSL=false

For each deploy or environment you create you can define a new .env file and the configuration will be picked up for you.

Copying API Files Over to Strapi Stable Project

We can copy all of our API files from the old strapi project to our strapi-stable folder:

cp -v old/api/* strapi-stable/api/

Now that all of our strapi api files are copied over we can make the required changes to our Models JSON definition.

Updating Model Fields: Changing Strapi Model Field Types

Strapi has changed some field types in the new stable release. If we were previously using the WYSIWYG field we will need to update each Model in our ./api/**/<Model>.settings.json file to account for the changes.

WYSIWYG fields from text to richtext

To make sure a Wysiwyg field stays the same when deploying, we introduced the richtext type. This type is equivalent to the previous text type with wysiwyg option enabled.

Before:

{
  //...
  "attributes": {
    "name": {
      "type": "string"
    }
    "description": {
      "type": "text"
    }
  }
}

After:

{
  //...
  "attributes": {
    "name": {
      "type": "string"
    }
    "description": {
      "type": "richtext"
    }
  }
}

Likewise, Strapi has made date type changes.

We have introduced new types in the admin panel: datedatetime and time. Previously all of those types where saved as datetime.

You will need to change the type of your fields from date to datetime if you don’t want to migrate your data.

https://strapi.io/documentation/v3.x/migration-guide/migration-guide-beta.17-to-beta.18.html#date-type-changes

Before:

{
  //...
  "attributes": {
    "name": {
      "type": "string"
    }
    "published": {
      "type": "date"
    }
  }
}

After:

{
  //...
  "attributes": {
    "name": {
      "type": "string"
    }
    "published": {
      "type": "datetime"
    }
  }
}

Now that we have our field types corrected we can change our routes configuration for our api endpoints.

Updating Routes: Update API Routes Params

In order to stay database agnostic, strapi decided that the identifier in url params should be named id instead of _id.

If your routes configuration still uses something else than id, you will need to modify all of them as in the following example:

Before: ./api/**/config/routes.json

{
  "method": "PUT",
  "path": "/assets/:_id",
  "handler": "Asset.update",
  "config": {
    "policies": []
  }
}

After: ./api/**/config/routes.json

{
  "method": "PUT",
  "path": "/assets/:id",
  "handler": "Asset.update",
  "config": {
    "policies": []
  }
}

Updating Media File Relationship in MongoDB

In the media library features, We wanted to make sure media would keep their ordering. To implement this in mongo we had to change the way the media relation was built.

Previously, the upload_file collection was the one keeping track of the relations and the entity related to the file had not reference to it. Implementing ordering without changes the relations proved unfeasible. Finally we decided to add the reverse reference in the entities so it would make accessing the files really easy. You will hence need to migrate your mongo database to avoid losing references to your files.

https://strapi.io/documentation/v3.x/migration-guide/migration-guide-beta.19-to-beta.20.html#mongodb-media-relation-changes

If you were using MongoDB and had relationships between Content Types and the Media Library upload_file we will need to generate some scripts to migrate the relationship.

First create a export.js file at the root of your project with the following content:

const fs = require('fs');

require('strapi')()
  .load()
  .then(() => {
    const models = {};

    Object.keys(strapi.api).forEach(apiName => {
      Object.values(strapi.api[apiName].models || {}).forEach(model => {
        models[model.globalId] = formatModel(model);
      });
    });

    Object.keys(strapi.plugins).forEach(pluginName => {
      Object.values(strapi.plugins[pluginName].models || {}).forEach(model => {
        models[model.globalId] = formatModel(model);
      });
    });

    Object.values(strapi.components).forEach(model => {
      models[model.globalId] = formatModel(model);
    });

    fs.writeFileSync('models.json', JSON.stringify(models, null, 2));
    process.exit(0);
  });

function formatModel(model) {
  return {
    collection: model.collectionName,
    files: Object.keys(model.attributes).reduce((acc, key) => {
      const attr = model.attributes[key];
      if (attr.model === 'file' && attr.plugin === 'upload') {
        acc[key] = 'single';
      }

      if (attr.collection === 'file' && attr.plugin === 'upload') {
        acc[key] = 'multiple';
      }
      return acc;
    }, {}),
  };
}

Then we can run the script from the root of your project:

node export.js

This will create a models.json file at the root of our project directory. We will then need to create a script (outside of the project root) that is called migration.js in which we will load into our mongo database. Inside migration.js we can populate the script:

var models = {
  // copy models.json file
}

for (var i in models) {
  var model = models[i];
  var update = {};
  var keyCount = 0;

  for (var key in model.files) {
    keyCount += 1;
    update[key] = '';
  }

  if (keyCount > 0) {
    db.getCollection(model.collection).update({}, { $unset: update }, { multi: true });
  }
}

var fileCursor = db.getCollection('upload_file').find({});

while (fileCursor.hasNext()) {
  var el = fileCursor.next();
  el.related.forEach(function(fileRef) {
    var model = models[fileRef.kind];

    if (!model) {
      return;
    }

    var fieldType = model.files && model.files[fileRef.field];

    // stop if the file points to a field the user didn't specify
    if (!fieldType) {
      return;
    }

    if (fieldType === 'single') {
      db.getCollection(model.collection).updateOne(
        { _id: fileRef.ref },
        { $set: { [fileRef.field]: el._id } }
      );
    } else if (fieldType === 'multiple') {
      db.getCollection(model.collection).updateOne(
        { _id: fileRef.ref },
        { $push: { [fileRef.field]: el._id } }
      );
    }
  });
}

We will need to copy the output from models.json file to be equal to our models variable in our migration.js script such as:

var models = {
  ModelName: {
    collection: 'collectionName',
    files: {
      image: 'single',
      images: 'multiple',
    },
  },
};

//... rest of the migration.js script

Now with our migration.js script defined we can log into our Mongo DB shell and load the script. First, make sure we open a terminal and cd to the working directory where our migration.js file is located. Then we can run the command:

mongo
> use stable
> ls()
> load("./migration.js")

Once our migration script is done loading we can delete the files export.js and models.json in our root directory.

Moving Public Files to new Stable Strapi Project

Lastly, we’ll need to migrate the files in our public/uploads directory to our new project. We can easily run the command:

cp old/public/uploads/* strapi-stable/public/uploads/

This will copy all of our Media Files from our old project into our stable strapi project.

Rebuilding the Administration Panel

Before deploying your Strapi application in production we’ll need to rebuild our administration panel.

You can run yarn build --clean to rebuild your admin panel with the newly installed version of strapi.

Finally restart your server: yarn develop in development mode or yarn start in production mode.

Hopefully these are some tips to help you in your migration from Strapi Beta version to the Stable release edition of Strapi. Every migration will be a little different in it’s requirements but these are some common issues we have previously ran into during migrations.