Managing Contentful Migration Scripts with Multiple Environments

Caesar Bell
5 min readMay 6, 2020

The Problem

If you are building a website and using Contentful, then you might have experienced the benefits with using a headless CMS that also has robust API functionality. Using Contentful allows developers to build out a Content Type structure which allows content owner to easily create reusable content that can be added to different site pages.

Contentful recommends using the Contentful Migration Tool along with the Contentful CLI as best practice when your project is using more than one environment in your Contentful Space (i.e. when your Contentful Space has a development and a master environment). However, as great as the Contentful Migration Tool is, my team and I became easily frustrated as the project grew and multiple change request came our way. Lets say a migration script was created and successfully ran on both environments, now you have on your development and master environment (in your Contentful space) a Content Type named Article. Article has the following fields; Title, Author and Date Published.

Let’s say their is a change request to update the field name Title to Article Title along with adding a new field called Ratings. When using the Contentful Migration Tool you would not be able to use the same migration script that was used to create the Article Content Type without doing some modifications to it. You would have to, first, replace createContentType with editContentType methods. Second, remove the fields that are applicable and third, add the new field. See example below.

//Script for creating the Article Content Typemodule.exports = function(migration) {
const article = migration.createContentType('article', {
name: 'Article',
description: 'Some description goes here...',
displayField: 'title'
})
article.createField('title', {
name: "Title",
type: "Symbol",
required: true,
localized: false,
disabled: false,
omitted: false,
deleted: false
})
article.createField('author', {
name: "Author",
type: "Symbol",
required: true,
localized: false,
disabled: false,
omitted: false,
deleted: false
})
article.createField('datePublished', {
name: "Date Published",
type: "Date",
required: true,
localized: false,
disabled: false,
omitted: false,
deleted: false
})
}
//Script for updating the Article Content Typemodule.exports = function(migration) { const article = migration.editContentType('article', {
name: 'Article',
description: 'Some description goes here...',
displayField: 'articleTitle'
})
// Notice, changing the field's name is simpler than changing the field's id. article.editField('title', {
name: "Article Title",
type: "Symbol",
required: true,
localized: false,
disabled: false,
omitted: false,
deleted: false
});
// Adding the new field article.createField('ratings', {
name: "Ratings",
type: "Integer",
required: true,
localized: false,
disabled: false,
omitted: false,
deleted: false
});
}

Diving Deeper

With the example above, notice that you will have to call createContentType instead of editContentType and editField instead of createField to successfully update the field with new options. If these changes are not made, the script will not run successfully and you will get an error from Contentful Migration Script stating that you cannot create a field or content type that already exist. A few pain points to recap here are; the scripts are not completely reusable and the scripts are not DRY.

The Solution

The birth of Contentful Smart Migration(CSM). This NPM package adds an abstraction layer over the Contentful Migration Tool which reduces the pain points mentioned. Using the same use case scenario, thanks to CSM we only need one script without explicitly using createContentType , editContentType , createField , editField .

//Script for creating the Article Content Typeconst OperateOn = require("contentful-smart-migration");

module.exports = async function(migration, context) {
const article = {
id: "article",
opts: {
name: "Article",
description: "Some description goes here...",
displayField: "title",
},
};

const fields = [
{
id: "title",
name: "Title",
},
{
id: "author",
name: "Author",
},
{
id: "datePublished",
name: "Date Published",
type: "Date"
}
];

const contentType = new OperateOn(migration, context, article, fields);
await contentType.operationOnContent();
await contentType.operationOnField();
};

Now lets modify the Content Type that will satisfy the request to change the field’s name from Title to Article Title and add a new field with the name Ratings.

//Using the same script aboveconst OperateOn = require("contentful-smart-migration");

module.exports = async function(migration, context) {
const article = {
id: "article",
opts: {
name: "Article",
description: "Some description goes here...",
displayField: "title",
},
};

const fields = [
{
id: "articleTitle", // With CSM, you can easily update the id
name: "Article Title",
modify: {
old_id: "title"
}
},
{
id: "author",
name: "Author",
},
{
id: "datePublished",
name: "Date Published",
type: "Date",
},
{
id: "ratings",
name: "Ratings"
}
];

const contentType = new OperateOn(migration, context, article, fields);
await contentType.operationOnContent();
await contentType.operationOnField();
};

With CSM, there is one script to rule them all (for building Content Types).

The breakdown

Install the package

npm i contentful-smart-migration --save-dev

Requiring the CSM methods

const OperateOn = require("contentful-smart-migration");

Wrap the script in Contentful’s migration function and pass in the two arguments (required).

module.exports = async function(migration, context) {}

The migration object is the main interface for creating and editing content types. The context object provides the Contentful API features not supported by the migration object. Read more here.

Creating the Content Type

const article = {
id: "article",
opts: {
name: "Article",
description: "Some description goes here...", //optional
displayField: "title",
},
};

Creating fields

const fields = [
{
id: "title",
name: "Title",
},
{
id: "author",
name: "Author",
},
{
id: "datePublished",
name: "Date Published",
type: "Date"
}
];

By default fields are type Symbol and set to required: true, localized: false, disabled: false, omitted: false, deleted: false , validations: [].

Create a new instance of OperateOn. Pass the migration , context , and article objects along with the fields array as it’s arguments.

const contentType = new OperateOn(migration, context, article, fields); 

The migration object being passed gives CSM access to Contentful’s migration methods, which are used to create or edit your Content Types and its respective fields.

The context object gives CSM the ability to query search for the respective content type to see if already exist.

The article object provides the information about the Content Type.

The fields array are the Content Type’s fields that will be created or edited if applicable.

Finally, calling the methods

await contentType.operationOnContent(); // Against the Content Type
await contentType.operationOnField(); // Against the Field

Want to know more of what CSM can do? Read these docs.

--

--