Search by

drupal / droost_cms

Drupal-Infrastructure

Drupal CMS for sites an AI agent builds under droost: Droost Theme, UI Patterns 2, SDC-first display patterns, and droost's site tools with their Canvas half.

Package info

git.drupalcode.org/project/droost_cms.git

Type:drupal-recipe

pkg:composer/drupal/droost_cms

Statistics

Installs: 177

Dependents: 0

Suggesters: 0

1.0.0-alpha1 2026-07-11 11:17 UTC

This package is auto-updated.

Last update: 2026-10-25 00:38:19 UTC


README

A Drupal recipe for Drupal CMS sites an AI agent builds under droost:

  • everything in drupal_cms_starter (Canvas page building, media, SEO, the admin UI, the Mercury theme…);
  • the Droost Theme as the site default: Tailwind v4 browser JIT plus daisyUI (no build step), design tokens in one css/tokens.css, and 21 Canvas-clean SDCs. Mercury stays installed and is no longer the default;
  • UI Patterns 2 (and its field formatters) as the SDC display layer for structured entities;
  • a worked design pattern: an event content type whose default display is composed entirely from SDCs;
  • droost's site tools: the droost module (2.x) and droost_canvas, through which an agent reads and writes content and Canvas pages (drush droost:tool), each call recorded for the run;
  • the agent layer (below): the component_lint gate and the paragraph droost carries into AGENTS.md.

The droost command is not a Drupal module: it is one command, droost, in the project's own tools/droost/. The recipe installs the module's content tier only (droost, droost_canvas), because a recipe runs in production too; turn on droost_build and droost_dev (configuration, fields, Views, the developer tools) in a development copy, never in production.

Requirements

