Content templates
Goal: make components render your CMS content. A content template renders every entity of one content type through a component composition, with props bound to entity fields instead of values an editor types in.
Until now, props held static values. But an article’s title shouldn’t be typed into a heading component per page; it should flow from the title field of each article. That binding is what a content template holds.
What you’ll have when you’re done
Section titled “What you’ll have when you’re done”- A content template for the
articlecontent type, versioned in your project ascontent-templates/node.article.full.json. - A component prop bound to an entity field, using the binding expressions your site actually exposes.
- Every published article rendering through your template at its own URL.
Prerequisites
Section titled “Prerequisites”- A component project connected to your site with working Canvas CLI credentials (Canvas quickstart)
- At least one content type with content on the site (this guide uses
article)
To create a content type, see Creating a new content type on docs.acquia.com; to add or change its fields, see Managing fields. Templates bind to fields that already exist.
-
Create the template in the Canvas editor
Section titled “Create the template in the Canvas editor”Templates are created in the editor, then maintained as code. In Drupal Canvas, open the
Templatespanel in the left toolbar, thenAdd new template. Pick the content type (Article) and the view mode (Full content), and confirm. The editor opens the new, empty template at/canvas/template/node/article/full/.There is no admin page for this; the Templates panel is the only place templates are created or deleted.
-
Pull the template into your project
Section titled “Pull the template into your project”Terminal window npx canvas pullThe scaffold’s
canvas.config.jsonsyncs content templates by default (sync.contentTemplates: true), intocontentTemplatesDir(content-templates/). The new template arrives empty:content-templates/node.article.full.json {"label": "Article content items — Full content view","entityType": "node","bundle": "article","viewMode": "full","elements": {}}elementsuses the same shape as page specs: keys are element UUIDs, each element has atype(js.<machineName>),props, and optionalslots. -
Discover what you can bind to
Section titled “Discover what you can bind to”Which fields can feed which props is site-specific. The CLI pulls the authoritative list:
Terminal window npx canvas agents-context(That is the
@drupal-canvas/cli0.20.1 form, the version the scaffold pins; from 0.21 on, the command takes a provider argument, andnpx canvas agents-context --allruns every default provider.) The context lands in.agents/drupal-canvas/(built for AI coding agents, equally readable by you; the directory is.gitignored), two files of it relevant here:prop-sources.json: for each entity bundle and component, every bindable prop and the exact binding expression for each candidate field.view-modes.json: the view modes each bundle exposes (and therefore which templates can exist).
An excerpt for the
headingcomponent onnode/article:"js.heading": {"heading": [{"label": "Title","source": {"sourceType": "entity-field","expression": "ℹ︎␜entity:node:article␝title␞␟value"}}]}The
expressionstring is an opaque field pointer (the separators are control-picture characters). Never compose one by hand; copy it verbatim fromprop-sources.json. -
Bind props to fields in the template
Section titled “Bind props to fields in the template”Add elements to the template’s
elementsmap. A prop’s value is either a literal (a plain string, number, boolean, or object) or a prop-source object copied fromprop-sources.json:{"label": "Article content items — Full content view","entityType": "node","bundle": "article","viewMode": "full","elements": {"a1b2c3d4-0000-4000-8000-000000000001": {"type": "js.heading","props": {"heading": {"sourceType": "entity-field","expression": "ℹ︎␜entity:node:article␝title␞␟value"},"headingElement": "h1","headingSize": "extra_large","textColor": "dark","layout": "left_aligned"}},"a1b2c3d4-0000-4000-8000-000000000002": {"type": "js.text","props": {"text": {"sourceType": "entity-field","expression": "ℹ︎␜entity:node:article␝body␞␟processed"},"textSize": "normal","textColor": "dark"}}}}Two details that break a template:
- Fill in every prop the component defines, not just the bound ones. The server rejects a template element that omits a prop key, even one the component schema doesn’t mark required (
'textColor' is a required key.). Give unbound props literal values. - For rich-text fields, bind the
processedproperty (rendered HTML), notvalue(raw stored text);prop-sources.jsonlabels them (Bodyvs. raw variants).
- Fill in every prop the component defines, not just the bound ones. The server rejects a template element that omits a prop key, even one the component schema doesn’t mark required (
-
Validate and push
Section titled “Validate and push”Terminal window npx canvas validatenpx canvas pushvalidatechecks templates alongside components (Content templates / Valid: node.article.full);pushuploads the template with thecanvas:content_templatescope. Command flags for both are in the Canvas CLI reference. -
Confirm with real content
Section titled “Confirm with real content”Open any published article at its own URL. The title renders through your heading element, the body through your text element; every article on the site now flows through the same template. Editing the template and pushing again restyles them all at once.
When something goes wrong
Section titled “When something goes wrong”[layout.0.inputs] 'textColor' is a required key. (from canvas push)
a template element omits a prop the component defines. Add the missing keys with literal values (step 4).
A bound rich-text prop renders empty, but the field has content
the entity’s stored text format doesn’t exist on the site, so the processed output is empty. Check the entity’s body field format against the site’s configured formats.
The template pulls but your content type isn’t in prop-sources.json
re-run npx canvas agents-context after any field or content-type change on the site; the files are a snapshot, not live.
You want a template for a view mode that isn’t offered
the Add new template dialog only lists view modes the bundle exposes (see view-modes.json). A view mode has to exist on the bundle before a template can target it; enable it for the bundle on the site, then re-run npx canvas agents-context so view-modes.json picks it up.
Next steps
Section titled “Next steps”- Version pages and global regions: the rest of the site structure as code.
- Canvas CLI & schema reference:
pull,push,validate, andagents-contextflags. - Component development: the props and slots the template binds into.
Was this page helpful?
What went wrong?
Still stuck? Contact Acquia Support (opens in a new tab)