# Content templates

**Goal:** make [components](/start-here/glossary/#component) render your CMS content. A [content template](/start-here/glossary/#content-template) renders every entity of one [content type](/start-here/glossary/#content-type-bundle) through a component composition, with [props](/start-here/glossary/#prop) 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

- A content template for the `article` content type, versioned in your project as `content-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

- A component project connected to your [site](/start-here/glossary/#site) with working Canvas CLI credentials ([Canvas quickstart](/source-cms/canvas-components/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](https://docs.acquia.com/acquia-source-cms/creating-new-content-type) on docs.acquia.com; to add or change its fields, see [Managing fields](https://docs.acquia.com/acquia-source-cms/managing-fields). Templates bind to fields that already exist.

## Steps

<Steps>

1. ### Create the template in the Canvas editor

   Templates are created in the editor, then maintained as code. In [Drupal Canvas](/start-here/glossary/#drupal-canvas), open the `Templates` panel in the left toolbar, then `Add 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.

2. ### Pull the template into your project

   ```bash
   npx canvas pull
   ```

   The scaffold's `canvas.config.json` syncs content templates by default (`sync.contentTemplates: true`), into `contentTemplatesDir` (`content-templates/`). The new template arrives empty:

   ```json
   // content-templates/node.article.full.json
   {
     "label": "Article content items — Full content view",
     "entityType": "node",
     "bundle": "article",
     "viewMode": "full",
     "elements": {}
   }
   ```

   `elements` uses the same shape as page specs: keys are element UUIDs, each element has a `type` (`js.<machineName>`), `props`, and optional `slots`.

3. ### Discover what you can bind to

   Which fields can feed which props is site-specific. The CLI pulls the authoritative list:

   ```bash
   npx canvas agents-context
   ```

   (That is the `@drupal-canvas/cli` 0.20.1 form, the version the scaffold pins; from 0.21 on, the command takes a provider argument, and `npx canvas agents-context --all` runs every default provider.) The context lands in `.agents/drupal-canvas/` (built for AI coding agents, equally readable by you; the directory is `.gitignore`d), 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 `heading` component on `node`/`article`:

   ```json
   "js.heading": {
     "heading": [
       {
         "label": "Title",
         "source": {
           "sourceType": "entity-field",
           "expression": "ℹ︎␜entity:node:article␝title␞␟value"
         }
       }
     ]
   }
   ```

   The `expression` string is an opaque field pointer (the separators are control-picture characters). Never compose one by hand; copy it verbatim from `prop-sources.json`.

4. ### Bind props to fields in the template

   Add elements to the template's `elements` map. A prop's value is either a literal (a plain string, number, boolean, or object) or a **prop-source object** copied from `prop-sources.json`:

   ```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 `processed` property (rendered HTML), not `value` (raw stored text); `prop-sources.json` labels them (`Body` vs. raw variants).

5. ### Validate and push

   ```bash
   npx canvas validate
   npx canvas push
   ```

   `validate` checks templates alongside components (`Content templates / Valid: node.article.full`); `push` uploads the template with the `canvas:content_template` [scope](/start-here/glossary/#scope). Command flags for both are in the [Canvas CLI reference](/source-cms/reference/canvas-cli/).

6. ### 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.

</Steps>

## 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

- [Version pages and global regions](/source-cms/canvas-components/pages-and-regions/): the rest of the site structure as code.
- [Canvas CLI & schema reference](/source-cms/reference/canvas-cli/): `pull`, `push`, `validate`, and `agents-context` flags.
- [Component development](/source-cms/canvas-components/guide/): the props and slots the template binds into.
