Typst fields and data
This page covers how to declare fields in Typst templates, annotate their types, and how data flows in from each trigger.
Declaring fields in Typst
Fields are declared as a #let dictionary at the top of your Typst source. The values you provide are used as defaults and shown as pre-fills on the Generate form.
#let data = (
recipient: "Alice Johnson",
issue_date: "15 Jan 2025",
amount: "5000",
)
Hello, #data.recipient — your invoice for #data.amount is due on #data.issue_date.Annotating field types
By default, Automette infers the field type from the field name (e.g. _date suffix → date, _qty → number). You can override this with inline // @type: comments:
#let data = (
recipient: "Alice Johnson", // @type: text
issue_date: "15 Jan 2025", // @type: date @format: DD MMM YYYY
amount: "5000", // @type: number
notes: "Payment due on receipt", // @type: textarea
paid: "false", // @type: boolean
logo: "logo.png", // @type: image
brand_color: "#1a56db", // @type: color
portal_url: "https://example.com", // @type: url
label: "Issue Date", // @label: Custom Label
)Supported types
| Annotation | Input shown | Injected value |
|---|---|---|
text | Single-line text input | String as-is |
textarea | Multiline text | String as-is |
number | Number input | String (use float() in template) |
date @format: ... | Date picker | Date formatted per @format |
boolean | Checkbox | "true" or "false" |
image | Image upload + URL | S3 key or URL string |
color | Color picker + hex input | Hex string e.g. "#1a56db" |
url | Text input | URL string |
array | Repeating row editor | Native Typst tuple — iterate directly |
json | Monospace JSON textarea | JSON string — call json.decode() in template |
Date fields and formatting
Date fields require a @format to be useful. Without it, @type: date is treated as plain text — no date picker is shown and no conversion happens.
// ✅ Date picker shown; value injected as "15 Jan 2025"
issue_date: "15 Jan 2025", // @type: date @format: DD MMM YYYY
// ✅ Different format
expiry: "Jan 2025", // @type: date @format: MMM YYYY
// ⚠️ No @format — treated as plain text input
start_date: "2025-01-15", // @type: date ← no picker, no conversionFormat tokens
| Token | Output |
|---|---|
DD | Day with leading zero (01–31) |
D | Day without leading zero (1–31) |
MMM | Short month name (Jan, Feb, …) |
MMMM | Full month name (January, …) |
MM | Month number with zero (01–12) |
M | Month number without zero (1–12) |
YYYY | 4-digit year (2025) |
YY | 2-digit year (25) |
Color fields
Color values are injected as hex strings. Use rgb() in your Typst template to convert them:
#let data = (
brand_color: "#1a56db", // @type: color
)
#rect(fill: rgb(data.brand_color), width: 100%)
#text(fill: rgb(data.brand_color))[Colored text]Image fields
Image fields accept a file upload or a URL. The value is injected as a filename (for uploaded assets) or a URL string:
#let data = (
logo: "logo.png", // @type: image
)
#image(data.logo, width: 80pt)Upload the image in the Assets tab before generating.
Array fields (repeating rows)
Use @type: array for fields that hold a list of items — line items on an invoice, subjects in a report card, features in a product sheet. The value is a native Typst tuple, so you iterate it directly without any decoding.
#let data = (
items: ( // @type: array @columns: desc:text,qty:number,amount:number
(desc: "Widget A", qty: "2", amount: "1200"),
(desc: "Service fee", qty: "1", amount: "500"),
),
)The @columns: key:type,... annotation lists sub-field names and their input types. Supported sub-types: text, number, date, boolean, image, color, url. Date sub-fields accept a format: delivery_date:date:DD MMM YYYY.
Iterate with a for loop:
#for item in data.items [
#item.desc — #item.qty × #item.amount
]Or spread into a table:
#table(
columns: (1fr, auto, auto),
table.header([Description], [Qty], [Amount]),
..data.items.map(item => (
[#item.desc], [#item.qty], [#item.amount],
)).flatten()
)Always include at least one representative row as the default value — it makes the live preview look correct.
@type: json (legacy)
Some older templates store repeating data as a JSON-encoded string and call json.decode() to parse it:
#let data = (
items: "[{\"desc\":\"Widget\",\"qty\":2}]", // @type: json
)
#let rows = json.decode(data.items)This still works and is supported, but @type: array is cleaner for new templates — no escaping, sub-field types are shown correctly in forms.
How data flows in
| Source | How values are provided |
|---|---|
| Generate page | Form fields filled manually |
| CSV bulk | One row per render — column headers map to field keys |
| Public Forms | Shareable form with the same field inputs |
| API | fields object in the POST body |
| Signed URLs | Query parameters in the URL |
Scalar values are always injected as strings — use int() or float() in the template to convert them. Array fields (@type: array) are injected as native Typst tuples and can be iterated directly.
Last updated on