Paragon

Report template format — schema 1.0

The contract between the designer and the engine. A consuming application never reads this: it copies the folder, references the package, and calls GenerateAsync(reportCode, json).

A report is a folder of pages, and a page on disk is the fragment its author wrote. The engine merges the template into it as it loads, so what sits in a repository is what a person typed once rather than the same template copied into every page by a tool.

The folder

wwwroot/ParagonTemplates/           configurable (ParagonOptions.RootPath); published out of wwwroot
└── Acme-ERP/                       one folder per project, named {name}-{code}
    ├── shared-assets/              this project's, reachable from every report below
    │   ├── css/house.css
    │   ├── fonts/Roboto-Regular.woff2
    │   ├── images/
    │   └── js/charts/index.js      the chart library, held once for all of them
    ├── R-acme-erp-003/             one folder per report, named after its code
    │   ├── template.html           what every page of this report shares
    │   ├── assets/                 this report's own; searched before shared-assets
    │   │   ├── css/report.css      created with the report, linked by the template
    │   │   ├── fonts/
    │   │   ├── images/logo.png
    │   │   └── js/
    │   ├── p1.html                 the pages, flat beside the assets they reference
    │   ├── p2.html
    │   └── p3.html
    └── R-acme-erp-004/            a report authored in Word, so its folder holds a document
        ├── template.docx           — or template.pptx
        ├── manifest.json           beside the file, because a document has nowhere to carry one
        ├── assets/
        └── parts/                  HTML pages the document draws its pictures from

A report written in Word or PowerPoint

An office template report is the second shape, and the only one where the manifest is a file of its own: everything below describes the same fields, written to manifest.json rather than stamped into markup. It has no pages and no page setup — the document carries its own. See Reports authored in Word or PowerPoint.

How a folder is read

  • A folder holding a template is a report; a folder holding none is a project. Decided per folder rather than by a setting, so a root that is a flat list of reports is read as it always was.
  • The folder name is the report code. Nothing parses a code out of a file name, which is why the template is simply template.html in every report. ParagonOptions.TemplateFileNamePattern can change that, and a pattern containing {code} is still substituted.
  • An asset is looked for in the report's own folder first, and in the project's shared-assets behind it. A report needing a different version of a shared file keeps its own copy at the same path and overrides it for itself. Exporting a report flattens the two, so the zip stands alone.
  • A new report and a shared-assets folder both start with css, fonts, images and js — so a path written into one report's markup means the same thing in the next, and moving a file into the shared folder does not change what refers to it.
  • Report codes may contain letters, digits, dots, dashes and underscores, up to 100 characters, matched case-insensitively. Anything else is rejected before it reaches the file system.

Pages

A report is a list of pages, rendered in order and concatenated into one document. Each page is its own print job, which is what lets one page be landscape while the next is portrait.

A page is a unit of authoring, not a unit of output. A page whose content runs long paginates into two or three output pages, handled at generation time. That is also where the one real trap lives — a background that paints on the first sheet and nowhere after it. See Authoring a report.

Every page becomes a complete, standalone HTML document, built from the template and the fragment. The file on disk is the fragment: the manifest, the assets and everything else the pages share belong to the template, named there once.

Pages are numbered by position — p1.html, p2.html, and so on. Deleting or reordering renumbers whatever moved. A page's id is separate from its file name and never changes, which is what keeps a page's own setup with its content when it moves.

The template

A complete HTML page holding everything the report's pages share: stylesheets, scripts, fonts, the bound data island, and the manifest. Pages register nothing of their own — they inherit it.

<script src="assets/js/charts/index.js"></script>
<link rel="stylesheet" href="assets/css/site.css">

Assets are referenced with relative paths exactly as written, and pages sit beside the assets folder rather than under a sub-folder, so those paths resolve when a page is opened straight from disk. Nothing is rewritten at export.

Two more inert islands, in the body beside the rest of the report's furniture. A new report is created with both present and empty, because an empty island means none. A page that declares its own overrides the template's for that page alone.

<template id="report-header"></template>

<template id="report-footer">
  <div style="font:9pt sans-serif">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>
</template>

The header is placed in the page, fixed to the top of the sheet — and a fixed element is repainted on every sheet, which is what repeats it. Being in the document, it is styled by the report's stylesheet, its images load, and it inherits the report's direction.

The footer is handed to the browser's print chrome, because a stylesheet cannot count sheets. It renders as a document of its own, so the stylesheets the template registers are read at export and travel with it — a footer is styled in report.css like the rest of the report. What it will not do is paint a background or draw an image, and a data: URI does not get round it. Text is what a footer can hold; put a logo in the header island, which is placed in the page.

At export each island is lifted into formats.pdf.headerHtml / footerHtml and removed from the file that ships, so the markup exists once rather than twice.

The manifest

