CodamAIDocs
Topicdone

How a template is built

Key, output type, scope, edition, language variant, draft and revision: the parts of a template and what a revision holds.

Variants
SYSTEMTENANTUSERLanguage variantDraftRevision

What this is about

A template has several parts that are easy to mix up. The key names the template, for example audit-report. Under this key there can be several editions: one of the platform, one per tenant, one per person. Every edition has one variant per language. And every variant has a draft to work in and a chain with all its revisions.

The picture

flowchart TB
    K["Key<br/>audit-report"]
    K --> E1["Edition SYSTEM<br/>output type DOCUMENT<br/>default language de"]
    K --> E2["Edition TENANT<br/>tenant acme"]
    K --> E3["Edition USER<br/>person anna"]
    E1 --> V1["Variant de<br/>(default)"]
    E1 --> V2["Variant en"]
    V1 --> D1["Draft<br/>saved freely"]
    V1 --> C1["Chain<br/>revision 1, 2, 3 …"]

The parts

PartWhat it isExample
Keythe stable name of the template, the same in every scope and every languageaudit-report
Output typewhat the template produces: DOCUMENT, WORD or TABLE. It decides which formats are possibleDOCUMENT → PDF, HTML
Editionthe template of one owner: the platform, a tenant or a personthe system edition of audit-report
Variantan edition in one language. One of them is the default variantde, de-CH, en
Draftthe workspace of a variant. It is not part of the chain—
Revisiona submitted state: source, fields, assets. Immutablerevision 3

The key

A key is kebab-case: lowercase letters, digits and hyphens. It starts with a letter and has at most 128 characters. audit-report is valid, Audit_Report is not.

The scope

The scope says who an edition belongs to. There are three.

The three scopes
SYSTEMTENANTUSER
Belongs tothe platformone tenantone person in their tenant
Who sees itevery signed-in personthe people of this tenantonly this person
Who writes itthe platform administrators CIAS knowswhoever has the role crms-template-authorthe person themselves, with the role crms-template-personal
Where it is storedsystem databasethe tenant's databasethe tenant's database

The role names crms-template-author and crms-template-personal are the defaults. A project can change them, see Roles. An installation can also switch scopes off (CODAMAI_CRMS_TEMPLATE_SCOPES). Creating an edition in a scope that is switched off ends with 422 template-scope-not-admitted|<scope>.

A person without a tenant has no tenant edition. If they try to create a TENANT edition, CRMS answers 422 template-tenant-required.

Edition and output type

There is exactly one edition per key and owner. A second one ends with 409 template-edition-exists.

The output type belongs to the key. If a tenant creates an edition of audit-report, it must have the same output type as the system edition. Otherwise CRMS answers 422 template-output-type-mismatch. The check only looks upward: person against tenant and system, tenant against system.

Create an edition
POST /crms/templates
{
  "key": "audit-report",
  "outputType": "DOCUMENT",
  "scope": "TENANT",
  "defaultLocale": "de"
}
The new edition
201 Created
{
  "id": "6f1c…",
  "key": "audit-report",
  "outputType": "DOCUMENT",
  "scope": "TENANT",
  "defaultLocale": "de",
  "createdBy": "anna",
  "createdAt": "2026-10-17T08:12:00Z"
}

Together with the edition, CRMS creates its default variant in the language defaultLocale.

The variant

You add another language with POST /crms/templates/{edition}/variants and {"locale": "en"}. There is one variant per language; a second one ends with 409 template-variant-exists. The language is a BCP 47 language tag, so de, de-CH or en. How CRMS picks the right variant for a request is described in How CRMS finds the template.

The draft

Every variant has one draft. You save it as often as you like, always as a whole. So that two people do not silently overwrite each other, every save names the version it is based on (expectedVersion, 0 for the first draft). If someone else saved in the meantime, CRMS answers 409 template-draft-changed.

What a revision holds

A draft and a revision have the same content: source, fields and assets.

Save the draft
PUT /crms/variants/{variant}/draft
{
  "expectedVersion": 2,
  "content": {
    "source": "<p>${title}</p>",
    "fields": [
      { "path": "title", "type": "STRING", "required": true },
      { "path": "items[].amount", "type": "NUMBER" }
    ],
    "assets": [
      { "name": "logo.png", "contentType": "image/png",
        "content": "<base64>" }
    ]
  }
}
Saved, now version 3
{ "variant": "…", "version": 3, "updatedBy": "anna",
  "updatedAt": "…", "content": { … } }
PartContentRules
sourcethe FreeMarker source, see The template languagemust not be empty: template-source-empty
fieldswhich fields the template expects in the payloadsee below
assetsfiles the template uses: logo, font, style sheetsee below

Fields

A template has no JSON schema. It declares the fields it needs. Every declaration has a path, a type and two switches.

EntryValuesMeaning
pathtitle, customer.name, items[].amountwhere the field sits in the payload, at most two levels
typeSTRING, NUMBER, BOOLEAN, DATE, DATETIMEwhat the field holds
requiredtrue / false (default)whether the payload must have the field
listtrue / false (default)whether the field is a list of values of the type

items[] means: items is a list of objects, and each one has a field amount. Only the first level may be such a list. A third part like a.b.c ends with template-field-too-deep, a broken path with template-field-path-invalid. If two declarations contradict each other, for example the same path twice or customer once as a value and once as an object, CRMS answers template-field-conflict|<position>. How CRMS checks the payload against these fields is described in The payload check.

Assets

An asset is a file that belongs to the revision, not to the template. So a new logo never changes an older revision.

  • The name is a plain file name: letters, digits, ., _, -, no path separator, no leading dot, at most 128 characters. Otherwise template-asset-name-invalid.
  • Allowed types: image/png, image/jpeg, image/gif, image/svg+xml, image/webp, font/ttf, font/otf, font/woff, font/woff2, text/css. Otherwise template-asset-type-not-allowed.
  • An asset without content is template-asset-empty, a name used twice is template-asset-duplicate|<position>.

The template names an asset as asset:logo.png. More in The template language.

Where the rows are stored

System editions are stored in the system database. Tenant and person editions are stored in their tenant’s database. So a tenant’s templates stay with its other data. If an installation has only one database, it is the same one. More in Database.

Everything except the draft is only inserted, never changed or deleted. The draft is the only row that changes.

Pitfalls

Next

Search