<h4>TL;DR</h4>
<p>Use <a href="https://craftcms.com/docs/4.x/project-config.html" rel="nofollow noreferrer">project-config for configuration</a> and migrations for <a href="https://craftcms.com/docs/4.x/extend/migrations.html" rel="nofollow noreferrer">content</a>.</p>
<hr />
<p>For configuration, you can use project-config all the way. This is well-supported and works reliably, and you don't have to bother with manually creating fields or sections using migrations. It's much less code to write, much less error-prone, and way easier. I've written a lot about a scalable <a href="https://craftcms.stackexchange.com/questions/39133/multiple-developers-working-on-the-same-site-project-config-issues/39134#39134">feature branch workflow with Craft here</a>.</p>
<p>For content, you need to ask yourself if you really <em>need</em> the same contents in a development environment. When we develop a feature, we try to build it as content-agnostic as possible. The project config defines the available sections and fields. But the code should work independently of specific entries, field values, etc. Using this approach, it's in fact preferable not to have the same contents everywhere, because you catch more bugs. For example, if you see an error after switching branches, it usually means there's a missing check for some piece of content that may or may not exist. It's better to catch those bugs early in development, not when it blows up in production.</p>
<p>Of course, this approach has some limits. If you frequently switch between branches with entirely different element types and fields, you will find you're creating the same dummy contents over and over again. In this case, I would use a migration, but only to populate the database with demo contents. So you use project config for the structure and migrations for the contents. In your deployment script (as well as while testing a feature branch), make sure to always apply the project config first to ensure the migration will work correctly.</p>
<p>The migration doesn't need to use low-level database commands if all it's doing is creating content. You can use the high-level service components instead. For example, here's a migration that creates a demo entry:</p>
<pre class="lang-php prettyprint-override"><code>$pageSection = $this-&gt;sections-&gt;getSectionByHandle('pages');
$pageEntryType = $pageSection-&gt;getEntryTypes()[0];

$entry = new Entry();
$entry-&gt;sectionId = $pageSection-&gt;id;
$entry-&gt;typeId = $pageEntryType-&gt;id;
$entry-&gt;enabled = true;
$entry-&gt;title = 'Demo page';
$entry-&gt;slug = 'demo';

$entry-&gt;setFieldValues([
    'headline' =&gt; 'This is a demo page',
    'body' =&gt; 'This is some demo content',
]);

return Craft::$app-&gt;getElements()-&gt;saveElement($entry);
</code></pre>
<p>This code can go into the <code>safeUp</code> function. In the <code>safeDown</code> function, you can find the entry using an entry query and delete it.</p>
<blockquote>
<p>if there is a bug in a migrations script, it throws an error, but not all the previous steps before this bug are reverted.</p>
</blockquote>
<p>Migrations are run inside transactions as long as you use <code>safeUp</code> and <code>safeDown</code> instead of <code>up</code> and <code>down</code>. So if there's an error, the transaction will be rolled back and the database won't be changed at all. You should always take a database backup before executing any migrations just to be safe, though.</p>

CC BY-SA 4.0
2023-02-03T16:36:35.810Z
2023-02-03T16:36:35.810Z
MoritzLost
<p>I'm trying to find out how to organize the cooperative work with a project on CraftCMS4. For example, I want to check a branch from a college that developing a new module, and switching to his branch from my one, where I'm developing a plugin. How should I do it correctly, so that I see new elements, fields, entries and content from his module, and then when I switch back to my plugin, to see all my elements &amp; content?</p>
<p>Partially it is solved with project YAML-Files - after switching the branches it offers to update the project configuration. But when I switch between different branches of the same feature, it partially overwrites and/or duplicates some entries, so they are not really able to be merged. Also there are often the looses of content.</p>
<p>And the second strategy is migration files, where I write commands in safeUp/safeDown methods, and don't commit/apply the project YAML configuration after switching the branches, but run the migrations through command line. But here are also several drawbacks. Directly I experienced that some of the methods are sometimes buggy, for example fields-&gt;deleteGroupById() only partially deletes the group but leaves the records in database that conflicts with new migrations; there can be created several records in database with the same section's name; if there is a bug in a migrations script, it throws an error, but not all the previous steps before this bug are reverted.</p>
<p>So now I wondering - are here any best practices? How do you do migrations correctly and reliably?</p>

