Pulp Engine Document Rendering
Get started

Template Format Compatibility

How Pulp Engine versions the template definition format, what compatibility you can rely on across upgrades, and how to pre-flight stored templates before upgrading a server.

The format generation is distinct from a template’s version field. version is the content semver — it bumps every time you edit a template. formatVersion is the format generation — it identifies which generation of the definition format the JSON document is written in.

The promise: additive-only within a format generation

Within format generation 1 (every release to date), schema changes are additive only:

  • New node types, new optional fields, and new enum values may be added.
  • Existing fields are never removed, renamed, or re-typed.
  • A definition that parsed under an older server parses under every newer server in the same generation.

This is enforced in CI by a frozen fixture corpus (apps/api/src/__tests__/fixtures/compat/ — including a production seed template from v0.18.0 and representative starter-pack builds). Those files are committed once and never regenerated; compat-fixtures.test.ts asserts they parse under the current save-boundary schema on every build. A schema change that breaks any frozen fixture cannot merge.

The reverse direction is not promised: a template that uses a node type introduced in v0.85.0 will be rejected by a v0.60.0 server. Downgrading a server below the version that authored a template is unsupported.

formatVersion semantics

ValueMeaning
absentFormat 1. Every definition saved before v0.85.0 omits the field; every format-1 reader treats absence as 1.
1Format 1, stamped explicitly. The API stamps formatVersion: 1 at the save boundary (create and update) from v0.85.0 onward, so all newly-saved definitions carry it.
2+Reserved for a future breaking format change. A format-1 server rejects such documents loudly (HTTP 400 with a formatVersion issue) instead of silently misreading them.

Notes:

  • The stamp covers every save path — hand-authored API calls, the visual editor, starter packs, and AI generation all pass through the same POST /templates / PUT /templates/:key validation and stamping.
  • Version restore is a faithful clone. Restoring a pre-v0.85.0 version reproduces its stored bytes, including the absent formatVersion — which still means format 1. Restore does not rewrite history.
  • Sending formatVersion: 1 yourself is valid and equivalent to omitting it.

Restore-on-drift behaviour

POST /templates/:key/versions/:version/restore re-validates the stored historical definition against the current schema before restoring. If a historical version predates the additive-only enforcement and no longer conforms, the restore fails with 400 Stored Version Invalid and the template’s history is left untouched. The error body lists the exact issues. Because format changes are additive-only, this should not occur for any version saved on v0.18.0 or later; it exists as a fail-closed guard.

Pre-flighting an upgrade

Before upgrading a server, you can validate exported/stored template JSON offline with the CLI — it applies the same Zod schema the API enforces at the save boundary:

# Validate one file or a directory of definitions (no API required)
pulp validate ./templates
pulp validate ./my-template.json --strict

A green pulp validate from the new release’s CLI against your stored definitions means every one of them will load on the new server.

Where the schema lives

ArtifactRole
@pulp-engine/template-modelTypeScript interfaces (TemplateDefinition, node types).
@pulp-engine/template-schemaThe runtime Zod schema — single source of truth, used by the API save boundary, the CLI validate command, and the editor’s starter-pack parity test.
apps/api/src/__tests__/fixtures/compat/Frozen cross-generation corpus; grows via node scripts/freeze-compat-fixture.mjs <pack-id> <fixture-name>.