Computed expressions

A computed output derives a value with a deterministic expression — arithmetic, string concatenation, date math, and conditionals over other case-data values. Expressions re-evaluate automatically (live in the case detail and again at document generation), and the special references $today and $now keep date math current without re-extraction.

Updated 5 min read

Calculations in Document Blueprint are computed outputs — one of the four derived-output kinds a field can carry (alongside AI prompts, static values, and bindings). Where an AI-prompt output asks a model a question, a computed output runs a deterministic formula: same inputs, same answer, every time, no AI call. Use them for totals, day counts, formatted currency, concatenated addresses, and conditional flags. For the broader picture of how outputs fit into a field, see Outputs.

What you need

  • A template with at least one field whose value the calculation will use
  • Owner or member role to edit templates

Where calculations live

Open a field in the template editor and find its Outputs section, then click Add output. On a form-only template (no source PDF) this creates a derived output directly. On a PDF-backed template, Add output first places the field's value as a write zone — expand that entry's AI Instructions control, type any instruction, and click Add transform to turn it into a derived output. Either way, set the derived output's Kind picker to Computed (the other options are AI instruction, Static value, and Binding). The output gets its own name and key — the calculated result is a separate value from the field's inputs, independently placeable on the PDF with its own write zones.

The expression itself is written as JSON in the output's Computed expression box: an object with an op (the operation) and args (its arguments). Each argument is one of three shapes:

ShapeMeaningExample
{"literal": ...}A constant{"literal": ", "}
{"ref": "..."}A reference to a case-data value by key{"ref": "bid_amount"}
A nested expressionAnother {op, args} object{"op":"add","args":[...]}

Expressions nest up to 10 levels deep, so you can compose operations — round a division, format the result as currency, wrap the whole thing in a conditional.

What an expression can do

The full operation catalog:

GroupOperations
Mathadd, subtract, multiply, divide, round(value, decimals), abs
Aggregates (take an array)sum, avg, count, min, max
FormattingformatCurrency(value, code) (e.g. USD → $12,625.58), formatPercent(value, decimals) (0.0875 → "8.75%"), formatDate(date, format) (tokens yyyy, MM, dd)
DatesaddDays, subtractDays, daysBetween(from, to) (whole days, to minus from)
Stringsconcat, substring, replace, uppercase, lowercase, titleCase, trim, isEmpty
Conditionalsif(condition, then, else), greaterThan, lessThan, equals
Booleanand, or, not

References ({"ref": ...}) accept several forms:

  • A plain keybid_amount reads that value from the case's data. Numeric strings are treated as numbers automatically.
  • A dotted pathaddress.city reads inside a structured value.
  • $today / $now — today's date and the current timestamp. Expressions using these re-evaluate on every view, so date math never goes stale.
  • An array projectionline_items[*].amount collects one column from a row-repeating (batch) field into an array, ready for sum/avg/count.
  • A key patternkeys:^line_item_\d+$ gathers every case-data value whose key matches the regular expression into an array. This powers "Total" fields that automatically include later-added line items.

Expressions never break a case or a document run. A bad reference, a divide-by-zero, or a malformed expression simply produces a blank value.

When expressions evaluate

Computed values stay current automatically — you never click "recalculate":

  • In the case detail, computed rows (in the Computed bucket of the Case Data panel) are evaluated against the case's current data. Expressions that reference $today, $now, an array projection, or a keys: pattern re-evaluate on every render — a "Days Until Bid Due" value counts down by itself.
  • At document generation, computed outputs are recomputed fresh before stamping, so the PDF always carries the value as of the moment it was generated. A manual override on the output's key wins over recomputation.
  • During extraction, a field's outputs evaluate in order — each output can use the field's input values and any output that came before it in the list.

A computed output that depends on a form-question input the user hasn't answered yet is dormant: the case detail shows it as pending with the missing inputs listed, rather than displaying a misleading partial result like 0 or a half-built string.

Worked examples

Days until a bid is due — given a date input with key bid_due_date:

{"op":"daysBetween","args":[{"ref":"$today"},{"ref":"bid_due_date"}]}

Positive means days remaining; negative means overdue. To show the word instead of a negative number:

{"op":"if","args":[
  {"op":"lessThan","args":[
    {"op":"daysBetween","args":[{"ref":"$today"},{"ref":"bid_due_date"}]},
    {"literal":0}
  ]},
  {"literal":"OVERDUE"},
  {"op":"daysBetween","args":[{"ref":"$today"},{"ref":"bid_due_date"}]}
]}

Full-address concatenation — given extracted inputs address_line_1, city, and state:

{"op":"concat","args":[
  {"ref":"address_line_1"},
  {"literal":", "},
  {"ref":"city"},
  {"literal":", "},
  {"ref":"state"}
]}

concat treats missing values as empty strings, so a case without a city renders the rest of the address rather than failing.

A field with a computed output
FieldBid Due Date
Inputextracted, key=bid_due_date
Outputcomputed, key=days_until_due, daysBetween($today, bid_due_date)

Reference inputs, not other outputs

The one rule that prevents the most confusing failure mode: point your refs at input keys (extracted or question values) or use literals — not at sibling static or binding outputs.

This bug is sneaky because extraction evaluates a field's outputs in sequence — where a sibling output's value is visible — so the same expression can look correct in one surface and render blank in the case detail. Sticking to input keys and literals makes the expression behave identically everywhere.

We use cookies to keep you signed in and improve the product. See our Cookie Policy.
Manage preferences