---
title: "Publish a new immutable version"
method: POST
path: "/v1/templates/{name}/versions"
tags: ["Templates"]
---

# Publish a new immutable version

`POST /v1/templates/{name}/versions`

## Request

- Base URL: `https://api.galleyrender.com`
- URL: `https://api.galleyrender.com/v1/templates/{name}/versions`
- Auth: HTTP bearer

Earlier versions keep rendering byte-for-byte as they did. Renders cached against the old version stay valid; the new version starts with a cold cache.

**Fields you leave out are inherited from the previous version, not reset.** `engine`, `schema`, `options`, `expected_pages` and `example` all carry forward, so `{"source": "…"}` publishes a new body against the same renderer and the same validation contract. Send a field to override it, and send an empty value (`{}` for `schema` or `options`, `null` for `example`) to clear it. `source` is always required, and `message` is never inherited — it is the note for this version.

## Path parameters

- `name` string, required

## Request body

- TemplateVersionInput
  - `name` string, required
  - `source` string, required: One self-contained HTML document with inline CSS and Liquid expressions. No file includes.
  - `engine` 'chromium' | 'satori'
  - `schema` object: JSON Schema for the `data` payload. Strongly recommended: it is what turns a bad payload into a field-level error.
  - `options` RenderOptions: Merged over the template's own defaults. Options are part of the cache key, so two calls that differ only here are two different renders.
    - `page_size` string: PDF page size: `Letter`, `A4`, `Legal`, or `210mm x 297mm`.
    - `landscape` boolean: PDF only.
    - `margin` union: PDF margins as a CSS length, or per side. Each value is a non-negative number with an optional unit (in, px, cm, mm) — these four and no others, because they are the four Chromium takes. A bare number is pixels: 48, "48", "48px" and "0.5in" are the same margin. A value that is not a length, or a negative one, is refused with 400 invalid_request before the render starts, as are margins larger than the sheet — the one named by page_size, or the largest Chromium knows when there is none, since the page then comes from the template's own CSS. Margins too large for the page a template actually prints on come back as render_failed 422 with cause template.
      - string
      - number
      - object
        - `top` union
          - string
          - number
        - `right` union
          - string
          - number
        - `bottom` union
          - string
          - number
        - `left` union
          - string
          - number
    - `width` integer: Viewport width in px for png/jpg. Default 1200.
    - `height` integer: Viewport height in px for png/jpg. Default 630.
    - `scale` number: Device pixel ratio for png/jpg; a CSS zoom on PDF, where it is capped at 2. Values above the cap are clamped, not rejected, and the cap is 3 on both engines.
    - `full_page` boolean: png/jpg: capture the whole document, not just the viewport. The capture is limited to 16,384 device pixels on either side, measured after `scale`; a larger page is refused with render_failed 422 and cause `template`.
    - `quality` integer: jpg only. Default 90.
    - `background` string: CSS colour painted under the document.
    - `print_background` boolean: PDF: print background colours and images. Default true.
    - `css` string: Extra CSS appended after the template's own styles.
    - `on_blocked_asset` 'fail' | 'skip': What to do when an image, font or other subresource is refused by the egress policy or fails to fetch. `fail` fails the render with `asset_blocked` naming the URL; `skip` drops the asset and renders without it. Both engines behave identically.
  - `expected_pages` integer: How many PDF pages a typical payload renders. This is the pre-flight estimate the free tier and the spend cap are checked against, so a multi-page template must declare it or a render that cannot fit the allowance will be allowed to start. It is not a limit: the render is metered on the pages it actually produced, and an under-estimate is corrected afterwards from that count. Ignored for png/jpg, which are always one unit.
  - `example` unknown
  - `description` string
  - `message` string: Change note for this version, like a commit message.

## Response `201`

Published.

- Template
  - `object` 'template', required
  - `id` string, required
  - `name` string, required
  - `description` string, nullable
  - `latest_version` integer, required
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required
  - `version` integer: Present when a version was resolved.
  - `ref` string
  - `engine` 'chromium' | 'satori'
  - `schema` object, nullable
  - `options` object, nullable
  - `expected_pages` integer: The version's declared PDF page estimate.
  - `example` unknown
  - `source` string
  - `checksum` string: SHA-256 of engine, source, schema and options. Part of the render cache key.
  - `message` string, nullable
  - `version_created_at` string, date-time
  - `origin` 'library' | 'import' | 'own': On `GET /v1/templates`: a starter from the library, a template saved from a document import, or one of the account's own.

## Other responses

- `400`: `invalid_request`.
- `401`: `authentication_error`.
- `404`: `not_found`.

---

[API](https://skmtc.dev/galleyrender/apis/galley-render-api.md) · [All operations](https://skmtc.dev/galleyrender/apis/galley-render-api/llms.txt) · [OpenAPI document](https://skmtc.dev/galleyrender/apis/galley-render-api/revisions/a85d68468cd2?raw)
