Drupal Logo

The Comprehensive Guide to Drupal Recipes

At the keynote speech at DrupalCon 2022, Dries Buytaert, the founder of Drupal, presented the idea of “Starter templates” to help ambitious site builders quickly and easily create solutions. From that, the Distributions and Recipes initiative was born.

Drupal recipes are the foundation for the new Starshot Initiative, which aims to make Drupal easier to use and out of the box for all users. It took a couple of years, but Recipes have been added to Drupal core in 10.3 as Experimental APIs.

The Problem

For decades, developers have been packaging functionality together for Drupal to create different solutions, such as eCommerce stores, blogs, LMS, social media, and publishing sites. Many approaches were taken, each with its own pain points.

Installation Profiles

  • You can only start with them.
  • Once you start with them, you are stuck with them forever!
  • You can’t change to a different install profile.

Distributions

  • Complex dependencies that need to be maintained
  • Updates could break customizations
  • Would be hard to get solutions into Drupal core.

Features and Custom modules

  • As with the above, these often become complex and hard to manage.
  • Need update hooks or manual steps to alter core and other contrib module’s configuration.

The Solution

An issue has been created to add four new APIs to Drupal that will allow Recipes to be applied to Drupal. These recipes allow Drupal module automation, theme installation, and configuration at any time during the lifecycle of a Drupal site. They can install modules, import configuration, alter existing configuration, and even provide content to a Drupal site. Recipes are ephemeral; once applied, the results become the site’s responsibility.

What makes up a Drupal Recipe?

A recipe needs only a folder with the recipe’s name, which contains a recipe.yml file.  Beyond that, optional items are a /config folder, a /content folder, and a composer.json file. You can also include README.md and LICENSE.md files like a module or theme.

Recipe structure

  • recipe_name folder
    • recipe.yml
    • /config folder
      • node.type.event.yml
    • /content folder
    • composer.json

recipe.yml structure

name: 'Event'
  type: 'Content type'
recipes:
  event_manager
install:
  datetime_range
  node
config:
  actions:
    user.role.event_manager:
      grantPermissions:
        'delete any event content'
        'edit any event content'

What can’t a Recipe do?

To keep things functional and sustainable, recipes can’t do certain things.  For instance, recipes can’t…

  • Have their own code. If you need code, have your recipe install a module with that code.
  • Make dynamic changes.
  • Provide their own upgrade path (functionality is now part of the site).

Applying a recipe to an existing site

Recipes are applied to Drupal, not installed. Until the code is merged into Drupal core, you need to patch Drupal.  Patches can be found here on the project page.

Once you’ ha’ve patched your Drupal core version, you can use the internal script to apply a recipe to an existing site. On your command line, run the following command from your webroot:

php core/scripts/drupal recipe core/recipes/standard -v

This command uses PHP to find the “recipe” script in core/scripts/drupal. Then list the full path to the recipe. The -v flag has the recipe runner output the application steps as they happen on the command line. 

Once you apply the patch, you will get Drupal recipes from core! Core’s recipes currently include the Standard installation profile recreated as recipes. You can apply Standard and get all the functionality or apply them individually as needed. They were created to be very atomic — so that you can choose your own adventure.

Applying a recipe using Quick Start

Another Drupal script allows you to install Drupal from a recipe. When you do this, you do not start from an install profile! This is a new change. Previously, you at least had to start with a minimal profile; now, that is no longer needed.

If you’re starting a new Drupal install, you can use the following script:

php core/scripts/drupal quick-start core/recipes/article_content_type

The new Recipe APIs

The core of the recipe’s code are four new APIs.

  • Drupal\Core\Config\Action
  • Drupal\Core\Config\Checkpoint
  • Drupal\Core\Recipe
  • Drupal\Core\DefaultContent

The Recipe API depends on DefaultContent, Action, and Checkpoint. The other three APIs have no interdependencies.

Drupal\Core\Config\Checkpoint

A checkpoint is made for the site’s configuration when a recipe is applied. The recipe runner uses this to revert if the application fails, but there may be other uses for this in the future, like unapplying a recipe.

Drupal\Core\DefaultContent

This API’s code came from the contributed Default Content module. It allows recipes to provide content to Drupal using YAML files.

Drupal\Core\Config\Action

