Ask Anvil

Answers to questions about automating PDFs, e-signatures, Webforms, and other paperwork problems.
PDFs
Categories

How do I version a document template so I can regenerate an old document exactly?

Store a content hash of the template alongside every document you generate, and archive the template bytes under that hash. Reproducing an old document then means loading the revision that was actually used, not whatever sits in the template editor today.

import hashlib

def version_of(template_bytes: bytes) -> str:
    """Content-addressed version id. Same bytes always give the same id."""
    return hashlib.sha256(template_bytes).hexdigest()[:12]

archive = {}  # (template_id, version) -> template bytes

def publish(template_id: str, template_bytes: bytes) -> str:
    v = version_of(template_bytes)
    archive[(template_id, v)] = template_bytes
    return v

def render_record(template_id, template_bytes, data):
    return {
        "template_id": template_id,
        "template_version": publish(template_id, template_bytes),
        "data": data,
    }

def reproduce(record):
    key = (record["template_id"], record["template_version"])
    if key not in archive:
        raise LookupError(f"template revision {key} was never archived")
    return archive[key], record["data"]

nda_v1 = b"<h1>NDA</h1><p>Term: {{term}} years.</p>"
rec = render_record("nda", nda_v1, {"term": 2})

# Someone edits the template a month later.
nda_v2 = b"<h1>NDA</h1><p>Term: {{term}} years. Governing law: {{state}}.</p>"
publish("nda", nda_v2)

tpl, data = reproduce(rec)
print(tpl == nda_v1)                                  # True
print(version_of(nda_v2) != rec["template_version"])  # True

Why a hash instead of a version number

Incrementing integers depend on whoever edits the template remembering to bump them. A content hash is derived from the bytes, so an edit cannot quietly keep the old number, and re-saving an unchanged template does not create a phantom revision.

Pin the data, not just the template

The template is only half the input. If the record stores a foreign key to a customer row and that customer moved last year, re-rendering produces a document that never existed. Store the merge payload that was actually used, denormalized, next to the version id.

Two caveats

Byte-identical output is a separate problem. PDF stores a creation timestamp in the document information dictionary and a file identifier in the trailer. Both usually change from run to run, so two renders of the same template with the same data can differ byte for byte even when they look identical. If you need byte-level proof of what was delivered, archive the rendered PDF and hash that file. Treat regeneration as a way to inspect a document, not to prove it.

Record the renderer too. The template and the data are not the only inputs. The software that turned them into a PDF is one as well, so store the rendering engine and its version in the same record. When a reproduced document does not match the original, that tells you which of the three inputs to look at first.

Back to All Questions

The fastest way to build software for documents

Anvil Document SDK is a comprehensive toolbox for product teams launching document flows where PDF filling, signing, and complex conditional scenarios are necessary.
Explore Anvil
Anvil Webforms