Skip to Content
WorkflowsTemplates

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.

Last updated on