Skip to content

Fields & Formatters

Aurghyadip edited this page Aug 3, 2026 · 1 revision

Fields & Formatters

typst-mailmerge provides a set of robust, fault-tolerant field manipulation utilities designed to handle real-world CSV inconsistencies, missing fields, variations in header naming, and formatting requirements.


πŸ” field(record, key, fmt: none, default: "")

Retrieves a field value from a record dictionary with smart key normalization and candidate array matching.

1. Key Normalization

field() automatically normalizes casing, spaces, hyphens, and underscores. For example, all of the following retrieve the same value:

field(record, "First Name")
field(record, "first_name")
field(record, "First-Name")
field(record, "firstname")

2. Candidate Keys Array

If CSV datasets might vary in header naming between exports, pass an array of candidate keys. field() returns the first non-empty match:

field(record, ("First Name", "FirstName", "Given Name", "Name"))

3. Built-in Format Presets (fmt: ...)

  • "upper" β€” Converts string to uppercase.
  • "lower" β€” Converts string to lowercase.
  • "title" β€” Converts string to Title Case.
  • "currency" β€” Prepends $ if not already present.
  • Closure val => content β€” Custom transform function.
#field(record, "First Name", fmt: "upper")    // "JANE"
#field(record, "City", fmt: "title")          // "New York"
#field(record, "Balance", fmt: "currency")    // "$150.00"
#field(record, "Code", fmt: v => [*#v*])       // Bold content

πŸ”— bind-field(record)

Binds a record dictionary to field() for ultra-concise template syntax:

#mail-merge(data, record => [
  #let f = bind-field(record)

  Dear #f("First Name"),
  Your city is #f("City") and your balance is #f("Balance", fmt: "currency").
])

🧹 join-fields(record, keys, separator: ", ", default: "")

Joins non-empty field values with a separator, automatically omitting missing or blank fields. Ideal for address lines, city/state pairs, or full names.

// Joins City and State cleanly: "Springfield, IL"
#join-fields(record, ("City", "State"), separator: ", ")

// Full address line joining (skips missing Address 2):
#join-fields(record, ("Address 1", "Address 2"), separator: " - ")

❓ if-field(record, key, then-content, else-content: [])

Conditionally renders content if a field is present and non-empty:

// Render Company name only if present
#if-field(record, "Company", c => [#c \ ])

// With else block
#if-field(record, "Phone", p => [Phone: #p], [No phone provided])

πŸ” Inspection Helpers

  • is-empty(record, key) β€” Returns true if field is empty or missing.
  • is-non-empty(record, key) β€” Returns true if field is non-empty.