Accent

Including templates

require(), include(), cms.partial() — and the editor and API lanes.


Every way one template gets into another render — from hand-written EJS to the editor to the API. The map first, then each lane:

Lane Where you write it Resolves by Best for
require() Any site/theme EJS File path, over the fork chain Fixed chrome and composition in code
include() Any EJS File path, same physical tree Quick same-folder includes
cms.partial() Document templates Entity (type-first, then partial id) Deprecated — use layout bands (docs/CHROME_COMPOSITION.md)
Editor embed The page editor (/) Content type Author-placed bands
Section insert The section chooser Content type (fragment with regions) Editable structure in the body
Layout composition The Layouts admin Visual placement Composed body arrangements
Composition API HTTP Zone id Machines: agents, importers

require()

The house mechanism for file-to-file composition. It returns a curried render function — one call resolves, the second renders:

<%- require('/site/templates/chrome/navbar.ejs')() %>
<%- require('/site/templates/chrome/navbar.ejs')({ active: 'home' }) %>
<%- require('./chrome/navbar.ejs')() %>

Path grammar — a ref must start with one of:

Prefix Meaning
/site/<rel>.ejs Absolute against the site's root chain: the site's own files first, the theme package second. A require from a theme file still picks up the site's fork of that child.
./ and ../ Relative to the requiring file, resolved over the same chain.
/accent/… App-wide shared partials.

Rules worth knowing:

  • The .ejs extension is required. A ref without it is treated as a JS module require — require('/site/utils/format.js') is also a thing, for shared helpers.
  • Bare names throw (require('chrome/navbar.ejs') is not in the grammar).
  • Props become the child's locals — the child reads locals.active. System context (cms, require, theme locals) is inherited automatically and stays out of the props bag.
  • Name the wrong directory and the error tells you where the file actually lives: "the file exists at /site/partials/… (directories are organizational; update the path)".
  • Requires are dependency-traced: editing a required file invalidates the cached renders that used it.

include()

Stock EJS, resolved relative to the including file's location on disk:

<%- include('chrome/navbar') %>
<%- include('../partials/card', { title: 'Hi' }) %>

It works, with two limits require() doesn't have: both files must live in the same physical tree (a theme file cannot include a site fork, and the fork chain is not consulted), and the child shares the including template's scope rather than getting a clean locals. Fine for a quick same-folder split; prefer require() for anything structural.

cms.partial()

DEPRECATED (2026-08-13, docs/CHROME_COMPOSITION.md). Chrome placement is per-site composition data — a layout band with explicit typeId + assetId — not a template call. New introductions are rejected at the write boundary (400 cms_partial_deprecated) and flagged by the boot guard; existing call sites keep rendering (a failed resolution now degrades to an HTML comment instead of killing the page) until phase 1 migrates them to bands. The description below documents the frozen behavior. Note the name collision: inside a partial's own render, cms.partial is that partial's context object — that is a different thing and is not deprecated.

The entity lane, available inside document templates: mount by name, not by file path.

<%- cms.partial('navbar')() %>
<%- cms.partial('chrome/footer')({ compact: true }) %>

Curried like require. Resolution is type-first:

  1. If the name is a content type, you get that type's current fragment renderer — swap the type's renderer and every mount follows — plus the type's single instance's data as base locals when exactly one instance exists. This is the "header and footer are content types" pattern: the navbar's links live as content, the template just says where the navbar goes.
  2. Otherwise the name is a partial id — a bare render of that entity's file, no instance data.

Merge order is placement-wins: instance data is the base, caller props override.

Why choose it over require(): entity mounts ride the uid, so slug renames are free; and the type-first branch carries data. Limits: it exists only in document-template render lanes (inside a partial's own render, cms.partial is that partial's context object, not a function), and file-path refs don't belong here — that's require()'s job.

Editor lanes

Covered in their own chapters; listed here so the map is complete:

  • Embeds/ in the page editor places a type-addressed band; it re-renders through the type's current renderer on every render. See Rendering content types.
  • Sections — a type whose fragment emits editable regions inserts as baked chrome with inline-editable columns. See Sections and regions.
  • Layouts — visual composition of bands around a Page-content band, bindable as a type's Content renderer.
  • Content renderer = Partial — the data page: the type's fields render through one partial as the whole body. See Rendering content types.

The machine lane

The composition verb embeds a partial or type instance into a named zone by id (primary, or any authored data-zone) — no positions, no labels. See The web API.

Choosing, in one breath

Chrome fixed by the designer → require(). Chrome managed in the CMS, possibly with content → a layout band (make it a type, place it in the type's Layout — cms.partial() is deprecated, docs/CHROME_COMPOSITION.md). Placed by authors per page → embed or section. Arranged visually → Layout. Driven by software → the API.