Add a set of Taxonomy terms via a custom Drupal module's update hook

From time to time, I've needed to have a default set of Taxonomy terms created at the same time as a content type, as in the case of a field with a required Taxonomy term reference, using a Taxonomy that is not 'free tag' style.

Instead of requiring someone to go in and manually add all the terms after code is deployed, you can add terms in a custom module's update hook, like so:

// Put the following line in the top of the .install file:
use Drupal\taxonomy\Entity\Term;

 * Add some terms to the Category vocabulary.
function modulename_update_8001() {
// Machine name of the Taxonomy vocabulary.
$vocab = 'category';

// Term names to be added.
$items = [
'Hot Pink',
  foreach (
$items as $item) {
$term = Term::create(array(
'parent' => array(),
'name' => $item,
'vid' => $vocab,

This assumes you want to add terms to a Taxonomy with machine name category, and your module's machine name is modulename. If you're using Core CMI for configuration management, and you need to deploy a new custom module to do an update like this, you could create the module locally, enable it on your local site, then export the configuration locally so the module gets enabled automatically through the core.extension.yml configuration file.

If you just created the module and this is the first thing you're doing with it (e.g. if this is a brand new custom module), you'll also need to add a hook_install() invocation, and inside it, call the update hook that installs the taxonomy terms (modulename_update_8001()). Otherwise, you could even put the code that creates the terms directly in mymodulename_install().

A quick note on hook_update_N() functions in Drupal 8: make sure you start the sequence of update hooks with 8001 and not 8000, otherwise strange things can happen to your module's schema!


Funny, I just wrote a custom module that had to do this. I ended up using hook_install() and hook_uninstall() to add/delete the vocabulary and terms. Is there an advantage to using hook_update_N() versus the install/uninstall hooks?

@Justin - Thanks for the note; I was writing this more from the perspective of having that module already created; an update function (8xxx numbering) can add the terms when you run database updates. In the case of a new module, you can (and most correctly, probably should) put the code directly inside hook_install().

This should really go in hook_post_update_NAME() instead.

I know this is a bit of an old post, but it constantly shows up at the top in my google searches about an issue I'm having.

I have a term reference field on a content type with a term selected as a default value. The content type is new, the field is new, the vocabulary is new, the terms are new.

I can't seem to find anyway to get this exported out of dev and into test correctly. The terms don't get created b/c they're content. But if I create the vocabulary and add the terms, the import complains b/c the vocabulary needs to be deleted before the import will be allowed.

And it seems the terms get created with different UUIDS and that is what is used to set the default value.

Even when creating the terms in a update hook it seems to be an issue. I can't seem to find anything about how to do this properly and I've been looking for quite a while. I've been scouring and the drupal issue queue for hours-- any links or advice would be greatly appreciated!

And hope you're doing much better since December.

Yeah this seems to be an age-old problem. See for example. Same with any content.

1. You could create a one-tim on-and-off module to provide the content. It then has to be switched on explicitly via Drush or UI to have the install hook being triggered. It won't work via config import.
2. You could try out Default Content for D8
3. Follow the issue on adding an hook_post_config_import_NAME and try that out, though it's not clear to me if this hook will get triggered in a module that just will be activated during this import.

I have just been looking into this and what has worked for me is a one-time module which on installation creates the terms (pretty much as described in the post).

The one modification I have made is to manually set the uuid on the term that needs to be my "default".

As you say, terms get created with different UUIDS so there's no way (that I know of) to specify that a new term is created with a specific UUID matching the default_value that's associated in our term reference field.

BUT, it seems we can update the new term with a manually defined UUID. E.g.

`$term->set('uuid', 'e999f130-b2c6-4fd3-8e7f-6057d12c63e5')->save();`

I took the uuid from the default_value in my field config, and manually set it on the term that is programatically created in my install hook.