skills/factorial-io/skills/drupal-recipe-content

drupal-recipe-content

SKILL.md

Drupal Recipe Content Import

Create Drupal recipes that ship default content (taxonomy terms, nodes, media, etc.) with full multilingual translation support, using Drupal core's built-in content export/import API.

Key Concepts

  • Drupal recipes are primarily for configuration, but since Drupal 11.3, core includes a content export/import API that allows recipes to ship content entities.
  • Content is stored as YAML files in a content/ directory inside the recipe.
  • Translations are included in the exported YAML — no separate files needed per language.
  • On import, entities get new IDs (not the original ones). UUIDs are used internally for dependency resolution.
  • The default_content contrib module is unmaintained and conflicts with Canvas — do not use it. Use core's built-in API instead.

Recipe Structure

my_recipe/
├── recipe.yml
├── config/
│   └── ... (optional config YAML files)
└── content/
    └── taxonomy_term/
        ├── <uuid-1>.yml
        ├── <uuid-2>.yml
        └── ...

Content files are organized by entity type in subdirectories under content/. Each entity is a separate YAML file named by its UUID.

recipe.yml

The recipe.yml does not need to explicitly reference content files. Drupal automatically imports everything in the content/ directory when the recipe is applied.

name: 'My Taxonomy Terms'
description: 'Provides taxonomy terms with EN/DE translations.'
type: 'Content'

recipes:
  # Ensure the vocabulary and translation config exist first
  - core/recipes/tags_taxonomy

install:
  - language
  - content_translation

Make sure any required vocabulary, content type, or field configuration is applied before the content import — either via dependent recipes or config in the same recipe.

Exporting Content

Drupal 11.3+ (Core CLI)

# Export a single taxonomy term
php core/scripts/drupal content:export taxonomy_term 42

# Export with all dependencies (referenced entities, files, etc.)
php core/scripts/drupal content:export taxonomy_term 42 --with-dependencies --dir=recipes/my_recipe/content

# Export a node with dependencies
php core/scripts/drupal content:export node 3 -W --dir=recipes/my_recipe/content

The -W / --with-dependencies flag is important — it ensures referenced entities (images, terms, users) are exported alongside the main entity.

Drush (with default_content as dev-only dependency)

If using Drush, default_content can be a dev-only dependency (not needed in production):

composer require --dev drupal/default_content
drush en -y default_content

# Export to a recipe's content directory
drush dcer taxonomy_term 42 --folder=recipes/my_recipe/content

# Export all terms of a vocabulary (if supported)
drush dcer taxonomy_term --folder=recipes/my_recipe/content

Multilingual Content

Translations are embedded in the exported YAML automatically. When you export an entity that has translations, the YAML will contain all language versions.

Workflow for multilingual recipes

  1. Create content on a reference site with all translations in place
  2. Export with --with-dependencies — translations are included
  3. Place the YAML files in content/<entity_type>/ inside your recipe
  4. When the recipe is applied, entities are created with all their translations

There is no need for separate export/import steps per language.

Gotchas and Tips

  • IDs change on import: Exported entity IDs (tid, nid) will not match on the target site. Drupal assigns new sequential IDs. References between entities are resolved via UUIDs.
  • Config must exist first: The vocabulary, content type, fields, and language configuration must be in place before content is imported. Use the recipes: and install: sections to ensure this.
  • No default_content at runtime: If you used Drush's dcer command for exporting, you only need default_content as a dev dependency. The core import mechanism handles the rest.
  • File assets: When exporting with --with-dependencies, file entities and their physical files are included in the export. They get placed in a file/ subdirectory under content/.
  • Entity references: Cross-references between content entities (e.g., a node referencing taxonomy terms) are resolved by UUID during import, so export order doesn't matter.

Canvas Module Workaround

Sites using the Canvas page builder module require special handling for recipe content imports. Canvas overrides entity reference field types (EntityReferenceItemOverride) and expects UUID strings, but core's exporter outputs integer values for user references (uid 0 and 1).

The Problem

Core's Exporter uses array_map('intval') for user 0/1 references, producing:

content_translation_uid:
  -
    target_id: 1
    target_uuid: 0

Canvas's EntityReferenceItemOverride::getTargetId() receives the integer 0 for target_uuid but expects a string, causing a TypeError during recipe import.

The Fix

After exporting content with content:export, strip the following non-essential fields from both default and translations sections in every content YAML file:

  • content_translation_uid — causes the Canvas TypeError
  • content_translation_source — not needed for import
  • content_translation_outdated — not needed for import
  • revision_translation_affected — auto-computed on save
  • path — only needed if you want specific aliases

A minimal content YAML for a translatable taxonomy term looks like:

_meta:
  version: '1.0'
  entity_type: taxonomy_term
  uuid: 386059fa-39bb-4fa4-ae79-1b204a6d55c6
  bundle: location
  default_langcode: en
default:
  status:
    -
      value: true
  name:
    -
      value: Ingelheim
  weight:
    -
      value: 0
translations:
  de:
    status:
      -
        value: true
    name:
      -
        value: Ingelheim
    weight:
      -
        value: 0

This also applies to nodes and other content entities — always strip content_translation_uid when Canvas is installed.

Alternative Approaches

If core's content API doesn't fit your needs (e.g., you're on Drupal < 11.3), see references/alternatives.md for fallback strategies using custom install hooks or migrations.

Weekly Installs
11
GitHub Stars
1
First Seen
Feb 18, 2026
Installed on
opencode11
gemini-cli11
amp11
github-copilot11
codex11
kimi-cli11