Templates
Templates: content types, with real schema migration
A Template (TemplatesEndpoint) is P1’s Content Type. See
Concepts for the WordPress/Drupal framing.
What’s worth calling out separately here is the migration tooling, because it’s more
capable than what either WordPress or Drupal ships natively for evolving a content
type’s structure after real content already exists against it:
deprecate/reactivate: soft-disable a template for new document creation without touching documents already built from it. Closest WordPress analog: hiding a CPT from the “Add New” menu without deleting existing posts.migrate: apply a structural change (e.g. a template’s component tree changed) to every document derived from that template, as an async job.previewMigration: a dry-run, with an optional per-document breakdown, before you commit to a migration that could touch hundreds of pages.rollbackMigration: undo a completed migration job by ID.getMigrationJob: poll job status, since migration runs async.
const preview = await client.templates.previewMigration(
siteId, branchId, templateId, migrationParams, /* detail */ true
);
// review preview.affectedDocuments or similar before committing
const job = await client.templates.migrate(siteId, branchId, templateId, migrationParams);
// job is async, poll:
await client.templates.getMigrationJob(siteId, branchId, job.id);Neither WordPress (manual register_post_type changes, no built-in dry-run/rollback)
nor Drupal (Update API / Field API, powerful but code-first and not self-serve) has a
close equivalent to previewing a content-type structural change against real content
before committing, with a one-call rollback if it goes wrong.
Can a template’s layout be defined in code?
Metadata yes, layout no, by design, not by omission.
CreateTemplateParams and UpdateTemplateParams (the actual TypeScript types
TemplatesEndpoint.create()/update() accept) only cover name, label,
description, defaultUrlPattern, and deprecated. That’s confirmed straight from
@pantheon-systems/css-client’s own type definitions, whose doc comment on
CreateTemplateParams says outright:
Parameters for creating a template. Metadata only; the layout is authored on the editor canvas afterwards.
The actual component tree (content, root.props._pinMap, zones) appears on the
read side (Template), but there is no corresponding write parameter for any of it
on either create() or update(). Concretely: you can script “create a template named
product with this label and URL pattern” in CI, but the pinned/editorial component
layout itself has to be built by hand in the Puck editor. There’s no
register_post_type()-with-fields equivalent, no Drupal-style config-as-code YAML
export for a content type’s field structure.
Templates are documented elsewhere as being stored as CCR documents at
_registry/templates/{name}, and the generic VersionsEndpoint.create() can write a
new version to any document given its ID. It’s architecturally plausible that
writing a Puck-shaped snapshot directly to a template’s document ID would work as an
unofficial code-first path. But doing so would bypass whatever pin-map integrity and
migration-tracking checks the dedicated Templates API deliberately layers on top, and
we have not tested it. Don’t rely on this without confirming it with Pantheon first;
it’s a plausible mechanism, not a supported one.
Dynamic (templated) routes
A template can define a dynamic route using colon segments: /company/leadership/:name
(verified directly in route-templates.js). Each
:param-shaped path segment matches any single URL segment at that position; a
concrete URL is matched against the template pattern and its params extracted
(matchConcretePathToTemplateParams). If a static document exists at a path that
would otherwise match a dynamic template, the static document always wins. See
Concepts for the WordPress/Drupal framing of
that resolution order.