drupal / droost_cms
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
Requires
- drupal/droost: ^1.0@dev
- drupal/droost_theme: ^1.0@dev
- drupal/drupal_cms_starter: ^2
- drupal/ui_patterns: ^2.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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
eventcontent 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_lintgate 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).
| File | What it is |
|---|---|
droost/gates/component-lint.php | The 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.md | A 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.