---
name: craftadoc-template
description: Write, convert or fix a Craftadoc template — a Typst project with craftadoc.json (the form, as JSON Schema) and example.json — so it can be published on craftadoc.com. Use when the user asks for a Craftadoc template, wants to turn a document, LaTeX or Typst file into one, or works in a folder with craftadoc.json.
---

# Writing Craftadoc templates

A Craftadoc template is a Typst project that people fill in through a form on craftadoc.com, which then typesets it into a PDF. The layout and fixed wording belong to the template; people only provide the values. Full reference: https://craftadoc.com/docs/template-format and https://craftadoc.com/docs/typst-helper.

## The folder

```text
my-template/
├── craftadoc.json   details and the form (JSON Schema)
├── example.json     a complete, valid set of sample values
├── main.typ         the Typst document
└── assets/          images, and fonts with their licence files
```

Everything the template needs lives in this folder. It compiles without network access, and the only package available is `@craftadoc/form:0.1.0` — no other Typst Universe packages.

## craftadoc.json

```json
{
  "$schema": "https://craftadoc.com/schemas/craftadoc-template-0.json",
  "format": "craftadoc-template/0",
  "name": "Wedding Menu",
  "description": "One sentence on what it is and what's on it, for the catalogue.",
  "entry": "main.typ",
  "language": "en",
  "categories": ["menu"],
  "tags": ["wedding", "elegant"],
  "sizes": ["a5", "us-letter"],
  "license": { "spdx": "MIT", "authors": ["Your name"], "note": "Credits for fonts or the original design." },
  "inputs": {
    "type": "object",
    "additionalProperties": false,
    "required": ["couple", "date", "courses"],
    "properties": {
      "couple": { "type": "string", "title": "Names", "maxLength": 40 },
      "date": { "type": "string", "title": "Date", "maxLength": 30 },
      "courses": {
        "type": "array", "title": "Courses", "minItems": 1, "maxItems": 5,
        "items": {
          "type": "object", "title": "Course", "additionalProperties": false,
          "required": ["name", "dish"],
          "properties": {
            "name": { "type": "string", "title": "Course", "maxLength": 24 },
            "dish": { "type": "string", "title": "Dish", "maxLength": 80 }
          }
        }
      }
    }
  }
}
```

Rules for `inputs`:

- Every object has `"additionalProperties": false` and lists its fields in `required`. Optional fields are nullable: `"type": ["string", "null"]`.
- Every field has a `title` (its label); add a `description` when the label isn't enough ("For example Friday 31 October.").
- Every string has a `maxLength` and every array a `maxItems` that the layout can really hold. Numbers get `minimum`/`maximum`.
- `categories`: cv, business-cards, letter, invoice, poster, recipe, invitation, card, menu, certificate, planner, game, seasonal (or a new lowercase slug). Up to 10 lowercase `tags`.
- `sizes` (optional): a1–a6, us-letter, us-legal, us-tabloid, business-card, us-business-card. Without it, the size is read from the page.
- Shared definitions go in `"$defs"` and are used with `"$ref": "#/$defs/name"`.
- Field kinds, in `"x-craftadoc"`:
  - `{"kind": "markdown"}` rich text (bold, italic, links, lists),
  - `{"kind": "image", "image": {"aspectRatio": 1, "shape": "circle"}}` an uploaded image (a file path),
  - `{"kind": "icon"}` a Font Awesome icon such as `"solid:phone"` or `"brands:github"`,
  - `{"kind": "color"}` a hex colour such as `"#566ef5"`,
  - `{"kind": "phone"}`, `{"multiline": true}`, `{"collapsed": true}` on an object,
  - enums: `"enum": [...]` with `{"widget": "radio" | "select", "labels": {"value": "Label"}}`; numbers can use `{"widget": "slider"}`.

## example.json

Realistic content (never lorem ipsum) that passes every rule above. It's the template's thumbnail and the starting point of every new document, so make it look good. Images point at files in `assets/`.

## main.typ

```typ
#import "@craftadoc/form:0.1.0": values, rich, icon, filled, date, paper-size

#let d = values(json("example.json"))

#set page(paper: paper-size("a5"), margin: 15mm)
#set text(font: "Lato", size: 11pt)
```

- `values(json("example.json"))` gives the filled-in values on craftadoc.com, and the example locally.
- `rich(d.field)` for markdown fields, `icon(d.icon, fill: blue)`, `filled(d.optional)` for optional values, `date(d.day)` for date fields, `paper-size("a4")` for templates with several `sizes`.
- Values are data: never pass them to `eval` and never build Typst code from them.
- Platform fonts, no need to ship them: Fira Sans, Hind Madurai, Lato, Roboto Slab, Font Awesome 7 (via `icon`), Libertinus Serif, New Computer Modern, DejaVu Sans Mono. Other fonts: put the `.ttf`/`.otf` files and their licence (SIL OFL or Apache 2.0) in `assets/fonts/`.

## Design so any value fits

People type long names and skip optional fields, so the layout must hold every value the schema allows:

- Shrink one-line titles and names to fit instead of letting them wrap:
  ```typ
  #let fitted(size, body) = layout(area => {
    let width = measure(text(size: size, body)).width
    text(size: if width > area.width { size * (area.width / width) } else { size }, body)
  })
  ```
- For a design that fills a fixed area (a card, a poster), scale content down when it's too tall:
  ```typ
  #let fit-height(body) = layout(area => {
    let height = measure(block(width: area.width, body)).height
    if height <= area.height { body } else {
      let k = area.height / height
      scale(k * 100%, origin: top + center, reflow: true, block(width: area.width / k, body))
    }
  })
  ```
- To support several sizes with one design, draw it at one width and scale it to the page: `context { let k = page.width / 148mm; ... scale(k * 100%, origin: top + left, ...) }`. With `flipped: true` pages, `page.width` stays the portrait width.
- Big display fonts have tall line boxes: set them as `block(spacing: 0pt, text(top-edge: "bounds", bottom-edge: "bounds", ...))` and add your own `v()`.
- Hide optional parts with `if filled(d.x) { ... }`, and never leave an empty label behind.
- Don't name variables `top`, `bottom`, `left`, `right` or `horizon`: they shadow Typst's alignments.
- Draw curves with `curve(...)`, not the older `path(...)`. `tiling` works for patterns.

## Check before publishing

1. Download the kit (helper package and fonts): https://craftadoc.com/downloads/craftadoc-typst-kit.zip
2. Compile every size with the same fonts as craftadoc.com, and look at every page:
   ```sh
   typst compile --ignore-system-fonts --font-path fonts --font-path . --package-path packages \
     --input craftadoc-size=us-letter main.typ "out-{p}.png" --ppi 80
   ```
3. Try the longest values the schema allows (every string at its `maxLength`, every list at its `maxItems`) by passing them as `--input craftadoc-values='{...}'`. Nothing may overflow, overlap or spill onto an extra page.
4. Check `example.json` against the schema.

Then publish at https://craftadoc.com/templates/new: upload the folder as a .zip, or import it from a GitHub repository. Craftadoc checks it again (form, example values, a test compile) and shows the problems in plain words.
