Back to Home

Compiled Templates

How ZeroExport prepares a template when you save it, how generation uses the result, and why saving a template once after upgrading makes it faster.

When you save or upload a template, ZeroExport also compiles it. The compiled template separates the layout and styles from the data bindings, and parses merge tokens, repeats, visibility rules, and aggregates once, at save time instead of on every document.


What happens at save

  • The compiled template is stored as a file on the template version and replaced on each save.
  • The version records when it was compiled and by which compiler version.
  • Compilation is all-or-nothing, and it is the step that automatically validates field mappings and rejects invalid merge tokens and structure errors.
  • Which repeating rows and groups apply is decided at save, from the shape of the template's data sources.
  • A compile failure is logged and never stops a save. Templates over 256 KB, or a save without enough CPU time left, are not compiled.

Version numbers

Since 1.11.0 a template's versions are numbered 1..n within the template. The version dropdown, the selected version in the builder and the template manager show Version N, newest first, instead of the org-wide auto number (VER-00012). A version saved before numbering keeps its auto number as its label.

The number is set when a version is saved (TemplateVersion__c.VersionNumber__c, read-only and never changed once assigned) as one more than the highest number among the template's versions. Saving a version reads and writes nothing on the template, so no lock is taken on it and a validation rule or flow on the template cannot block the save. If two saves of the same template get the same number, the later one is numbered again automatically. Deleting the newest version does not free its number while it is in the recycle bin.


What happens at generation

Documents generated from a saved version are merged from its compiled template. On templates with many rows this takes about 30–40% less Apex CPU for merging and rendering. For example, an invoice with 400 lines went from 1,123 ms to 682 ms. Small templates take about the same time as before.

Since 1.10.0, building a document takes 27–50% less Apex CPU (an invoice with 400 lines went from 243 ms to 178 ms, a quote with 200 lines from 342 ms to 190 ms) and table HTML is 13–32% smaller, with identical PDF pages. Saving also stores each node's CSS in the compiled template, so a generated document never parses a style.

Text rendering is also faster: a template of long legal text renders in about a fifth of the time it did, and templates with many short texts gain 10–50%.

The builder preview

Since 1.11.0 the builder preview merges like a generated document. With a record chosen, the canvas Preview, Download PDF, and Preview and Download on a record's ZeroExport Documents compile the template as a save does and use the compiled merge, so the preview matches the PDF. The list preview of a template with Record Scope Multiple is merged the same way. A preview with no data, and a template that does not compile, keep the legacy merge.

When the earlier merge is used instead

Generation falls back to the earlier (legacy) merge when:

  • the version has no current compiled template: it was saved before 1.9.0, never compiled, or compiled by an earlier shape of the compiler (a version compiled by 1.9.0 keeps its 1.9.0 behavior until it is saved again)
  • the compiled template's file was changed after it was compiled
  • the compiled merge fails for a document (with debug logging on, the fallback is logged)

Generation never compiles. A version keeps the legacy merge until it is saved, and an existing compiled template is not recompiled when a newer compiler is installed. A compiled template from a different major compiler version also uses the legacy merge until it is saved.


After upgrading

Open each template you want faster and save it once. Upgrading changes no template, and templates saved before 1.9.0 keep generating the way they did.

The compiler is version 1.2.0 in 1.11.0 (1.1.0 in 1.10.0). Save a template once after upgrading to get rows that share a list printing together for each item (see Iterations). In 1.10.0 the compiler added the new grouped-table total-row behavior. Save a template once after upgrading to get the new grouped-table total-row behavior (see Iterations). Check any grouped table that has a total row: after you save it, the total prints once after the groups instead of once per group.

Saving in 1.9.0 and later also applies the automatic field-mapping validation, so a template may be rejected until a token is fixed. See Merge Field Syntax.

A few rare cases print differently from the legacy merge once a template is saved in 1.9.0 or later:

  • A table grouped by a list with no rows prints its header rows only, and the grouped table's own visibility rule applies.
  • In a table grouped on three or more levels, a group with no rows at the next level prints only its own rows.
  • A repeating row whose list is missing from the data prints once.
  • A total written in upper case, such as {{Lines.SUM(Amount)}}, prints its value instead of nothing.
  • A field whose value itself contains token text prints that text as it is.

See also