Content Migrations in Craft

Building Your First Content Migration

We build a simple content migration that creates a new channel section in our Craft project.

Let’s start out by cre­at­ing a sim­ple migra­tion file for cre­at­ing a new sec­tion. Our ulti­mate goal is to house the entire con­tact form cre­ation process in one migra­tion file — for the sake of porta­bil­i­ty — but let’s start simple!

The first step is to cre­ate the migra­tion file. Using Craft and Yii, we can eas­i­ly cre­ate new migra­tion file using the migrate/create command.

The com­mand has one required argu­ment, which is the name of the migra­tion. Craft will prepend a time­stamp to it, so the migra­tion file has a unique name.

craft migrate/create add_contact_form_section

The result­ing file will be stored in the migrations direc­to­ry of your project and named some­thing like m21105_144903_add_contact_form_section

Let’s edit that file and build our first migration!

The Parts of a Con­tent Migration

The two parsts of a migra­tion file that do the work are the safeUp and safeDown meth­ods. These are the tran­sca­tion­al migra­tion ver­sions of up and down.

These meth­ods are con­sid­ered safe because the entire migra­tion will suc­ceed or fail as a whole. This main­tains the integri­ty of the data­base in the event that there’s an issue with the migra­tion part­way through. Since the entire migra­tion either fails or suceeds, you won’t ever get into the sit­u­a­tion of hav­ing your data­base in an unsta­ble state.

safeUp allows you to make changes to the data­base via the code inside of the method. For con­tent migra­tions, this is where you add a new field, sec­tion, user, or entry. Any­thing, real­ly, that isn’t tied to installing or updat­ing a plu­g­in (plu­g­ins have their own migrations).

safeDown allow you to roll­back the changes made in safeUp.

Cre­at­ing a Craft Sec­tion With a Con­tent Migration

To test out our new migra­tion file, let’s cre­ate a new sec­tion in our Craft installation. 

We could cre­ate that sec­tion via the Craft con­trol pan­el UI but the idea here is that we want to script every­thing so the next time we need to add a con­tact form to a project, we can just drop the migra­tion files into our project and then run them. Since the migra­tion is self-con­tained, it doesn’t require any­thing else except for a prop­er­ly run­ning instal­la­tion of Craft.

The first thing we want to do is define our new sec­tion. We can do that via the Sec­tion mod­el that Craft provides.

We’ll first import it into our migra­tion file:

use craft\models\Section;

And then we can use the mod­el to define our new sec­tion inside of the safeUp method.

The sec­tion con­struc­tor takes an array of set­tings as name, val­ue pairs in order to ini­tial­ize the object.

$section = new Section([
	

]);

We’ll give our sec­tion a name, han­dle, type (chan­nel), and then the sec­tion set­tings for the site.

$section = new Section([
            'name' => 'Contact Form',
            'handle' => 'contactForm',
            'type' => Section::TYPE_CHANNEL,
            'siteSettings' => []
        ]);

The site set­tings take an array of data and we need to pop­u­late that using a siteSettings mod­el. Let’s first import that class into our migra­tion file:

use craft\models\Section;
use craft\models\Section_SiteSettings;

And then we’ll define three items in the set­tings: siteId, enabledByDefault, and hasUrls.

We’d like his sec­tion to be part of the pri­ma­ry site (and we only have one site in this instal­la­tion), we want it enabled by default, and we don’t want it to have URLs. 

Because we’re sav­ing con­tact form sub­mis­sions as entries in a sec­tion, we don’t want that data avail­able at any URLs at all.

$section = new Section([
            'name' => 'Contact Form',
            'handle' => 'contactForm',
            'type' => Section::TYPE_CHANNEL,
            'siteSettings' => [
				new Section_SiteSettings([
					'siteId' => Craft::$app->sites->getPrimarySite()->id,
					'enabledByDefault' => true,
					'hasUrls' => false
				])
			]
        ]);

Now that we have our new sec­tion defined, we can cre­ate and save it using the saveSections method in the sec­tions ser­vice. The method takes one required para­me­ter, which is the new sec­tion we want to save.

$section = new Section([
            'name' => 'Contact Form',
            'handle' => 'contactForm',
            'type' => Section::TYPE_CHANNEL,
            'siteSettings' => [
				new Section_SiteSettings([
					'siteId' => Craft::$app->sites->getPrimarySite()->id,
					'enabledByDefault' => true,
					'hasUrls' => false
				])
			]
        ]);

Craft::$app->sections->saveSection($section)

Let’s make it easy to undo our migra­tion by defin­ing some instruc­tions for the safeDown method. What we want to do is get the sec­tion by han­dle and then pass it into the deleteSectionById method and remove the section.

public function safeDown()  
{  
 	$section = Craft::$app->sections->getSectionByHandle('contactForm'); 
	
	return (Craft::$app->sections->deleteSectionById($section->id));  
}

I know the sec­tion han­dle will be contactForm because that’s what I defined it as in the safeUp code. Once I fetch that sec­tion by han­dle, then I can just get the ID and pass that into deleteSectionById().

Now we are ready to test our new migration!

First, we’ll cre­ate the new sec­tion by run­ning the migration’s safeUp method. There are two ways to do that:

  1. Run the migra­tion from the com­mand line
  2. Run the migra­tion from the Craft con­trol panel.

Run­ning the migra­tion from the Craft con­trol pan­el requires nav­i­gat­ing to the Util­i­ties sec­tion and then choose Migra­tions. The migra­tion should show up with a sta­tus of new. If we click Apply New Migra­tions”, then the migra­tion will run. There is not, how­ev­er, a way to roll­back a migra­tion from with­in the con­trol pan­el. If you want to do that, you’d need to use the com­mand line (which sounds like a good safe­ty feature!).

But let’s run our migra­tion from the com­mand line, so we can get used to the command.

First, let’s make sure our migra­tion is ready to run.

craft migrate/new

And the out­put should look some­thing like this:

Found 1 new migration:
        m211015_144903_add_contact_form

That looks good. Let’s run the new migra­tion and cre­ate our new sec­tion. This com­mand will run all new migra­tions and appy their changes to the database. 

craft migrate/up
Total 1 new migration to be applied:
        m211015_144903_add_contact_form

Apply the above migration? (yes|no) [no]:yes
*** applying m211015_144903_add_contact_form
*** applied m211015_144903_add_contact_form (time: 0.060s)

When we look at our sec­tions list in the con­trol pan­el, we should see the new Con­tact Form section.

Con­grats, you built your first con­tent migration!

Content Migrations in Craft is made up of the following videos: