<p>This works:</p>
<p><strong>UPDATE:</strong> As @MatsMikkelRummelhoff has pointed out, Craft's project config will handle the schema for category groups.</p>
<pre class="lang-php prettyprint-override"><code>$defaultSite = Craft::$app-&gt;sites-&gt;getSiteByHandle('default');

// Create category group
// UPDATE: Craft's project config will actually handle the category groups; better to allow it to do so
Craft::$app-&gt;categories-&gt;saveGroup(new CategoryGroup([
    'name' =&gt; 'My Category Group',
    'handle' =&gt; 'myCategoryGroup',
    'maxLevels' =&gt; 1,
    'siteSettings' =&gt; [$defaultSite-&gt;id =&gt; new CategoryGroup_SiteSettings([
        'hasUrls' =&gt; true,
        'uriFormat' =&gt; 'categories/{slug}',
        'template' =&gt; 'myTemplate',
    ])],
]));

$categoryGroup = Craft::$app-&gt;categories-&gt;getGroupByHandle('articleCategories');

// Create category within group
Craft::$app-&gt;getElements()-&gt;saveElement(new Category([
    'title' =&gt; 'My Category',
    'slug' =&gt; 'my category',
    'enabled' =&gt; true,
    'siteId' =&gt; $defaultSite-&gt;id,
    'groupId' =&gt; $categoryGroup-&gt;id,
]));
</code></pre>
<p>Two things:</p>
<ol>
<li>It seems that when creating a category group, the <code>siteSettings</code> property needs to be an associative array with the key(s) corresponding to the site ID for which it applies. This is not clear from the API documentation.</li>
<li>Furthermore, I was running this code with the <code>safeUp()</code> method of a migration and wasn't returning <code>true</code>, therefore everything was being rolled back.</li>
</ol>
<p>Nonetheless, I would welcome any comments about a better way to learn how to programmatically create different content elements (other than wading through the API documentation and fiddling with things until they work).</p>

CC BY-SA 4.0
2022-11-12T14:52:37.937Z
Also, be aware that category groups is schema and not content – and as such Craft will write to the [project config](https://craftcms.com/docs/4.x/project-config.html) when the groups are added. If the project is tracking schema changes by having the project config YAML files in version control, you should be careful that the code adding the groups isn't run in production (i.e. any environment that has the `allowAdminChanges` config setting set to `false`).
Mats Mikkel Rummelhoff
CC BY-SA 4.0
2022-11-14T20:00:20.467Z
@MatsMikkelRummelhoff you're right. I expected category groups to be handled by project config, but for some reason I did not see the changes reflected in mine so I thought Craft was treating them like content. It's much better/easier to let the project config handle the groups.
Garrett
CC BY-SA 4.0
2022-11-11T23:01:19.273Z
2022-11-14T20:01:48.930Z
2022-11-14T20:01:48.930Z
Garrett
<p>I am struggling to create a new category group and category programmatically. I've found a few examples for <a href="https://craftcms.stackexchange.com/questions/21864/how-to-save-matrix-data-programmatically-in-craft-3">creating an entry</a>, and one for <a href="https://craftcms.stackexchange.com/questions/6799/best-way-to-programmatically-create-categories">creating categories in Craft 2</a>, but I can't get it working in Craft 4. Here's my attempt at creating a new category group and related category within a content migration:</p>
<pre class="lang-php prettyprint-override"><code>use \craft\elements\Category;
use \craft\models\CategoryGroup;
use \craft\models\CategoryGroup_SiteSettings;

[...]

$defaultSite = Craft::$app-&gt;sites-&gt;getSiteByHandle('default');

Craft::$app-&gt;categories-&gt;saveGroup(new CategoryGroup([
    'name' =&gt; 'My Category Group',
    'handle' =&gt; 'myCategoryGroup',
    'maxLevels' =&gt; 1,
    'defaultPlacement' =&gt; 'end',
    'siteSettings' =&gt; [new CategoryGroup_SiteSettings([
        'siteId' =&gt; $defaultSite-&gt;id,
        'uriFormat' =&gt; 'categories/{slug}',
        'template' =&gt; 'myTemplate',
    ])],
]));
// -&gt; Exception: Tried to save a category group that is missing site settings

$myCategoryGroup = Craft::$app-&gt;categories-&gt;getGroupByHandle('myCategoryGroup');

Craft::$app-&gt;getElements()-&gt;saveElement(new Category([
    'title' =&gt; 'My Category',
    'slug' =&gt; 'my-category',
    'enabled' =&gt; true,
    'siteId' =&gt; $defaultSite-&gt;id,
    'groupId' =&gt; $myCategoryGroup-&gt;id,
]));
</code></pre>
<p>Is there a guide or some examples for doing this? Please help.</p>

<craft3><plugin-development><categories><craft4>
How to programmatically create category groups and categories in Craft 3/4
# How to programmatically create category groups and categories in Craft 3/4

(Asked by: username_0 on 2022-11-11)

I am struggling to create a new category group and category programmatically. I've found a few examples for creating an entry, and one for creating categories in Craft 2, but I can't get it working in Craft 4. Here's my attempt at creating a new category group and related category within a content migration:
use \craft\elements\Category;
use \craft\models\CategoryGroup;
use \craft\models\CategoryGroup_SiteSettings;

[...]

$defaultSite = Craft::$app->sites->getSiteByHandle('default');

Craft::$app->categories->saveGroup(new CategoryGroup([
    'name' => 'My Category Group',
    'handle' => 'myCategoryGroup',
    'maxLevels' => 1,
    'defaultPlacement' => 'end',
    'siteSettings' => [new CategoryGroup_SiteSettings([
        'siteId' => $defaultSite->id,
        'uriFormat' => 'categories/{slug}',
        'template' => 'myTemplate',
    ])],
]));
// -> Exception: Tried to save a category group that is missing site settings

$myCategoryGroup = Craft::$app->categories->getGroupByHandle('myCategoryGroup');

Craft::$app->getElements()->saveElement(new Category([
    'title' => 'My Category',
    'slug' => 'my-category',
    'enabled' => true,
    'siteId' => $defaultSite->id,
    'groupId' => $myCategoryGroup->id,
]));

Is there a guide or some examples for doing this? Please help.

**Tags:** craft3, plugin-development, categories, craft4


---

## 1 answers

### Answer by username_0 on 2022-11-11. Score: 2 | Accepted answer

This works:
UPDATE: As @MatsMikkelRummelhoff has pointed out, Craft's project config will handle the schema for category groups.
$defaultSite = Craft::$app->sites->getSiteByHandle('default');

// Create category group
// UPDATE: Craft's project config will actually handle the category groups; better to allow it to do so
Craft::$app->categories->saveGroup(new CategoryGroup([
    'name' => 'My Category Group',
    'handle' => 'myCategoryGroup',
    'maxLevels' => 1,
    'siteSettings' => [$defaultSite->id => new CategoryGroup_SiteSettings([
        'hasUrls' => true,
        'uriFormat' => 'categories/{slug}',
        'template' => 'myTemplate',
    ])],
]));

$categoryGroup = Craft::$app->categories->getGroupByHandle('articleCategories');

// Create category within group
Craft::$app->getElements()->saveElement(new Category([
    'title' => 'My Category',
    'slug' => 'my category',
    'enabled' => true,
    'siteId' => $defaultSite->id,
    'groupId' => $categoryGroup->id,
]));

Two things:

It seems that when creating a category group, the siteSettings property needs to be an associative array with the key(s) corresponding to the site ID for which it applies. This is not clear from the API documentation.
Furthermore, I was running this code with the safeUp() method of a migration and wasn't returning true, therefore everything was being rolled back.

Nonetheless, I would welcome any comments about a better way to learn how to programmatically create different content elements (other than wading through the API documentation and fiddling with things until they work).

> **Comments:**
> **username_1**: Also, be aware that category groups is schema and not content – and as such Craft will write to the [project config](https://craftcms.com/docs/4.x/project-config.html) when the groups are added. If the project is tracking schema changes by having the project config YAML files in version control, you should be careful that the code adding the groups isn't run in production (i.e. any environment that has the `allowAdminChanges` config setting set to `false`).  
> **username_0**: @MatsMikkelRummelhoff you're right. I expected category groups to be handled by project config, but for some reason I did not see the changes reflected in mine so I thought Craft was treating them like content. It's much better/easier to let the project config handle the groups.  

---

