Template format

Version 0 of the format.

A Craftadoc template is a Typst project plus a manifest that describes the form. The manifest's input schema is plain JSON Schema (2020-12), so the same file drives the generated form, server-side validation, AI structured outputs, and the API and MCP tool definitions.

Worked examples: the business card and AltaCV templates. Creating templates walks through building one.

Business card, frontBusiness card, backAltaCV
Business card frontBusiness card backAltaCV

1. Folder layout

templates/altacv/
├── craftadoc.json   manifest: metadata and the input schema
├── example.json     sample values: previews, thumbnails, tests, AI examples
├── main.typ         Typst entry point (name set by "entry")
└── assets/          images and other files the template uses

inputs/ is reserved: the compiler puts uploaded images there.

2. Manifest (craftadoc.json)

FieldRequiredMeaning
formatyesAlways "craftadoc-template/0" for this version.
name, descriptionyesShown in the marketplace.
entryyesThe Typst file to compile, usually main.typ.
languageyesDocument language, e.g. en, nl-BE.
categoriesnoMarketplace categories, e.g. ["cv"].
sizesnoDocument sizes the template offers, e.g. ["a4", "us-letter"]; the first is the default. With more than one, the form shows a size choice; read it with paper-size(). Without sizes, Craftadoc detects the size from your page setup. Known sizes: a1–a6, us-letter, us-legal, us-tabloid, business-card (85 × 55 mm), us-business-card (3.5 × 2 in).
licenseyesspdx identifier, authors, optional source URL and note. The licence decides whether people may copy and change the template.
inputsyesJSON Schema for the form values; its root is an object.

The manifest itself is checked against a JSON Schema, https://craftadoc.com/schemas/craftadoc-template-0.json.

3. Inputs: the JSON Schema subset

A field uses these keywords; anything else is rejected, which keeps forms predictable and every schema usable for AI structured outputs.

PurposeKeywords
Typetype: string, number, integer, boolean, array, object; ["string", "null"] for optional values
Labelstitle (form label), description (help text), default, examples
TextminLength, maxLength, pattern, format (email, uri, date), enum
Numbersminimum, maximum, multipleOf
Listsitems, minItems, maxItems
Groupsproperties, required, additionalProperties: false
Reuse$defs and $ref (#/$defs/...)
Craftadoc hintsx-craftadoc (below)

Form order follows property order in the file. Every property is listed in required; an optional value allows null, e.g. "type": ["string", "null"]. This is the style AI structured outputs handle best (strict modes need every property present).

x-craftadoc hints

HintValuesEffect
kindmarkdownRich text: Markdown input, rendered safely (section 5).
imageAn uploaded image; see image below.
iconA Font Awesome icon as "solid:globe", "regular:envelope" or "brands:github"; the form shows an icon picker.
color"#rrggbb"; the form shows a colour picker.
phoneA phone number input.
imageaspectRatio, shape (rectangle, circle), maxPixelsCrop and downscale in the browser before upload.
multilinetrueA multi-line text box.
widgetslider, radio, select, checkboxOverrides the default control.
labels{ value: label }Display labels for enum values.
collapsedtrueGroups shown folded, e.g. colour settings.

From the old LaTeX markers

Old marker typeFormat 0
text, email, url, datestring with maxLength, format: email, uri or date
textblockstring with multiline, or kind: markdown
number, slidernumber/integer with minimum/maximum; widget: slider
radio, dropdown, multiplechoiceenum (with widget); multiple choice is an array of enum items
colorkind: color
faiconkind: icon
image, cropkind: image with image.aspectRatio and shape
phonenumberkind: phone
list, grouparray with minItems/maxItems, object
reference, deleteOnEmpty, escapeLatexGone: the template reads a value wherever it needs it, tests whether optional values are filled, and values are never code

4. What the template receives

Craftadoc validates the submitted values against inputs and then passes them to Typst as the craftadoc-values input (a JSON string), after three changes:

  1. Images become file paths: an upload for photo arrives as e.g. "inputs/photo.jpg", already cropped and downscaled. example.json uses files under assets/.
  2. Markdown becomes a restricted tree: blocks paragraph and list (with listItem), inlines text, strong, emphasis, link (http, https and mailto only) and break, named as in mdast. Other Markdown is reduced to its text (headings become paragraphs, images their alt text).
  3. Empty optional values become null (none in Typst).

5. The Typst helper package

Templates import @craftadoc/form:0.1.0:

#import "@craftadoc/form:0.1.0": values, rich, icon, filled, date

#let d = values(json("example.json"))   // the submitted values, or the example
= #d.name                               // strings always render as text
#rich(d.about)                          // markdown tree, or a plain string
#icon(d.links.first().icon, fill: blue) // "brands:github" → Font Awesome glyph
#if filled(d.linkedin) [LinkedIn: #d.linkedin]
#date("2026-10-25").display()           // a "date" value as a datetime

values(fallback) returns the submitted values, or fallback when the template is compiled on its own, so every template stays a normal Typst project that compiles and previews without Craftadoc.

6. Rules

  • Values are data, never code. No eval, and no Markdown packages that pass raw Typst through (@preview/cmarker allows it by default). Strings in Typst content always render as literal text: values containing #strong[...], $...$ and #eval(...) were printed verbatim in testing.
  • Fonts come from the platform set (font list): Fira Sans, Hind Madurai, Lato, Roboto Slab, Font Awesome 7, plus Typst's built-ins. A template may also ship its own .ttf/.otf fonts (with a licence that allows redistribution); they are loaded for that template only.
  • Packages: @craftadoc/form plus an allow-list of vendored Typst Universe packages (none yet); the compiler has no network access.
  • Size: at most 10 MB in total, fonts and images included, with at most 200 files of at most 5 MB each. Assets stay inside the template folder.

7. Checks

  1. craftadoc.json against the manifest schema.
  2. example.json against inputs, including the kind rules (icon names, #rrggbb colours, image files that exist).
  3. No eval in any .typ file.
  4. A compile with only the platform fonts and the helper package, failing on any warning (for example a missing font).

Publishing will run the same checks, then record pages, compile time and thumbnails per version.

8. Versions and compatibility

format versions the manifest. Documents keep the template version they were made with; the editor offers to move them to a newer one.

Two templates are compatible when their inputs have the same shape: the same field names, nested the same way, with the same types (integer and number count as one) and the same kind for images, icons and colours. Titles, descriptions, examples, hints, the order of the fields and the limits (maxLength, maxItems, enum, required and so on) don't matter. Documents move between compatible templates with everything filled in; where a template's limits are tighter, the editor shows which fields to change. Compatible templates are listed on each other's pages.

To make a new design for an existing form, start from that template's inputs and keep the field names. Wording and limits can change freely.