Config Export & Recipes
When to Use
When packaging taxonomy vocabulary config for reuse, distribution via modules, or deployment via recipes.
Steps
Export existing vocabulary config:
- Via UI — Configuration > Synchronize > Export > Single item > Taxonomy vocabulary
-
Select vocabulary, copy YAML
-
Via Drush — Export specific config
drush config:get taxonomy.vocabulary.tags drush config:export taxonomy.vocabulary.tags --destination=/tmp/ -
Clean exported YAML — Remove UUIDs, dependencies if needed
# Remove this line for module config uuid: 12345678-1234-1234-1234-123456789abc
Create recipe for taxonomy setup:
Recipe pattern for vocabulary + field + display config:
# recipe.yml
name: 'Article Tags'
description: 'Provides tags on article content'
type: 'Content field'
recipes:
- article_content_type
- tags_taxonomy
install:
- views
config:
strict:
- field.storage.node.field_tags
import:
taxonomy:
- taxonomy.vocabulary.tags
- views.view.taxonomy_term
actions:
core.entity_form_display.node.article.default:
setComponent:
name: field_tags
options:
type: entity_reference_autocomplete_tags
weight: 3
settings:
match_operator: CONTAINS
size: 60
core.entity_view_display.node.article.default:
setComponent:
name: field_tags
options:
type: entity_reference_label
settings:
link: true
weight: 10
Reference: /core/recipes/article_tags/recipe.yml
Include default terms:
Terms are content, not config. A recipe ships them in its content/ folder:
- Create the terms on a working site.
- Export them with core's
content:exportDrush command. This needs Drupal 11.3 or later — recipes importcontent/from 10.3, but the export command lands in 11.3. On earlier versions, export with the contrib Default Content module instead:The command is core's (drush content:export taxonomy_term --bundle=VOCAB_ID --dir=recipes/RECIPE_NAME/contentcore/lib/Drupal/Core/DefaultContent/Command/ContentExportCommand.phpon 11.4), and is marked experimental. It writes one YAML file per term undercontent/taxonomy_term/. - List the vocabulary's recipe under
recipes:in the term recipe, so the vocabulary exists before the terms import. - Apply the recipe. Core imports
content/withExisting::Skip: a term whose UUID already exists is left as-is, so re-applying the recipe is safe. A child term names its parent by UUID under_meta.depends, so the importer creates the parent first.
Use an install hook instead when a module, not a recipe, owns the vocabulary:
function mymodule_install() {
$terms = ['PHP', 'JavaScript', 'CSS'];
foreach ($terms as $name) {
Term::create(['vid' => 'technologies', 'name' => $name])->save();
}
}
Default Content and Content as Configuration are contrib alternatives, not the default — reach for them only when the recipe route above doesn't fit:
-
Default Content module — Export terms as JSON
drush dce taxonomy_term TERM_ID -
Content as Configuration module — Save terms as config entities
Decision Points
| At this step... | If... | Then... |
|---|---|---|
| Export method | One-time manual export | Use UI export |
| Export method | Automated deployment | Use Drush in scripts |
| Recipe vs module | Reusable taxonomy pattern | Create recipe with vocabulary + field configs |
| Recipe vs module | Site-specific taxonomy | Export to sync config, deploy via config management |
| Default terms | Terms are essential for functionality | Ship them in the recipe's content/ folder; use an install hook only when a module, not a recipe, owns the vocabulary |
| Default terms | Terms are sample data | Skip; let site builders create them |
Common Mistakes
- Including UUIDs in module config → Causes conflicts on import. Remove uuid keys before committing to module
- Not using
enforcedmodule dependency → Vocabulary persists after module uninstall. Always add enforced dependency for module-owned config - Exporting entire config directory for one vocabulary → Bloats repository. Export only necessary files: vocabulary, field storage, field instances
- Forgetting to include field display configs → Vocabulary + field storage aren't enough; widget/formatter configs needed for complete setup
- Trying to export terms as config → Terms are content entities. Ship terms in a recipe's
content/folder, or create them in an install hook - Not setting
strict: falsein recipes when optional → Recipe fails if config already exists. Usestrict: falsefor optional/reusable configs like shared vocabularies - Keeping the empty
pathitem thatcontent:exportwrites → A term with no alias still exportspath: [{langcode: en, alias: null}], becausePathItem::isEmpty()returns FALSE wheneverlangcodeis set, and export always sets it. Importing that item requires the Path module. Remove it when no term has an alias, or addpathto the recipe'sinstall:when terms do - Leaving out Views → Core's
/core/recipes/tags_taxonomy/recipe.ymlinstallsviewsbesidetaxonomy, with a comment citing drupal.org issue 3479980: taxonomy has no fallback to display terms without Views. Copy that line and its comment into any recipe that ships a vocabulary - Committing
config/syncvocabulary files exported from another database → A vocabulary inconfig/synccarries the UUID of the site it was exported from. If the live vocabulary has a different UUID,drush config:importdeletes and recreates it, and deleting a vocabulary deletes its terms. Exportconfig/syncfrom the site that will import it, not from a scratch database
See Also
- ← Previous: Taxonomy with Entity Reference | Next: Best Practices & Patterns →
- Reference:
/core/recipes/tags_taxonomy/recipe.yml - Reference:
/core/recipes/basic_shortcuts/content/— a core recipe that ships content - Reference: Drupal.org Content as Configuration module