An inert JSON island — browsers neither execute nor render it — carried by the template. A page file carries none: the template's manifest is the only one, and it is what declares the pages.

<script type="application/json" id="report-manifest">
{
  "schemaVersion": "1.0",
  "projectCode": "ERP",
  "projectAssetsVersion": "3.0",
  "reportCode": "R-acme-erp-003",
  "name": "Customer Invoice (Arabic)",
  "templateVersion": 7,
  "exportedOnUtc": "2026-08-14T10:22:00Z",
  "direction": "rtl",
  "outputFormat": "pdf",
  "awaitsReadySignal": true,
  "pages": [
    { "id": "a3f9c1d20e77", "file": "p1.html" },
    { "id": "b81e04aa9c31", "file": "p2.html",
      "formats": { "pdf": { "orientation": "landscape", "pageSize": "A3" } } },
    { "id": "c72d55b1f004", "file": "p3.html" }
  ],
  "requiredAssets": [
    { "path": "assets/js/charts/index.js", "sha256": "…" }
  ],
  "formats": {
    "pdf":        { "pageSize": "A4", "orientation": "portrait",
                    "printBackground": true, "scale": 1.0, "footerHtml": "…" },
    "word":       { "inherits": "pdf" },
    "powerpoint": { "inherits": "pdf" }
  },
  "sampleData": { "customer": { "name": "…" } },
  "integrity":  { "algorithm": "SHA-256", "templateHash": "…" }
}
</script>

The fields

Field Required Notes
schemaVersion yes 1.0. See compatibility below
reportCode yes Should match the folder name; the folder name is what resolves
pages yes Ordered. id is durable identity, file the positional name, name a label, formats this page's setup alone. Absent for an office template report, which has no pages
authoring no html (default), or template for a report written in Word or PowerPoint — see Reports authored in Word or PowerPoint
name no What the designer calls the report
projectCode, projectAssetsVersion no The project it belongs to, and the generation of shared assets it was approved against
templateVersion no Raised on each export
exportedOnUtc, engineVersion, browserVersion no Stamped at export; provenance only
direction no ltr (default) or rtl
outputFormat no pdf (default), word, powerpoint. The report chooses it, never the caller
awaitsReadySignal no true when a page finishes drawing asynchronously — see Waiting for several asynchronous things
requiredAssets no Verified at load, with a sha256 each when VerifyAssetHashes is on
formats no pdf, word, powerpoint. Page-level formats are layered over these
sampleData no Shape documentation for consumers; also the designer's preview payload
integrity no SHA-256 of the file, checked when VerifyTemplateIntegrity is on

A format holds pageSize, widthMm and heightMm, orientation, scale, printBackground, headerHtml, footerHtml, imageBasedPdf, and inherits.

Page setup resolves nearest-first

page.formats[format] → its inherits chain → formats[format] → its inherits chain → defaults (A4 portrait). A page that specifies nothing renders exactly as the report does.

Margins are not a field. The engine writes @page { margin: 0 } and the edges are the template's to pad — see Pagination and right-to-left.

Named sizes are A3, A4, A5, B4, B5, Letter, Legal, Tabloid and Ledger, plus the slide sizes Widescreen, OnScreen16x9, OnScreen16x10, OnScreen4x3, Overhead, Slide35mm and Banner. Anything else needs widthMm and heightMm.

Failures at load

All of these are raised before a document is produced, never during it — a report that cannot be rendered correctly must not produce a document that looks fine and is wrong.

Condition Exception
No folder for the code ReportNotFoundException (suggests the nearest code)
Code contains anything outside the allowed charset InvalidReportCodeException
Two folders claim one code (case-sensitive file systems) DuplicateReportCodeException
No manifest island, malformed JSON, or no pages declared ReportManifestException
Report needs a newer schema than the engine reads TemplateSchemaVersionException
A page the manifest lists is not in the folder MissingReportPageException (lists every one)
A required asset is not on disk MissingReportAssetException (lists every missing path)
An asset's hash differs from export CorruptReportAssetException
The template's own hash differs from export TamperedTemplateException
Unknown page size, or an inherits that is unknown or too long ReportFormatException
Deployed assets are a different generation from the approved ones AssetsVersionMismatchException

All derive from ParagonException.

This is loading only. The caller's data and the composing of the file come after it, and their failures — ReportDataException, TemplateBindingException, ReportRenderTimeoutException, EmptyWorkbookException — are in Quick start for a consuming application.

Compatibility

The engine supports schema 1.0 and accepts anything at or below it within the same major. Within a major version new fields get defaults and nothing is renamed or removed, so a report written today keeps working on later 1.x engines.

A report needing a newer major, or a newer minor than the engine reads, is refused outright with a message saying to upgrade the package. Reading the parts of it that happen to be recognised is how a report comes out looking right and being wrong.