For the complete documentation index, see llms.txt. This page is also available as Markdown.

Multi-Page Certificate Templates

Configuring multi-page PDF certificates

OpenCRVS supports certificate templates that render as multiple PDF pages. Each page is defined as a <g data-page="N"> group inside a single SVG file.


How It Works

When OpenCRVS renders a certificate, it checks whether the SVG contains any elements with a data-page attribute. If it finds any:

  1. It selects all elements matching [data-page].

  2. Each matching element becomes a separate PDF page.

  3. The per-page height is calculated as total SVG height ÷ number of pages.

  4. Each page element is wrapped in a copy of the root <svg> (inheriting all root attributes) and the transform attribute is removed.

If no [data-page] elements exist, the entire SVG is rendered as a single page — this is the standard single-page behaviour.


SVG Dimensions

The root <svg> element must have its height set to the total height of all pages combined. The width stays the same as a single-page template.

total height = page height × number of pages

For a 2-page A4-portrait template (A4 ≈ 595 × 842 pt):

<svg
  width="595"
  height="1684"
  viewBox="0 0 595 1684"
  xmlns="http://www.w3.org/2000/svg"
>
  ...
</svg>

For a 3-page template of the same size:

The PDF page size is automatically derived:


Page Groups

Each page is a <g> element with a data-page attribute. The value can be any string — OpenCRVS just uses the presence of the attribute, not its value, to detect pages.

Convention: use data-page="1", data-page="2", etc. to match the visual order.


The Transform Pattern

Here is the most important thing to understand about multi-page SVGs:

Every page's content must be designed in the coordinate space (0, 0) to (width, pageHeight) — not in the total SVG coordinate space.

But in the combined SVG, page 2 needs to sit visually below page 1. To achieve this, each page after the first gets a transform="translate(0, offset)" where offset = (pageNumber - 1) × pageHeight:

Page
transform

Page 1

(none)

Page 2

transform="translate(0, 842)"

Page 3

transform="translate(0, 1684)"

When OpenCRVS extracts each page for rendering, it removes the transform attribute. The content then renders at its native coordinates (y=0 to y=pageHeight), filling the PDF page correctly.

Visual result in the SVG file vs PDF output:

The transform is only there so design tools (Figma, Inkscape, Illustrator) display both pages stacked vertically in a single canvas. The transform plays no role in the final PDF output.


Step-by-Step: Creating a Multi-Page Template

1. Decide your page dimensions

Pick a page size. Standard is 595 × 842 (A4 portrait in points).

2. Set root SVG dimensions

3. Wrap page 1 content

Content for page 1 uses coordinates from y=0 to y=842. No transform needed.

4. Wrap page 2 content with a translate transform

Page 2 content also uses coordinates from y=0 to y=842 (as if it were its own page). Apply transform="translate(0, 842)" to shift it visually below page 1 in the combined SVG view.

5. Add Handlebars expressions normally

All template variables and helpers work exactly the same on every page. Access $declaration, $metadata, helpers, etc. just as you would on a single-page template.


Handlebars Variables Across Pages

All template variables are available on every page — there is no per-page scoping.

Variable
Available on page 2, 3, etc.?

$declaration

Yes

$metadata

Yes

$review

Yes

$references

Yes

All built-in helpers

Yes

Custom country helpers

Yes

Handlebars is compiled once for the full SVG, then the DOM is split into pages. Variables and helpers are resolved before the split.


Important Constraints

All visible content must be inside a [data-page] group

When OpenCRVS detects [data-page] elements, only those elements are rendered into PDF pages. Anything at the root level of the SVG (outside any [data-page] group) is excluded from the output.

Move <defs> inside each page group that uses them

Design tools like Figma export <defs> (clip paths, gradients, masks) at the root SVG level. When a page is extracted, its wrapper SVG only contains the page group — root-level <defs> are not copied over. If your page content references a <clipPath> or <mask> by ID, move the <defs> inside the page group:

Page height is calculated from SVG dimensions, not content

If your height attribute is not an exact multiple of your page count, the PDF page height will be a fraction. Always set height = pageHeight × pageCount.

No page-level layout control

OpenCRVS does not support page-break hints, overflow, or automatic content flow across pages. You control exactly what appears on each page by placing content inside the appropriate [data-page] group.


Complete Two-Page Example

This minimal example shows the full structure for a two-page A4 certificate.

What happens during rendering

Step
Result

OpenCRVS finds [data-page] elements

2 groups found

PDF page height calculated

1684 ÷ 2 = 842

Page 1 extracted

<svg width="595" height="1684" ...><g data-page="1">...</g></svg> → PDF page 1

Page 2 extracted

<svg width="595" height="1684" ...><g data-page="2">...</g></svg> → PDF page 2 (transform removed)

PDF output

2-page document, each page 595 × 842

The transform="translate(0, 842)" on page 2 is removed during extraction. Page 2's content (designed at y=0..842) renders correctly on the second PDF page.

Last updated