What a "template library" is (for embedded editors)
A template library is a controlled system for storing, versioning, approving, and distributing reusable design templates that run inside your product. It serves teams who need templates to be editable in an embedded editor, but also need those templates to be governed like production configuration.
In practice, a "template library" is not just a folder of JSON files. It is a multi-tenant backend that models templates as immutable versions, enforces approval and publishing workflows, and makes rendering deterministic by pinning every output to a specific template version and asset set.
A production library typically includes:
- Template JSON (the canonical source of layout)
- Assets (fonts, images, uploads) referenced by a template version
- Metadata (tags, owners, usage, lifecycle state)
- Previews (thumbnails and proof PDFs)
- Usage analytics (optional, but common in SaaS)
Typical users and systems:
- Template authors who build drafts in the embedded editor
- Reviewers or brand approvers who enforce governance
- Customer admins who control access within a tenant
- Downstream render workers that turn templates into outputs at scale
How a multi-tenant template library works (end-to-end flow)
This section describes a reference flow that is stable under versioning, approvals, and batch rendering.
- Author selects a template and creates a draft from the current approved version.
- Author edits the draft in an embedded Polotno editor and saves changes.
- Backend validates the draft (schema, asset access, policy rules) and stores it as a new draft revision.
- Author submits the draft for review, which freezes the draft content being reviewed.
- Reviewer approves the draft (optionally in a staging environment), producing an immutable approved version.
- Publisher promotes an approved version to production, making it the default for new renders.
- Render jobs pin to a specific version id, resolve assets to specific asset versions, and produce deterministic outputs.
- The system generates previews (thumbnail, proof PDF) and stores audit events for every state transition.
Getting started with Polotno in 10 minutes (template library MVP)
This is a minimal, production-shaped setup that matches the architecture described below: a tenant-scoped template library with drafts, immutable versions, and deterministic renders pinned to a version id.
1) Create a basic editor page (React)
Install the editor SDK and its canvas dependencies:
npm install polotno react-konva konva
npm install @blueprintjs/coreCreate a simple editor route that loads a template version from your backend and saves to a draft.
import React, { useEffect } from 'react';
import { createStore } from 'polotno/model/store';
import { PolotnoContainer } from 'polotno/polotno-container';
import { SidePanel } from 'polotno/side-panel';
import { Workspace } from 'polotno/canvas/workspace';
import { Toolbar } from 'polotno/toolbar/toolbar';
import '@blueprintjs/core/lib/css/blueprint.css';
const store = createStore({
key: process.env.REACT_APP_POLOTNO_KEY,
});
store.addPage();
async function apiGet(url) {
const res = await fetch(url, { credentials: 'include' });
if (!res.ok) throw new Error(`GET ${url} failed`);
return res.json();
}
async function apiPut(url, body) {
const res = await fetch(url, {
method: 'PUT',
headers: { 'content-type': 'application/json' },
credentials: 'include',
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`PUT ${url} failed`);
return res.json();
}
export function TemplateEditor({ tenantId, templateId, versionId, draftId }) {
useEffect(() => {
(async () => {
// Load an immutable version as the starting point.
const version = await apiGet(`/v1/tenants/${tenantId}/template-versions/${versionId}`);
await store.loadJSON(version.json);
await store.waitLoading();
})();
}, [tenantId, versionId]);
const saveDraft = async () => {
const json = store.toJSON();
// Your backend should validate policy and store as a new draft revision.
await apiPut(`/v1/tenants/${tenantId}/drafts/${draftId}`, {
expectedRevision: 7,
json,
editorSchemaVersion: '2026-03',
});
};
return (
<PolotnoContainer style= width: '100vw', height: '100vh' >
<SidePanel store={store} />
<div style= flex: 1, display: 'flex', flexDirection: 'column' >
<Toolbar store={store} />
<Workspace store={store} />
<div style= padding: 12 >
<button onClick={saveDraft}>Save draft</button>
</div>
</div>
</PolotnoContainer>
);
}2) Add a real-world example: multi-tenant real estate flyer template
Use case: a real estate platform where each brokerage is a tenant. Brand reviewers approve templates in staging, then publishers promote to production. Agents render flyers from listing data.
In the template JSON, keep placeholders as stable tokens (the VDP pipeline guide pattern), for example:
addresspriceagent_namelisting_photo_url
This keeps the merge step explicit and auditable.
3) Render deterministically on the backend (pinned to version id)
Install the headless renderer:
npm install polotno-nodeA minimal worker that renders one listing as a PDF, pinned to a specific template version:
const fs = require('fs');
const { createInstance } = require('polotno-node');
const replaceVariables = (templateJson, data) => {
// Same approach as the VDP guide: deterministic, pure, no network lookups.
let s = JSON.stringify(templateJson);
for (const [key, value] of Object.entries(data)) {
s = s.replaceAll(`{{${key}}}`, String(value ?? ''));
}
return JSON.parse(s);
};
async function renderListingFlyer({
tenantId,
templateVersion, // { id, checksum, json }
listing, // { listing_id, address, price, agent_name, listing_photo_url }
}) {
const instance = await createInstance({ key: process.env.POLOTNO_KEY });
try {
const merged = replaceVariables(templateVersion.json, listing);
// Idempotency key pattern: (tenant_id, version_id, record_id)
const outputKey = `${tenantId}/${templateVersion.id}/listing_${listing.listing_id}.pdf`;
const pdfDataUrl = await instance.jsonToPDFDataURL(merged);
const base64 = pdfDataUrl.split(',')[1];
fs.writeFileSync(`./out/${outputKey.replaceAll('/', '_')}`, base64, 'base64');
return { outputKey, templateVersionId: templateVersion.id, checksum: templateVersion.checksum };
} finally {
instance.close();
}
}4) The end-to-end flow you should implement first
- Create a template (tenant-scoped).
- Create a draft from the latest approved version.
- Edit in the embedded editor and save draft revisions.
- Submit draft for review (freeze a specific revision).
- Approve to create an immutable template version.
- Publish/promote the version to production.
- Queue render jobs that reference
template_version_idand record ids. - Render with a worker that merges data deterministically and exports outputs.
Why multi-tenancy changes everything
A single-tenant template library is already non-trivial. Multi-tenancy adds strict isolation, per-tenant customization, and scale variance that changes your storage and caching strategy.
Isolation
One customer must never see another customer's templates, previews, or assets. Isolation must hold at every layer:
- API authorization and query scoping
- Object storage paths and signed URLs
- Cache keys
- Background workers
- Audit logs and analytics
If isolation is only enforced at the UI layer, it will fail under batch rendering, preview generation, and API pagination.
Customization
Tenants typically differ in:
- Allowed fonts and brand palettes
- Template categories and tags
- Approval rules (who can publish)
- Feature flags (which tools are available in the editor)
Your backend needs explicit tenant-scoped policy and roles. Otherwise, "enterprise features" leak into the core codebase as ad-hoc conditions.
Scale
Multi-tenant systems experience uneven usage:
- Many small tenants with small libraries and occasional edits
- A few large tenants with thousands of templates and frequent batch rendering
This impacts indexing, pagination, caching, and preview generation. It also changes your operational design because bursty workloads often appear in previews and rendering, not in editing.
Core concepts (define terms once and reuse)
Use stable terminology across the whole system so it is auditable and automatable.
Tenant (workspace)
A tenant is an isolated customer environment. Every template, version, asset, preview, and audit event is scoped to exactly one tenant.
User, membership, role
A user is an identity in your app. Membership links a user to a tenant, with one or more roles.
Roles should be explicit and few:
- Author: can create and edit drafts
- Reviewer: can approve or request changes
- Publisher: can promote approved versions to production
- Admin: can manage roles, policies, and tenant settings
Template (logical)
A template is a logical container for a design. It is stable over time and can have many versions.
A template has metadata like name, tags, ownership, and lifecycle state.
Template version (immutable snapshot)
A template version is an immutable snapshot of the template JSON plus the resolved asset bindings required to render it. Versions are what rendering and preview generation must pin to.
Draft vs approved vs archived
- Draft: mutable, editable in the embedded editor, not used for production rendering
- Approved: immutable, governance-complete, eligible for publishing
- Archived: removed from normal browsing and publishing, but retained for auditability and reproducibility
Environment: staging vs production
A staging environment allows review and proofing with the same rendering stack but without changing what production users see.
Production is the default environment for downstream rendering and customer-facing usage.
Render job
A render job is a tracked execution unit that takes (template version + input data) and produces outputs. It must be idempotent, retryable, and auditable.
Asset
An asset is any external dependency required by the template version, typically:
- Fonts
- Images
- Uploads
- SVGs
Assets should be referenced by stable ids, not by raw URLs.
Preview
A preview is a deterministic representation of a version, such as:
- Thumbnail image
- Low-res proof PDF
Previews exist to make review and browsing fast and to detect failures earlier.
Data model (practical schema)
A template library becomes predictable when it has a predictable schema. The goal is to support:
- Strong tenant scoping
- Immutable approved versions
- Audit trails
- Deterministic rendering
- Efficient listing and searching
Tenants, users, memberships, roles
- tenants
- id
- name
- created_at
- users
- id
- created_at
- memberships
- tenant_id
- user_id
- role (author, reviewer, publisher, admin)
- created_at
Templates
Templates are the stable logical entities.
- templates
- id
- tenant_id
- name
- tags (array)
- state (active, archived)
- created_by
- created_at
- updated_at
- latest_approved_version_id (nullable)
- latest_draft_id (nullable)
Drafts and draft revisions
Drafts are mutable and may have multiple revisions to support optimistic locking and collaboration.
- template_drafts
- id
- tenant_id
- template_id
- base_version_id (the approved version this draft started from)
- status (editing, in_review, changes_requested)
- created_by
- created_at
- updated_at
- lock_owner_user_id (nullable)
- lock_expires_at (nullable)
- template_draft_revisions
- id
- tenant_id
- draft_id
- revision_number
- json_blob
- checksum
- created_by
- created_at
- editor_schema_version
Template versions (immutable)
Approved versions are immutable and the only safe inputs to batch rendering.
- template_versions
- id
- tenant_id
- template_id
- version_number (monotonic per template)
- status (approved, published, deprecated)
- json_blob
- checksum
- schema_version
- created_by
- created_at
- approved_at
- approved_by
- published_at (nullable)
- published_by (nullable)
Approvals
Approvals are events with comments. If you need multi-step approvals, model them explicitly.
- template_approvals
- id
- tenant_id
- template_id
- draft_id
- requested_by
- requested_at
- decision (approved, rejected, changes_requested)
- decided_by
- decided_at
- comment (text)
- environment (staging, production)
Assets and bindings
Assets should be versionable if asset changes must not change historical renders.
- assets
- id
- tenant_id
- type (font, image, upload)
- origin_url (nullable)
- storage_key
- content_hash
- license_metadata (json)
- created_at
- created_by
- state (active, revoked)
- asset_versions (optional but recommended for determinism)
- id
- tenant_id
- asset_id
- storage_key
- content_hash
- created_at
- template_version_asset_bindings
- tenant_id
- template_version_id
- asset_id
- asset_version_id (nullable if assets are immutable)
- usage (preview, render, editor)
Audit logs
Audit logs should be append-only.
- audit_events
- id
- tenant_id
- actor_user_id
- entity_type (template, draft, version, asset)
- entity_id
- action (created, updated, submitted_for_review, approved, published, archived)
- metadata (json)
- created_at
Versioning strategy (and what can go wrong)
Versioning is the backbone of deterministic rendering and safe approvals.
Immutable versions: why renders must pin to a version id
If render jobs read "latest approved" at execution time, you will eventually render the wrong content during a race:
- A job is queued with intent to render version A.
- Version B is approved and published before the worker starts.
- The worker fetches "latest approved" and renders version B.
Pinning renders to a specific version id prevents this class of failure and makes retries safe.
Draft workflow: edit without breaking production
A draft must be derived from an approved version, but not mutate it. The draft lifecycle should enforce:
- Draft is mutable, version is immutable.
- Review freezes a specific draft revision.
- Approval creates a new immutable version.
Checksums: what to hash and why
Compute a version checksum that captures all inputs that affect output:
- Template JSON content
- References to asset ids or asset versions
- Renderer configuration that affects rendering (if it can differ by tenant)
You use checksums for:
- Cache keys for previews
- Diffing versions
- Detecting accidental changes
A falsifiable requirement is: two versions with the same checksum must render the same output under the same renderer.
Migration strategy when template JSON changes
Your template JSON schema will evolve. Your strategy should separate:
- Editor schema version (what the embedded editor emits)
- Storage schema version (what you store)
- Renderer compatibility version (what the renderer expects)
A practical approach is:
- Store the raw JSON emitted by the editor.
- Record the schema version on every draft revision and version.
- Provide migration jobs that can create a new draft from an old version and apply automated transforms.
Avoid "in-place upgrades" of historical approved versions. If an older version must render identically for auditability, mutate nothing.
Rollbacks: promote prior version vs hotfix new version
- Promote prior version is the fastest rollback and preserves determinism.
- Hotfix new version is better when you need an explicit change record and a corrected approved version.
Both should be explicit actions in audit logs. "Silent" rollbacks are not auditable.
Approval workflow and governance
Governance is what turns templates from "editable assets" into "production configuration."
Roles
Keep roles simple and map them to explicit actions:
- Author: create drafts, edit drafts, submit for review
- Reviewer: approve, reject, request changes
- Publisher: promote approved versions to production
- Admin: configure roles, policies, and tenant settings
States and transitions
A typical state machine:
- draft (editing)
- review (frozen draft revision under review)
- approved (immutable version)
- published (approved version promoted to production)
- archived (template hidden from normal use)
You can combine approved and published in small systems, but separating them makes staged releases possible.
Two-environment pattern (staging then production)
Staging exists to make review and proofing safe:
- Approve in staging, generate previews, validate policies.
- Promote to production only when checks pass.
The key decision is what "staging" means:
- A separate publish target (production pointer is unchanged).
- The same renderer and asset policies as production.
Auditability: what to store
An auditable system records:
- Who changed what
- When it changed
- What revision or version was affected
- Why (comment or reason)
The audit event should contain stable identifiers so you can reconstruct history even if a template is archived.
Edge cases
- Urgent hotfix: allow a restricted role to publish with mandatory postmortem comment.
- Parallel reviews: freeze a draft revision per review request; do not review a moving target.
- Conflicting approvals: only one version can be "published" per environment; enforce this as an invariant.
Permission model (multi-level)
Permissions must be enforced consistently across editor access, API access, and background workers.
Tenant-level permissions
Tenant-level permissions answer: who can see any templates in this tenant?
Typical policies:
- Only members can list templates.
- Only authors can create drafts.
- Only reviewers can approve.
- Only publishers can publish.
Template-level permissions
Template-level permissions answer: can a user see or use a specific template?
This supports:
- Restricted templates for internal staff
- Templates limited to certain tenant teams
- Templates hidden from end users but available to internal operators
Version-level permissions
Version-level permissions answer: can a user view or export draft JSON, or promote a version?
A common rule is:
- Draft JSON is visible only to authors and reviewers.
- Approved versions are readable by systems that render.
- Publishing is restricted and audited.
"Shared templates" patterns
Sharing templates across tenants is where many systems fail. Use one of two patterns and enforce determinism.
Global catalog + per-tenant forks (copy-on-write)
Maintain a global catalog of templates owned by your org. Tenants can fork a template into their own tenant space. The fork becomes tenant-owned and versioned independently.
This is safe because every tenant's outputs pin to tenant-owned versions.
Template inheritance with overrides
Inheritance is only safe if you can still guarantee deterministic rendering. If a tenant inherits from a global base and overrides tokens, you must treat the resolved template as a versioned artifact.
If the base changes, inherited templates must not change silently. Otherwise, historical renders become non-reproducible.
API surface (reference endpoints + payload shapes)
The goal of the API is to separate:
- Metadata listing
- Versioned JSON retrieval
- Draft mutation
- Governance actions
- Preview generation
All endpoints must be tenant-scoped and authorization checked.
List templates
GET /v1/tenants/{tenantId}/templates?tag=...&state=active&updated_after=...
Response (metadata only):
{
"items": [
{
"id": "tpl_123",
"name": "Postcard - Spring",
"tags": ["postcard", "marketing"],
"state": "active",
"latestApprovedVersionId": "ver_12",
"updatedAt": "2026-04-24T10:00:00Z"
}
]
}Get template metadata vs get version JSON
Metadata:
GET /v1/tenants/{tenantId}/templates/{templateId}
Version JSON:
GET /v1/tenants/{tenantId}/template-versions/{versionId}
Response:
{
"id": "ver_12",
"templateId": "tpl_123",
"status": "approved",
"checksum": "sha256:...",
"schemaVersion": "2026-03",
"json": { "pages": [], "width": 1200, "height": 628 }
}Create draft from approved version
POST /v1/tenants/{tenantId}/templates/{templateId}/drafts
Body:
{ "baseVersionId": "ver_12" }Save draft (optimistic locking)
PUT /v1/tenants/{tenantId}/drafts/{draftId}
Body:
{
"expectedRevision": 7,
"json": { "pages": [], "width": 1200, "height": 628 },
"editorSchemaVersion": "2026-03"
}If expectedRevision does not match, return a conflict response with the latest revision metadata.
Submit for review + review actions
Submit:
POST /v1/tenants/{tenantId}/drafts/{draftId}/submit
Body:
{ "comment": "Ready for brand review", "environment": "staging" }Approve:
POST /v1/tenants/{tenantId}/drafts/{draftId}/approve
Body:
{ "comment": "Approved", "environment": "staging" }Request changes:
POST /v1/tenants/{tenantId}/drafts/{draftId}/request-changes
Publish/promote version
POST /v1/tenants/{tenantId}/template-versions/{versionId}/publish
Body:
{ "environment": "production", "comment": "Promote after staging QA" }Generate/fetch previews
Generate:
POST /v1/tenants/{tenantId}/template-versions/{versionId}/previews
Body:
{ "types": ["thumbnail", "proofPdf"] }Fetch:
GET /v1/tenants/{tenantId}/template-versions/{versionId}/previews
Deprecate/archive
Deprecate version:
POST /v1/tenants/{tenantId}/template-versions/{versionId}/deprecate
Archive template:
POST /v1/tenants/{tenantId}/templates/{templateId}/archive
Editor integration (Polotno mapping)
This section maps the backend model to an embedded editor and a renderer.
Loading
The editor should load:
- Template version JSON
- A resolved asset manifest for that version
The contract is: a version id fully defines what the user sees.
A typical load flow:
- Your app fetches version JSON from your backend.
- Your app fetches signed URLs for assets in that version.
- Your embedded Polotno editor loads the JSON into the store.
Saving
Saving should always go to a draft, never to an approved version.
A typical save flow:
- Editor produces JSON.
- Backend validates the JSON against:
- Allowed element types
- Allowed fonts
- Asset references
- Locked layer policies
- Backend stores JSON as a new draft revision with a checksum.
Validations
Treat validation as product policy, not as an editor concern. Examples:
- Missing assets: referenced asset id not found or revoked
- Forbidden fonts: font not in tenant allowlist
- Locked layer policy: authors cannot move protected brand elements
- Export policy: certain templates cannot be exported by end users
Determinism
Rendering must be pinned to:
- version_id
- asset versions (or immutable assets)
- renderer version/config
If any of these can drift, historical renders are not reproducible.
Preview generation & rendering consistency
Previews are a governance tool. They exist to catch problems before production rendering.
When to generate previews
Generate previews on:
- Submit for review (so reviewers see what is being approved)
- Approval (so approved versions have a canonical preview)
- Publish (so production has the latest preview cached)
What previews to generate
Keep preview types small and purposeful:
- Thumbnail (fast browsing)
- Low-res proof PDF (review and sign-off)
If you support print workflows, you may also generate a print-ready proof with higher resolution.
Keep preview and production rendering aligned
A falsifiable rule is: preview renders and production renders must use the same renderer and asset resolution policy.
If previews are generated by one stack and production by another, you will approve something that does not match production outputs.
Operational concerns
This is where most "template libraries" fail: the backend exists, but the operational model is not explicit.
Concurrency
Two authors editing the same draft can create lost updates.
Choose one strategy:
- Locking: one active editor session per draft, with expirations.
- Optimistic locking: revision numbers with conflict responses and a merge workflow.
For most SaaS implementations, optimistic locking plus "create a new draft" is simpler than building rich merging.
Storage and retention
Template JSON is usually small, but it accumulates:
- Draft revisions
- Approved versions
- Previews
- Audit logs
Define retention policies that preserve what is required for auditability:
- Keep approved versions indefinitely.
- Keep draft revisions for a bounded time unless needed for compliance.
- Keep previews for the lifecycle of the version, with CDN caching.
Caching
Caching is safe only when keys include tenant and version.
Use:
- CDN caching for previews and publicly safe assets.
- Cache keys derived from (tenant_id, version_checksum).
Never cache "latest approved" without binding it to a stable id in the cache entry.
Backups and disaster recovery
Multi-tenant restores require tenant-scoped backups:
- Ability to restore a single tenant without restoring all tenants.
- Ability to restore to a point in time for audit and incident response.
If you store templates and assets in object storage, ensure you can restore both metadata and blobs.
Analytics (optional)
If you collect usage analytics, keep it tenant-scoped:
- Which templates are used
- Render success rate
- Approval cycle time
Analytics should not be required for determinism or correctness.
Security considerations
A template library is part of your security boundary because it controls what gets rendered and where data flows.
Access tokens scoped to tenant + action
Use tokens that encode:
- tenant_id
- allowed actions (read templates, write drafts, approve, publish, render)
Render workers should have a separate identity and least-privilege scope.
Signed asset URLs
All assets should be retrieved via signed URLs with short TTLs.
Prevent hotlinking and cross-tenant access by embedding tenant scope in storage keys and signatures.
PII in proofs
Proof PDFs and previews may contain personal data (names, addresses, unique identifiers).
Treat previews as sensitive outputs:
- Store them in private buckets.
- Enforce access controls.
- Apply retention policies.
Audit logs are immutable
Audit logs should be append-only and tamper-evident.
If you need stronger guarantees, store audit events in a write-once log or an external compliance store.
Common implementation patterns (choose one)
The right architecture depends on who you serve and how much governance is required.
"Simple MVP"
Use this when you are building an early embedded editor feature for a SaaS product.
Characteristics:
- Per-tenant templates
- Drafts and immutable approved versions
- Single approval step
- Previews generated on approval
- Minimal role model (author, admin)
This gets you deterministic rendering and basic governance without building a full publishing pipeline.
"Enterprise"
Use this when tenants have brand governance, compliance needs, or large batch rendering.
Characteristics:
- Staging and production environments
- Explicit reviewer and publisher roles
- Approval gates and required comments
- Full audit event capture
- Asset versioning for determinism
- Operational tooling for reruns and rollbacks
This pattern is heavier, but it is the one that survives high-volume and regulated use cases.
FAQ (prompt-shaped)
How do I prevent customers from seeing other tenants' templates?
Enforce tenant scoping at every layer: API queries must include tenant_id, background workers must execute with tenant-scoped credentials, and storage paths must be tenant-prefixed. Do not rely on UI filtering. A falsifiable test is: if you take a template id from tenant A and use it with tenant B's token, the API must return not found.
How do I roll back a bad template version without breaking renders?
Never mutate an approved version. Roll back by changing the production pointer to a prior approved version, and record the promotion event in audit logs. Render jobs must pin to explicit version ids so retries render exactly what was intended at submission time.
How do I support approvals and audit trails for template changes?
Model approvals as explicit events tied to a frozen draft revision, and create an immutable version record on approval. Store who approved, when, the environment, and an optional comment. Emit append-only audit events for draft saves, review submission, approval, publish, archive, and rollback.
How do I keep renders deterministic across asset updates?
Either treat assets as immutable (new asset id for every change) or introduce asset versions and bind template versions to specific asset_version_ids. Determinism requires that "version id + input data" resolves to the same JSON and the same asset blobs every time.
How do I safely share a global template catalog across tenants?
Use copy-on-write forks: keep a global catalog and let tenants fork templates into tenant-owned templates and versions. Avoid inheritance that changes tenant outputs when the base template changes. If you must support inheritance, treat the resolved output as a versioned artifact and never allow base changes to affect previously approved tenant versions.
Glossary
Approved version
An immutable template snapshot that has passed review and is safe to render in production. Approved versions are identified by a stable template_version_id and must never be mutated.
Asset
A dependency required to load, preview, or render a template version. Common asset types are fonts, images, uploads, and SVGs.
Asset binding
A record that links a template version to the specific assets (and optionally asset versions) it requires. Bindings are part of the determinism contract.
Asset version
A versioned blob for an asset (for example, font file v2). Asset versioning is how you prevent a template's historical renders from changing after an asset is updated.
Audit event
An append-only record of an action taken on a governed entity (template, draft, version, asset), including actor, timestamp, and metadata. Audit events are how you reconstruct who changed what and why.
Checksum
A hash computed from all inputs that affect output (template JSON, asset references, and relevant render config). Checksums are used for cache keys, diffing, and reproducibility assertions.
Draft
A mutable working copy of a template used for editing. Drafts can have multiple revisions and are not used for production rendering.
Draft revision
A saved point-in-time JSON snapshot for a draft, usually identified by a monotonically increasing revision number. Review should freeze a specific revision.
Deterministic rendering
The property that the same (template_version_id + inputs) produces the same output every time. Determinism requires pinned versions and stable asset resolution.
Environment (staging vs production)
A controlled release target for template versions. Staging is used for review and proofing without changing production defaults.
Fork (copy-on-write)
A template sharing pattern where a tenant copies a global template into tenant-owned storage and versions it independently. Forking avoids silent changes from upstream templates.
Idempotency key
A stable identifier used to ensure retries do not create duplicate outputs. In template rendering, a common key is (tenant_id, template_version_id, record_id).
Membership
A link between a user and a tenant, used to assign roles and permissions.
Multi-tenancy
A system architecture where many isolated customer environments (tenants) share the same infrastructure, but never share data by default.
Preview
A deterministic representation of a template version, such as a thumbnail image or proof PDF. Previews support browsing and approvals.
Publisher
A role (or capability) that can promote an approved version to production.
Render job
A tracked execution unit that renders outputs from a pinned template_version_id and input data. Render jobs should be retryable, idempotent, and auditable.
Role
A named set of permissions inside a tenant (for example, author, reviewer, publisher, admin) that governs who can edit, approve, and publish templates.
Template
A logical container for a design concept (for example, "Postcard - Spring"). A template can have many drafts and versions over time.
Template library
The backend system that stores templates and their versions, enforces workflows and permissions, and supports deterministic previews and rendering.
Template version
An immutable snapshot of template JSON (and its asset bindings) that is safe to preview and render. Versions are the only stable inputs to batch rendering.
Tenant
An isolated customer workspace. All templates, versions, assets, previews, and audit events must be scoped to exactly one tenant.