Drupal CMS ships Canvas, and this recipe adds UI Patterns. The two fought over the SDC plugin manager (canvas#3561618) until UI Patterns 2.0.22, which stops decorating the manager once Canvas has replaced it. This recipe requires ^2.0.22, so no patch and no bridge module are needed. Measured 2026-10-20 on Canvas 1.12.0: with UI Patterns 2.0.23 the recipe applies the first time; with 2.0.21, drush cr fatals in Canvas's PropShape, and scripts/assert-recipe-applied.sh says so.

Install

On a fresh Drupal CMS project (composer create-project drupal/cms <dir>):

composer require 'drupal/droost_theme:1.0.x-dev' 'drupal/droost:2.0.x-dev' 'drupal/droost_cms:1.0.x-dev'
drush site:install minimal -y          # or pick "Droost CMS" in the Drupal CMS installer
drush recipe ../recipes/droost_cms
recipes/droost_cms/scripts/assert-recipe-applied.sh .

droost_theme, droost and this recipe must each be root-required with explicit dev constraints until they have stable releases: Composer ignores stability flags in a package that is not the root, so the recipe's own @dev requirement cannot widen stability for you.

Then droost itself, in its own vendor directory (as its README installs it), and the layer:

mkdir -p tools/droost
composer config -d tools/droost platform.php 8.3.0   # the PHP droost runs under (the site's)
composer require -d tools/droost droost/workflow
php tools/droost/vendor/bin/droost init
php tools/droost/vendor/bin/droost code:index
recipes/droost_cms/scripts/install-layer.sh .
# merge recipes/droost_cms/examples/droost.yml into droost/droost.yml
php tools/droost/vendor/bin/droost init     # again: carries droost/agents.md into AGENTS.md
drush en droost_build droost_dev -y         # a development copy only

scripts/assert-recipe-applied.sh <site-dir> checks every promise this recipe makes against a DDEV site and refuses to say OK otherwise: the modules it installs (droost and droost_canvas among them, and no build or developer tier), the theme, that Canvas and UI Patterns share one SDC manager after a rebuild, that the front page serves through droost_theme with no Mercury chrome, and that the site's configuration passes core's schema check for every config object.

Apply it once. Re-applying fails inside drupal_cms_starter (2.2.4): it creates canvas.content_template.node.page.full with create: rather than createIfNotExists:, so a second run stops at "Entity canvas.content_template.node.page.full exists" (measured 2026-10-24, with this recipe and with the one before it). That is the starter's, and it applies to any site the starter already built, so this recipe cannot be applied on top of an installed Drupal CMS site either.

The agent layer

The Canvas tools are the droost module's: droost_canvas lists pages and resolves aliases, reads, validates and writes a page's tree and a page variant's (the frame), hides components from the editor, and lints SDCs against Canvas's requirements. Its canvas-build skill, which says which of Canvas page, frame, composed display or View to build and how, is installed by droost init from the module. They lived here as a copied layer (droost/bin/canvas) until 2026-10-24.

What remains here is what a project needs beside the module, and scripts/install-layer.sh <project> copies it in. It is a script and not Composer's scaffold because Drupal CMS unpacks a recipe when it is required: the recipe leaves the lock, so a package file mapping would never run again. The script never overwrites a file you changed (it reports it and exits 1; --force replaces it).

FileWhat it is
droost/gates/component-lint.phpThe component_lint gate: every SDC in custom code meets Canvas's requirements, asked of the module (drush droost:component-lint --custom). Exit 4 when there is none, which droost records as a labelled pass (droost/workflow F-340); exit 3 when the module could not answer
droost/agents.mdA paragraph droost carries into AGENTS.md (appended, if another package's text is already there)

The gate file sits under droost/gates/, which droost freezes while a run is open, and droost's guard refuses writes into modules/contrib/droost (droost/workflow F-367), so a run cannot loosen the check its components pass.

examples/droost.yml is this recipe's part of droost/droost.yml: the component_lint gate and three house rules (new markup lives in an SDC; a landing page is a Canvas page written with droost_canvas_tree_set; site state changes through droost's tools, not raw drush php:eval). scripts/validate-layer.sh checks the layer before it ships.

Known issue: installing Custom block (block_content) fails on Canvas 1.12

On a site this recipe built, drush en block_content stops halfway with "Table 'db.block_content' doesn't exist", and leaves the module enabled with no table. Core clears the plugin caches before it creates a new module's tables; Canvas's block manager regenerates its components on that clear, and block_content's block deriver queries the table that is not there yet. Measured 2026-10-20 on Canvas 1.12.0 (the newest release). It is Canvas's: #3582851, with a merge request (!1839) that defers the generation until the install completes. Until it is released, keep site-wide editable details in config (a small custom module's settings) rather than a custom block type.

Known nuance: theme blocks

Droost Theme ships its block placements (branding, main menu, messages, local tasks) as theme config/optional, which recipe-driven theme installs can skip. This recipe therefore ships the same four block configs in its own config/ so a fresh site always gets the navbar. (The theme's optional title block is deliberately not placed: Canvas pages and composed displays carry their own headings.)

The event machine name, and why it is a risk worth naming

This recipe's config/ ships node.type.event — an unprefixed machine name in the same namespace Drupal CMS uses for its own content types (page, blog). If Drupal CMS ever ships an event content type of its own, a site that applied both would end up with ONE type carrying a mixture of two designs, because this recipe sets config: strict: false and a non-strict recipe reconciles differing config rather than refusing.

Checked against Drupal CMS 2.1.3 (2026-08-15): no such recipe exists. The installed roster is starter, content_type_base, media, search, seo/privacy/ anti-spam, forms, ai, admin_ui, authentication, accessibility_tools, google_analytics and the site-template bases — none of which defines event. So this is a forward risk, not a present conflict, and renaming now would cost ten config files plus every demo node for a collision that may never arrive.

Before installing an events recipe alongside this one, check whether it defines node.type.event. If it does, install it on a site WITHOUT this recipe, or rename this pattern's type first — a merged content type is the kind of breakage that shows up as a display that mostly works.

What the design pattern demonstrates

config/core.entity_view_display.node.event.default.yml renders every event field through droost_theme:section with UI Patterns component formatters: the field label becomes the heading prop, and the field's value fills the content slot through the field's own core formatter. It is the template the canvas-build skill points an agent at for a composed display.