Add fields to a Typst template
Fields are the parts of a Typst template that change from one document to the next. For the full list of field types, see Typst field types.
Declare the fields
Put a #let data = (...) block at the top of the template, with one line per field. The value after each name is sample text: it shows in the editor's preview and nowhere else. A field that is left empty when you generate prints blank (Blank values).
#let data = (
recipient: "Alice Johnson",
issue_date: "15 Jan 2026",
amount: "5000",
)
Hello, #data.recipient. Your invoice for #data.amount is due on #data.issue_date.Use a field anywhere in the template as #data.name.
Choose the type of box
Add a comment after a field to set the box people fill in on the Generate page and in forms:
#let data = (
recipient: "Alice Johnson", // @type: text
notes: "Payment due on receipt", // @type: textarea
paid: "false", // @type: boolean
website: "https://example.com", // @type: url
issue_date: "15 Jan 2026", // @type: date @format: DD MMM YYYY @label: Issue date
)@label sets the text shown above the box. Without a comment, the type is guessed from the field's name.
Add a date
A date needs a format, or it is a plain text box. With @format, people pick the date from a calendar and the template receives it written in that pattern:
issue_date: "15 Jan 2026", // @type: date @format: DD MMM YYYYThe format tokens are listed in Typst field types.
Do calculations with numbers
Every value arrives as text. Convert it before calculating:
#let total = float(data.amount) * 1.2
Total with tax: #totalAdd a colour
A colour arrives as a hex value. Wrap it in rgb():
#let data = (
brand_color: "#1a56db", // @type: color
)
#rect(fill: rgb(data.brand_color), width: 100%)Add an image
An image field lets people upload a picture or paste a link to one:
#let data = (
logo: "logo.png", // @type: image
)
#image(data.logo, width: 80pt)Upload the sample image in the editor's Assets tab, so the preview can show it.
Add a list, such as line items
A list field holds rows that people can add and remove. Write the sample rows as a list and name the columns with @columns:
#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"),
),
)Loop over the rows:
#for item in data.items [
#item.desc: #item.qty × #item.amount
]Or put them in a table:
#table(
columns: (1fr, auto, auto),
table.header([Description], [Qty], [Amount]),
..data.items.map(item => ([#item.desc], [#item.qty], [#item.amount])).flatten()
)Keep at least one sample row, so the preview shows the table.
Older templates that store lists as JSON
Some older templates keep a list as JSON text (@type: json) and read it with json(bytes(data.items)). That still works. A template that calls json.decode(...) fails with function json does not contain field decode: replace the call with json(bytes(...)). For new templates, use a list field as above.
Last updated on