typstgenv0.5.0typst.casoon.de

Archiving

Record how each PDF was built, prove later that nothing changed and that the PDF can be rebuilt byte for byte, and keep sources and documents in a layout that stays readable.

compile --manifest writes a record of the build; verify checks it later.

typstgen compile invoices/2026/RE-2026-001.typ --pdf-standard a-3b \
  --creation-timestamp 1790000000 --manifest archive.json
typstgen verify archive.json
ok      invoices/2026/RE-2026-001.pdf
FAILED  letters/2026/BR-2026-004.pdf
        changed: letters/2026/BR-2026-004.typ
        the rebuild differs from the recorded PDF
1 of 2 documents verified

What the manifest holds

For every PDF (the key, relative to the working directory) archive.json stores:

  • the source file and the typstgen.toml that was used,
  • the compile options: sys.inputs, PDF standards, creation timestamp, tagging, ZUGFeRD data files,
  • SHA-256 of the PDF,
  • SHA-256 of every file Typst read: imports, templates, JSON data, images, bibliographies, package files (as @preview/name:version/path),
  • SHA-256 of every font file loaded from disk (the bundled fonts are fixed by the Typst version),
  • the typstgen and Typst versions and the page count.

Recording the same PDF again replaces its entry. --pages cannot be combined with --manifest: an archive holds whole documents.

What verify checks

  1. The stored PDF still has the recorded SHA-256.
  2. The document is rebuilt with the recorded options, and every file and font it reads has the recorded SHA-256. Changed, missing and newly used files are listed.
  3. The rebuilt PDF is byte-identical to the recorded one.

Byte-identical rebuilds need a fixed date: pass --creation-timestamp (or SOURCE_DATE_EPOCH) when recording. A document recorded without one verifies only if it never uses the current date. A newer Typst version may typeset differently; verify then names both versions. Run compile --manifest and verify from the same directory, because paths are relative to it, like those in typstgen.toml. verify exits with code 1 if any document fails, so it works as a CI step; see Checks and CI for --rebuild-only and a workflow.

A PDF signed after recording still passes: signers append the signature as an incremental update, so the recorded bytes stay at the start of the file. verify finds them there and adds a note; revisions appended without a signature fail. See Signing.

Timestamp the manifest

A timestamp from an RFC 3161 timestamp authority (TSA) proves that archive.json, and with it every SHA-256 it lists, existed at a given time. It changes no PDF. With OpenSSL 3 or later (the LibreSSL that ships with macOS cannot verify the certificate chain; use Homebrew’s openssl):

openssl ts -query -data archive.json -sha256 -cert -out archive.tsq
curl -s -H "Content-Type: application/timestamp-query" --data-binary @archive.tsq \
  http://timestamp.digicert.com -o archive.json.tsr
openssl ts -verify -data archive.json -in archive.json.tsr -CAfile /etc/ssl/certs/ca-certificates.crt
Verification: OK

The CA file is the system bundle: /etc/ssl/certs/ca-certificates.crt on Debian and Ubuntu, on macOS with Homebrew $(brew --prefix)/etc/ca-certificates/cert.pem. If archive.json changes afterwards, the check fails with message imprint mismatch.

Commit archive.json.tsr together with archive.json after each archiving run. Git then keeps every manifest next to its timestamp; check an older pair after git show <commit>:archive.json > old.json and git show <commit>:archive.json.tsr > old.tsr. The TSAs in Signing are free but not qualified under eIDAS; a qualified timestamp, which carries a legal presumption of the correct time, needs a qualified trust service provider.

A layout that stays readable

project/
  typstgen.toml
  archive.json              # in Git
  _data/company.json        # master data, in Git
  templates/                # or a package in vendor/typst-packages
  invoices/2026/
    RE-2026-001.json        # data, in Git
    RE-2026-001.typ         # thin wrapper: template + data, in Git
    RE-2026-001.pdf         # output: archive storage, not Git
  • One folder per document type and year; the file name is the document number.
  • Data, wrapper and manifest are small text files and belong in version control; the history shows who changed what and when.
  • PDFs are outputs: keep them in the archive storage, not in Git. The manifest ties each stored PDF to the exact sources it was built from.
  • Pin packages (download_packages = false with a project-local cache) and record fonts, so a rebuild in eight years reads the same inputs.

This is background, not legal advice; check the current rules or ask your tax advisor. As of October 2026:

  • Invoices and other bookkeeping vouchers are kept for eight years since 1 January 2025 (§ 147 Abs. 3 AO, § 14b UStG); books and annual accounts for ten.
  • The GoBD (BMF letter of 28 November 2019, last amended 14 July 2025) do not prescribe a specific archive system or checksums. They name versioning and automatic logging as examples of measures for unalterability, and say that plain file storage is usually not enough on its own.
  • For a hybrid e-invoice such as ZUGFeRD, the XML is the leading part for VAT; keeping the XML is generally sufficient unless the PDF holds additional tax-relevant information.
  • For outgoing invoices produced by an invoicing program, the GoBD do not require an image copy if a content-identical copy can be produced at any time (Rz. 76 in the version of 14 July 2025). A manifest plus a successful verify is one way to document that ability.
  • PDF/A is not legally required; it is the container format ZUGFeRD uses.

Sources: § 147 AO, § 14b UStG, GoBD, 2nd amendment of 14 July 2025 (BMF).

Edit this page on GitHub · Docs for v0.5.0