The Config Action API is the magic of the recipe system. It allows you to alter configurations already in a site, including core’s configs!

Drupal\Core\Recipe

Finally, the Recipe API puts it all together and applies the recipe. The recipe runner takes a recipe, and follows the following steps:

  1. Applies dependent recipes
  2. Install modules and themes
  3. Imports configuration from the /config folder
  4. Alters configuration based on actions
  5. Imports content from the /content folder

Deep Dive into an Actual Recipe

This recipe creates two fully configured content types. It includes meta tags, paths, configured admin forms:

name: 'Saplings - Content types'
description: 'Configuration for the Saplings Content types.'
type: 'Site'
# Here we require other dependent recipes.
recipes:
  - saplings-content-base
  - saplings-component-types
# Now we install modules.
install:
  # Core.
  - menu_ui
  - tour
  # Contrib.
  - access_unpublished
  - field_group
  - metatag_open_graph
  - metatag_twitter_cards
  - pathauto
  - publication_date
  - scheduler
  - scheduler_content_moderation_integration
  - schema_article
  - simple_sitemap
  - token_or
  - ui_patterns_layouts
config:
  # Now can import configuration
  import:
    # Core.
    menu_ui: "*"
    # Contrib.
    access_unpublished: "*"
    pathauto: "*"
    # If we only want to import certain configs, we can identify them like this.
    scheduler:
      - scheduler.settings
      - views.view.scheduler_scheduled_content
  # Config actions are where we can change configuration in Drupal 
  actions:
    # Set Metatag Home page:
    metatag.metatag_defaults.front:
      simple_config_update:
        tags.canonical_url: '[site:url]'
        tags.description: '[node:sa_description|node:sa_seo_description]'
        tags.image_src: '[node:sa_seo_image:entity:field_media_image:sa_social_media_facebook|node:sa_featured_image:entity:field_media_image:sa_social_media_facebook]'
     tags.og_description: '[node:sa_description|node:sa_seo_description]'
    # Set permissions for anonymous role.
    user.role.anonymous:
      ensure_exists:
        id: anonymous
      grantPermissions:
        - 'access content'
        - 'access sitemap'
        - 'access_unpublished node sa_page'
        - 'access_unpublished node sa_post'
    # Set permissions for content editor role.
    user.role.content_editor:
      ensure_exists:
        id: content_editor
      grantPermissions:
        - 'access administration pages'
        - 'access block library'
        - 'access content overview'
        - 'access environment indicator'
        - 'access environment indicator ribbon'
        - 'access help pages'
        - 'access media overview'
        - 'access media_entity_browser entity browser pages'
        - 'access media_entity_browser_modal entity browser pages'

What are Config Actions?

As you can see in the recipe example above, you can import config from modules and themes, and take action against config that already exists. Config actions are written to make specific changes to config files — including Drupal core configuration files, which previously could only be done through update hooks.

Look for this list to update as the initiative digs into phase 2 and creates more recipes and possibilities.

Available to all config entities

simple_config_update

This is a config action that can be used to make foo: bar type updates to any configuration file.

setThirdPartySetting(s)

The setThirdPartySetting(s) config actions allows recipe authors to set third-party

settings values.  Add the ‘s’ to apply multiple third part settings.

ensure_exists

This action can be used to ensure as a user role exists before applying other actions.  It helps a recipe not fail if the role does not exist.

create

The create config action can be used to create a config entity, but it is much easier to just put the config file in the /config folder.

Applicable to specific config entity types

addItemToToolbar

Used to add a button to the toolbar of a CKEditor 5 editor.

addToAllBundles

Used to add a field to all bundles of an entity type. Should be used in combination with setComponent(s) below to make sure the field displays on forms and displays.

addNodeTypes, addTaxonomyVocabularies

Used to add editorial workflows to node and taxonomy bundles.

grantPermission(s)

Used to grant permissions to any user.role.* config files.  Add the ‘s’ to add multiple permissions.

setComponent(s)

Used to to add fields to an entity’s view or form display configs.

What’s next for Drupal Recipes?

Now that recipes are in Drupal core, the phase 2 of the roadmap will soon be updated. Integration with Project Browser, and recipe creation in Starshot are definitely on the top of the list, but there are lots more things to do.  Stay tuned and check out the issue queue to learn more!