<migration>
Best way to do migrations on CraftCMS4
# Best way to do migrations on CraftCMS4

(Asked by: username_0 on 2023-02-03)

I'm trying to find out how to organize the cooperative work with a project on CraftCMS4. For example, I want to check a branch from a college that developing a new module, and switching to his branch from my one, where I'm developing a plugin. How should I do it correctly, so that I see new elements, fields, entries and content from his module, and then when I switch back to my plugin, to see all my elements & content?

Partially it is solved with project YAML-Files - after switching the branches it offers to update the project configuration. But when I switch between different branches of the same feature, it partially overwrites and/or duplicates some entries, so they are not really able to be merged. Also there are often the looses of content.

And the second strategy is migration files, where I write commands in safeUp/safeDown methods, and don't commit/apply the project YAML configuration after switching the branches, but run the migrations through command line. But here are also several drawbacks. Directly I experienced that some of the methods are sometimes buggy, for example fields->deleteGroupById() only partially deletes the group but leaves the records in database that conflicts with new migrations; there can be created several records in database with the same section's name; if there is a bug in a migrations script, it throws an error, but not all the previous steps before this bug are reverted.

So now I wondering - are here any best practices? How do you do migrations correctly and reliably?

**Tags:** migration

---

## 1 answers

### Answer by username_1 on 2023-02-03. Score: 2 | Accepted answer

TL;DR
Use project-config for configuration and migrations for content.

For configuration, you can use project-config all the way. This is well-supported and works reliably, and you don't have to bother with manually creating fields or sections using migrations. It's much less code to write, much less error-prone, and way easier. I've written a lot about a scalable feature branch workflow with Craft here.
For content, you need to ask yourself if you really need the same contents in a development environment. When we develop a feature, we try to build it as content-agnostic as possible. The project config defines the available sections and fields. But the code should work independently of specific entries, field values, etc. Using this approach, it's in fact preferable not to have the same contents everywhere, because you catch more bugs. For example, if you see an error after switching branches, it usually means there's a missing check for some piece of content that may or may not exist. It's better to catch those bugs early in development, not when it blows up in production.
Of course, this approach has some limits. If you frequently switch between branches with entirely different element types and fields, you will find you're creating the same dummy contents over and over again. In this case, I would use a migration, but only to populate the database with demo contents. So you use project config for the structure and migrations for the contents. In your deployment script (as well as while testing a feature branch), make sure to always apply the project config first to ensure the migration will work correctly.
The migration doesn't need to use low-level database commands if all it's doing is creating content. You can use the high-level service components instead. For example, here's a migration that creates a demo entry:
$pageSection = $this->sections->getSectionByHandle('pages');
$pageEntryType = $pageSection->getEntryTypes()[0];

$entry = new Entry();
$entry->sectionId = $pageSection->id;
$entry->typeId = $pageEntryType->id;
$entry->enabled = true;
$entry->title = 'Demo page';
$entry->slug = 'demo';

$entry->setFieldValues([
    'headline' => 'This is a demo page',
    'body' => 'This is some demo content',
]);

return Craft::$app->getElements()->saveElement($entry);

This code can go into the safeUp function. In the safeDown function, you can find the entry using an entry query and delete it.

if there is a bug in a migrations script, it throws an error, but not all the previous steps before this bug are reverted.

Migrations are run inside transactions as long as you use safeUp and safeDown instead of up and down. So if there's an error, the transaction will be rolled back and the database won't be changed at all. You should always take a database backup before executing any migrations just to be safe, though.


---

