For this example, we're starting with a blank DatoCMS project, and then progressively add models/records using migrations. We'll first create an Article model with a simple Title field.
With the CLI tool successfully set up, run the following command inside your project:
$ datocms migrations:new 'create article model'Created migrations/1591173668_createArticleModel.ts
This will create inside your migrations
directory a new script named <TIMESTAMP>_createArticleModel.js
. Let's take a look at its content:
'use strict';/** @param client { import("@datocms/cli/lib/cma-client-node").Client } */module.exports = async (client) => {// DatoCMS migration script// For more examples, head to our Content Management API docs:// https://www.datocms.com/docs/content-management-api// Create an Article model:// https://www.datocms.com/docs/content-management-api/resources/item-type/createconst articleModel = await client.itemTypes.create({name: 'Article',api_key: 'article',});// Create a Title field (required):// https://www.datocms.com/docs/content-management-api/resources/field/createconst titleField = await client.fields.create(articleModel, {label: 'Title',api_key: 'title',field_type: 'string',validators: {required: {},},});// Create an Article record:// https://www.datocms.com/docs/content-management-api/resources/item/createconst article = await client.items.create({item_type: articleModel,title: 'My first article!',});}
The script exports an async function with a client
argument, which is an instance of our Content Management API client.
The body of the function is already filled with everything we need for this particular example:
it creates a Article model,
it adds a Title field to it, and
it creates a first Article record.
Since our Content Management API client is fully typed, we strongly suggest you to write your migration scripts in TypeScript. You will get auto-completion suggestions on every call to an endpoint, and type checks for free.
If you're project has a tsconfig.json
file, the datocms migrations:new
command will automatically create migration scripts in TypeScript, but you can also manually pass the --ts
flag to the migrations:new
command.
If you would like to scaffold new migration scripts from a custom template instead of the default one, feel free to pass the --template
flag. Or, even better, you can add it as a default setting to you profile with the datocms profile:set
command, so that the choice will propagate to every other team member.
To execute the migration, run the following command:
$ datocms migrations:run --destination=feature-add-article-model
Here's the result:
Upon execution, the command does the following:
Forks the primary environment into a new sandbox environment called feature-add-article-model
;
Runs any pending migrations inside the sandbox environment.
To track which migrations have already run in a specific environment, the CLI creates a special schema_migration
model in your project. After each migration script completes, it creates a record referencing the name of the script itself.
You can configure the name of the model with the --migrations-model
flag, or configure your profile accordingly with the datocms profile:set
command!
To verify that only pending migrations are executed, we can re-run the same command and see the result:
$ datocms migrations:run --destination=feature-add-article-modelMigrations will be run in "feature-add-article-model" sandbox environmentCreating a fork of "main" environment called "feature-add-article-model"... !› Error: Environment "feature-add-article-model" already exists!› Try this:› * To execute the migrations inside the existing environment, run "datocms migrations:run --source=feature-add-article-model --in-place"› * To delete the environment, run "datocms environments:destroy feature-add-article-model"
Ouch! The sandbox environment feature-add-article-model
already exists, so the command failed. We can follow the CLI suggestion try to re-run the migrations inside the already existing sandbox environment.
$ datocms migrations:run --source=feature-add-article-model --in-placeMigrations will be run in "feature-add-article-model" sandbox environmentNo new migration scripts to run, skipping operation
As you can see, no migration gets executed, as our script has already been run in this environment!
Remember that you can always add the --json
flag to any CLI command, to get a JSON output, easily parsable by tools like jq
.
Let's create a new migration to add a new Author model, and an Author field on the article:
$ datocms migrations:new addAuthorsWriting "tempMigrations/1653062813_addAuthors.ts"... done
Let's replace the content of the new migration file with the following:
/** @param client { import("@datocms/cli/lib/cma-client-node").Client } */module.exports = async (client) => {// Create the `author` modelconst authrModel = await client.itemTypes.create({name: 'Author',api_key: 'author',});// Add a `name` field to the `author` modelawait client.fields.create('author', {label: 'Name',api_key: 'name',field_type: 'string',validators: {required: {},},});// Add an `author` field to the `article` modelawait client.fields.create('article', {label: 'Author',api_key: 'author',field_type: 'link',validators: {item_item_type: { item_types: [authorModel.id] },},});// Create an `author` recordconst authorRecord = await client.items.create({item_type: authorModel,name: 'Mark Smith',});// Set the `author` field on every existing articlefor await (const article of client.items.listPagedIterator({filter: { type: 'article' },})) {await client.items.update(article, {author: authorRecord.id,});}};
Then run the new script in our existing feature-add-article-model
sandbox environment:
$ dato migrations:run --source=feature-add-article-model --in-placeMigrations will be run in "feature-add-article-model" sandbox environmentRunning migration "1653062813_addAuthors.js"... failed!----ReferenceError: authorModel is not definedat module.exports (/Users/stefanoverna/dato/cli/packages/cli/migrations/1653062813_addAuthors.js:25:37)at processTicksAndRejections (internal/process/task_queues.js:95:5)at async Command.runMigrationScript (/Users/stefanoverna/dato/cli/packages/cli/lib/commands/migrations/run.js:103:17)at async Command.run (/Users/stefanoverna/dato/cli/packages/cli/lib/commands/migrations/run.js:70:13)at async Command._run (/Users/stefanoverna/dato/cli/packages/cli-utils/node_modules/@oclif/core/lib/command.js:67:22)at async Config.runCommand (/Users/stefanoverna/dato/cli/packages/cli/node_modules/@oclif/core/lib/config/config.js:268:25)at async Object.run (/Users/stefanoverna/dato/cli/packages/cli/node_modules/@oclif/core/lib/main.js:73:5)----› Error: Migration "1653062813_addAuthors.js" failed
Ouch, seems that there was a typo in our migration script (authrModel
instead of authorModel
):
/** @param client { import("@datocms/cli/lib/cma-client-node").Client } */module.exports = async (client) => {// Create the `author` modelconst authorModel = await client.itemTypes.create({name: 'Author',api_key: 'author',});
Since we're working on a sandbox, we can just fix the typo, delete the current sandbox, fork a new one from the primary environment and re-run the migrations:
$ datocms environments:destroy feature-add-article-model && \datocms migrations:run --destination=feature-add-article-modelDestroying environment "feature-add-article-model"... doneMigrations will be run in "feature-add-article-model" sandbox environmentCreating a fork of "main" environment called "feature-add-article-model"... doneCreating "schema_migration" model... doneRunning migration "1653061497_createArticleModel.js"... doneRunning migration "1653062813_addAuthors.js"... doneSuccessfully run 2 migration scriptsDone!
It goes without saying that you also need to work on your website/app to adapt it to the changes you just made. We suggest to work on a feature branch, including in the commits also the migration scripts.
All of our APIs and integrations offer a way to point to a sandbox environment instead of the primary one. For example, if you're using our GraphQL Content Delivery API, you can explicitly read data from a specific environment using one of the following endpoints:
https://graphql.datocms.com/environments/{ENVIRONMENT-NAME}https://graphql.datocms.com/environments/{ENVIRONMENT-NAME}/preview
Once everything is working as expected, we can ship everything production.
After you've run your tests you might need to programmatically delete a sandbox environment.
In this case you can simply run:
$ datocms environments:destroy <SANDBOX-ENVIRONMENT-NAME>
Check out this tutorial on how to migrate your content schema using scripts: