AI page building¶
Describe a page or a section and have it built from your approved
components, with your real content. Ships as the optional
canvas_builder_ai submodule and mounts as a Create tab in the Canvas
editor's right panel.
Nothing generates markup. The AI picks from a governed catalogue of the components you already have, and every plan it produces is validated server-side before a single node reaches the page.
Where it lives¶
There is no floating launcher — the Build UI is a Create tab, a real peer of the panels you already use.
Nothing selected: Create sits next to Details in the Page data panel.
A top-level section selected: Create joins Review/Style/Advanced and switches to transform mode.
Land on any Canvas page and Create is there immediately, next to Details in the Page data panel. Select a top-level section and the same tab appears in its settings panel — alongside Review/Style/Advanced — but now it modifies the section instead of creating a new one. Select a nested component (a card in a grid, a slide in a carousel) and the tab disappears, keeping that component's own props uncluttered.
Build a single section — step by step¶
Describe one section and the AI returns up to three takes, built from your approved components. Four screens, in order:
1. Pick a starting point. Choose a section template, or switch to Describe and type a prompt (with an optional screenshot or mockup).
2. Generating. A short checklist shows what's happening; this usually takes 20-40 seconds.
3. Review. Facts only — label, component count, style chips — plus See other options if you asked for more than one take.
4. Inserted. The section lands on the canvas as a draft, with a one-click undo.
It lands after the current selection, or at the end of the page if nothing top-level is selected.
Modify an existing section instead¶
Select a top-level section and describe a change ("make this more concise", "add a call to action") instead of starting from scratch — the same Describe your change field, plus one-tap transform ideas and a list of Available components to drop straight in. The AI sees the section's current content and is asked to preserve its intent, not replace it wholesale.
Build a full page — step by step¶
Starts from a blueprint rather than a blank prompt, because a page is a sequence with structure, not one big paragraph of instructions — see Blueprints for what a blueprint actually is.
1. Templates. Pick one of the shipped blueprints (or switch to Describe and write the page freely). The recommended direction is picked for you — choose another under the template, or change it later from Review.
2. Review your selections. Every choice on one screen. Auto builds everything in the background; Guided stops after each section for your approval. Advanced holds an optional emphasis note.
2a. Sections. The section order the blueprint produces — reorder, drop sections, or override how a data-driven section (like "People") resolves.
2b. Content sources. Optionally ground the build in real pages — the AI reuses their actual words and images instead of inventing them.
A full-page build always adds to whatever page you had open — there is no separate "create a new page" step. If that page already has at least one section, clicking Build page stops first with "This page already has content," naming the page and asking you to confirm before it adds more; a blank page skips straight past this. Clicking Build page may then also ask you to confirm anything that looks data-driven, before it starts building:
A quick sanity check for any section that looks like it should pull from a View, before the build spends a call on it.
Guided mode: each section lands on the canvas, then waits for Approve, Modify (with a note), regenerate, or Skip.
4. Done. Every section's status, plus a single Undo entire build that restores the pre-build page exactly.
Auto skips the per-section checkpoints and runs straight through the same sequence — useful once you trust the plan from Review and just want the page built.
Blueprints¶
A blueprint is a page recipe — a config entity, exportable and diffable like any other. Eight ship in-box: About us, Campaign landing, Contact & visit, Department page, Event landing, Program landing, Cinematic and Cinematic Long-form.
Each section is generated on its own
A build makes one model call per section, so a section's guidance knows nothing about what any other section produced. Guidance phrased as "if the hero did not already take the animation" or "use a different image from the gallery" cannot be evaluated and is answered optimistically every time. Write budgets as fixed assignments ("the opening statement takes the reveal; the turn statement never animates"), and enforce anything that genuinely needs cross-section state in code. Two such rules ship today: the per-build image ledger, so no photo appears in two sections, and the per-build band rhythm, which drops a section's background colour when it repeats the section before it — asking for alternation in prose produced three identical tinted bands in a row, because no section could see its neighbour's choice.
The portability rule is the important part:
Blueprints never name your components
A blueprint declares abstract capabilities — hero, card_grid,
stats, faq, cta, people — not SDC machine names. A resolver maps
each capability to whatever your site's approved component set can
actually provide. The same blueprint works on any site, with any theme,
with any component library.
Blueprint
├── category selection grouping
├── best_for[] hints that match a request to this blueprint
├── sections[] the section pool — the hard structure
│ ├── intent what the section is for
│ ├── capabilities[] semantic archetypes, from an extensible registry
│ ├── fallback_capabilities[]
│ └── optional may be pruned if it does not fit the request
└── variations[] ordered arrangements + the soft steering
├── tone e.g. "Urgent, action-first"
├── emphasis how to build this variation
└── section_order[]
Sections are the hard rules; variations are the steering. A variation draws from the blueprint's own section pool — it can reorder and omit, but never invent a new section. One call-to-action may echo earlier in the order, which is how "CTA early and often" landing pages work without opening the door to arbitrary duplication.
In the Review screenshot above, "About Us / Story-driven" is the blueprint
and the variation the Choose step auto-picked — the Change link
reopens the variation picker without losing your content-source choice, and
the Page plan list below it is exactly that variation's section_order,
rendered as a draggable, editable list rather than a config file.
Capability resolution¶
Each section resolves against your approved components and reports a status the Review step shows you before anything is built:
| Status | Meaning |
|---|---|
| 🟢 Green | A primary capability matched a real component. |
| 🟡 Amber | Resolved through a fallback, or a custom one-off — best effort. |
| 🔴 Red | Nothing on this site can build it. |
Capabilities come from a registry, not a hardcoded list — adding one is a config entry plus an optional hook, not a core change.
Content¶
The full page build's Content step (screenshot above) decides what the copy is made of:
- AI copy — written for the page.
- Placeholder — structure now, words later.
- Site name & tagline — the safest real-content source.
Grounding goes further: pick published pages as reference material and the build uses their actual text and images. The picker searches your pages, and one tap adds the page you are editing.
Two things worth knowing about grounding:
- The client sends only references, never content. The server resolves them, so grounding stays server-authoritative.
- Grounded material is framed to the model explicitly as data, not instructions, so nothing a referenced page happens to say can redirect the build.
Images are found by walking entity reference fields down to a media entity with a populated image field. A plain image field directly on the node, or a media item only embedded inline in a text field, is not visible to that walk.
Where the AI can put an image¶
Two places, and the difference matters:
- A component's own image prop — a card's picture, a split band's media
column. The model writes the url into
inputs; anobject-typed prop takes{"src": "<url>"}. - Any component's background — the
background_imagestyle. This is what lets a plain Section become a full-bleed banner without depending on a hero component being installed, so a cinematic opening is composed from Section + background image + overlay tint + a text component rather than from one big SDC. The model is told to always pair it with the overlay tint, because that is what keeps a headline readable over a photograph.
Background video is deliberately not offered to the model: nothing grounds it with real video urls, so the key could only produce invented ones. Editors set video backgrounds themselves.
A build only ever places images the server handed it in that section's AVAILABLE IMAGES block, and no image appears in more than one section of a page — the per-build ledger excludes anything an earlier section already took, and the stock provider is paged deeper to keep later sections supplied.
Stock photo providers¶
Real photography comes from a pluggable provider subsystem. Unsplash and Pexels ship with the module; each stays inactive until you add its API key on the AI settings page, and with none configured a build simply gets no AVAILABLE IMAGES block rather than failing.
The model never talks to a provider. The server searches, picks candidates for the section it is about to build, and hands over a fixed list — which is what makes the "only places images the server handed it" rule enforceable rather than a request in the prompt.
Adding another source is a plugin: extend StockPhotoProviderBase, declare the
credentials it needs, and it appears alongside the shipped two.
Motion¶
With canvas_builder_motion enabled, a build can also author the section's
timeline — which elements move, in what order, and on what trigger.
The vocabulary is not a list the AI module keeps: it is read from the motion submodule, so an effect you disable, or one a target cannot support, is not something the model can propose. Timelines are written through the motion module's public API, which validates every target and action and rejects a bad plan rather than persisting it.
With the submodule disabled, nothing here applies and no motion keys are produced.
Governance¶
This is the part that makes the feature safe to turn on.
- An allowlist, not the whole catalogue. Only components you approve can be used.
- Server-side validation. Every plan is validated against the real component schemas before it becomes page content. An invalid plan is rejected, not repaired into something plausible.
- Structural guardrails. Caps on sections per page, components per page, and slot nesting depth.
- Two-layer kill switch. The feature can be disabled wholesale, and each workflow (section transform, content grounding) toggles independently.
- Placeholder URLs are stripped. Stock-photo placeholder services are rejected as fake content rather than silently shipped.
- Permissions. Building requires the
use canvas_builder aipermission plusupdateaccess on the page itself, and every request carries a CSRF header token.
Cost¶
Token usage is recorded per generation and surfaced on a usage dashboard, with per-model input/output pricing configurable in settings. A full-page build is one model call per section, not one call for the page.
Configuration → Content authoring → Canvas Builder → Usage — every generation logged, filterable by action and date, with a per-model cost estimate.
Setup¶
The submodule hard-depends on three contrib projects, none of which the root
composer require pulls in — they are suggest-only so a styling-only site
never carries the AI stack:
composer require drupal/ai drupal/ai_agents drupal/key
drush en canvas_builder_ai
Then, in order:
- Configure a provider in the AI module.
- Turn the feature on —
canvas_builder_ai.settings: enabledships asfalse, so the panel stays hidden until you enable it under Canvas Builder → AI. - Approve the components the generator may use.
- Grant
use canvas_builder aito the roles that should see the panel.
Two operation types matter:
chat_with_tools— the section-create path, which uses native function calling. It must be a tools-capable model.chat_with_structured_response— everything else.
Check your configured models are current
A provider default pointing at a retired model fails at invoke time with an error that does not obviously name the cause. If generation fails immediately and consistently, verify the model ids in the AI module's settings before looking anywhere else.
Stock photography (optional). Add an Unsplash or Pexels API key on the AI settings page to let builds place real imagery. Without one, sections are built without photographs rather than with invented urls.
Config keys without an admin form¶
The settings page covers the rest; these are drush config:set only:
| Key | Purpose | Default |
|---|---|---|
canvas_builder_ai.settings: generation.max_prompt_length |
Longest editor prompt accepted, in characters. | 2000 |
canvas_builder_ai.settings: generation.http_timeout |
Provider HTTP timeout, in seconds. | 60 |
canvas_builder_ai.settings: guardrails.max_slot_depth |
Deepest slot nesting a generated plan may use. | 5 |
canvas_builder_ai.settings: pricing.models |
Per-model rate rows ({model, input, output, cached}) that win over the synced OpenRouter rates and pricing.default. |
{ } |
canvas_builder_ai.components: components.N.allowed_props / allowed_slots / workflows / capabilities |
Per-component allowlists; empty means everything. The components form carries them through unchanged. | (absent) |
drush config:set canvas_builder_ai.settings generation.http_timeout 120
Full contracts, endpoints and extension points:
modules/canvas_builder_ai/docs/AI.md and
modules/canvas_builder_ai/docs/SECTION-BLUEPRINTS.md.













