How to use templates

Creating a Template

  1. Prepare an Excel workbook (.xlsx) with your desired layout, labels, and formatting.
  2. Insert placeholders using {{field_name}} syntax in cells where dynamic data should appear.
  3. Upload the workbook via the Templates page in the application, choosing the appropriate template type (Batch, Formula, or Production Quote).

Generating a Report

  1. Navigate to a batch, formula, or production quote detail page.
  2. Click one of the available template buttons.
  3. The system renders the template against that resource and stores the generated report.
  4. Download the output .xlsx from the reports list.

Field Reference

Templates use a simple inline syntax with the following constructs:

Placeholders

Insert a value from the resource data:

{{field_name}}
{{nested.field_name}}

Dot paths drill into nested hashes. For example, {{formula.name}} resolves to data["formula"]["name"].

Formatting

By default, placeholders are rendered as strings or decimals depending on their type. {{numberFormat}} controls the numeric type and display: the cell is stored as a number (so it can be summed and used in formulas) with an Excel number format applied for display. When a {{numberFormat}} is mixed with static text in the same cell, the cell is stored as text instead.

Each function takes optional arguments, which are passed as key/value pairs after the field name.

Dates

CodeValueOutput
{{field_name}}2024-06-01 12:34:56 UTC2024-06-01 12:34:56 UTC
{{dateFormat field_name}}2024-06-01 12:34:56 UTC2024-06-01
{{dateFormat field_name format="full"}}2024-06-01 12:34:56 UTCSaturday, June 1, 2024
{{dateFormat field_name format="long"}}2024-06-01 12:34:56 UTCJune 1, 2024
{{dateFormat field_name format="medium"}}2024-06-01 12:34:56 UTCJun 1, 2024
{{dateFormat field_name format="short"}}2024-06-01 12:34:56 UTC6/1/24
{{dateFormat field_name format="%m/%d/%Y"}}2024-06-01 12:34:56 UTC06/01/2024

Numbers

CodeValueOutput
{{field_name}}1234.561234.56
{{numberFormat field_name "integer"}}1234.561,235
{{numberFormat field_name "integer" roundingMode="up"}}1234.501,235
{{numberFormat field_name "integer" roundingMode="down"}}1234.501,234
{{numberFormat field_name "decimal" precision=2}}1234.561,234.56
{{numberFormat field_name "decimal" precision=2 roundingMode="up"}}1234.5671,234.57

Currency

CodeValueOutput
{{field_name}}1234.561234.56
{{numberFormat field_name "currency"}}1234.56$1,234.56
{{numberFormat field_name "currency" currency="EUR"}}1234.56€1,234.56
{{numberFormat field_name "currency" currency="EUR" precision=0}}1234.56€1,235
{{numberFormat field_name "currency" currency="EUR" precision=0 roundingMode="down"}}1234.50€1,234
{{numberFormat field_name "currency" currency="EUR" precision=3}}1234.50€1,234.500

Percentages

CodeValueOutput
{{field_name}}0.12340.1234
{{numberFormat field_name "percentage"}}0.123412.34%
{{numberFormat field_name "percentage" precision=2}}0.123412.34%
{{numberFormat field_name "percentage" precision=2 roundingMode="up"}}0.1234512.35%

Measurements

CodeValueOutput
{{field_name}}125.5 g125.5 g
{{numberFormat field_name "integer"}}125 g125
{{numberFormat field_name "decimal" precision=2}}125 g125.00
{{measurementFormat field_name}}125.5 g125.5 g
{{measurementFormat field_name precision=2}}125 g125.00 g
{{measurementFormat field_name precision=2 unit="kg"}}125 g0.13 kg

Loops

Repeat a block of rows for each item in a collection:

[[#each collection_name]]
  ...rows to repeat...
[[/each]]

The collection name must match a top-level key in the data that resolves to an array. Inside the block, placeholders resolve against each item first, then fall back to the outer scope.

Empty collections: At the top level, body rows are preserved (for a "no items" message). Nested (inside another [[#each]]), body rows are removed.

Inline loops

To render all items of a collection into a single cell (rather than expanding rows), wrap the body in an inline {{#each}} block inside the cell. Items are joined with an optional join separator:

{{#each orders}}{{size}}{{/each}}                    -> "1 kg2 kg3 kg"
{{#each orders join=", "}}{{size}}{{/each}}          -> "1 kg, 2 kg, 3 kg"
Orders: {{#each orders join=" / "}}{{size}}{{/each}} -> "Orders: 1 kg / 2 kg / 3 kg"

The body can mix placeholders, functions, and static text, and inline {{#each}} blocks can be nested. Placeholders resolve against each item first, then fall back to the outer scope, so an inline block placed inside a row-level [[#each]] can reference the row item's collections. An empty collection renders as an empty string.

Inline conditionals

To render part of a cell only when a condition resolves to a truthy value (anything other than nil or false), wrap it in an inline {{#if}} block:

{{#if completed_at}}{{dateFormat completed_at}}{{/if}}

An optional {{else}} branch renders when the condition is falsey:

{{#if completed_at}}{{dateFormat completed_at}}{{else}}Not completed{{/if}}

The condition is a data path and is resolved against the current scope with fallback to the outer scope, so inline {{#if}} blocks work inside [[#each]] and inline {{#each}} blocks. Blocks can be nested.

Conditionals

Include a block of rows conditionally:

[[#if condition]]
  ...rows...
[[/if]]

Currently the body rows are always included. The condition field is reserved for future use.

Unresolved Placeholders

Any placeholder that does not match a key in the data resolves to an empty string.


Template Types

There are three template types, each with a distinct set of available fields.

Batch Templates

Used to generate reports for batch resources.

Available fields:

FieldTypeDescription
brand_namestringBrand name (constant: "Brand Inc")
formula_namestringFull formula name: "RepositoryName / BranchName"
distribution_typestringDistribution channel label
formula_skustringSKU string: "EXT_ID - branch_num"
authored_atdatetimeBranch authorship timestamp
quantitydecimalBatch quantity
unitstringUnit of measurement
total_weightmeasurementFormatted total batch weight, e.g. "100 kg"
total_percentagepercentageFormatted total batch percentage, e.g. "100.00000000%"
positionintegerBatch position number
started_atdatetimeWhen production started
completed_atdatetimeWhen production completed (nullable)
notestextBatch notes
proceduretextProcedure text
initial_appearancestringInitial appearance observation
initial_colorstringInitial color observation
initial_phstringInitial pH reading
initial_gravitystringInitial specific gravity
initial_odorstringInitial odor observation
initial_viscosity_cpsstringInitial viscosity in cP
hour24_appearancestring24-hour appearance observation
hour24_colorstring24-hour color observation
hour24_phstring24-hour pH reading
hour24_gravitystring24-hour specific gravity
hour24_odorstring24-hour odor observation
hour24_viscosity_cpsstring24-hour viscosity in cP
range_ph_minstringpH specification minimum
range_ph_maxstringpH specification maximum
range_gravity_minstringGravity specification minimum
range_gravity_maxstringGravity specification maximum
range_viscosity_cps_minstringViscosity spec minimum (cP)
range_viscosity_cps_maxstringViscosity spec maximum (cP)
total_plate_count_specificationstringTPC specification limit
total_plate_count_test_methodstringTPC test method
total_plate_count_test_resulttextTPC test result
yeast_mold_count_specificationstringYMC specification limit
yeast_mold_count_test_methodstringYMC test method
yeast_mold_count_test_resulttextYMC test result
viscosity_procedurestringViscosity test procedure
viscosity_toolstringViscosity measurement tool
phasesarrayArray of phase objects (see below)
created_atdatetimeRecord creation timestamp
updated_atdatetimeRecord update timestamp

Phases and Ingredients

The phases field is an array intended for use with [[#each phases]]. Each phase object contains:

FieldTypeDescription
namestringPhase name (e.g. "Oil Phase")
ingredientsarrayArray of ingredient objects (for nested [[#each ingredients]])

Each ingredient object contains:

FieldTypeDescription
namestringMaterial trade name
inci_namestringINCI name
supplier_namestringSupplier name
weight_percentagepercentageFormatted percentage, e.g. "25.00000000%"
weightmeasurementFormatted weight, e.g. "25 kg"
lot_numberstringMaterial lot number
positionintegerIngredient position in phase
phasestringPhase name (e.g. "Oil Phase")
part_numberstringThe part number of the material

Formula Templates

Used to generate reports for formula resources.

Available fields

FieldTypeDescription
uuidstringUUID
external_idstringExternal identifier
is_masterbooleanIs this the master branch?
version_namestringVersion name
distribution_typestringDistribution channel label
authored_atdatetimeWhen the branch was authored
created_atdatetimeRecord creation timestamp
updated_atdatetimeWhen the branch was last updated
brand_namestringBrand name (constant: "Brand Inc")
formula_namestringFull formula name: "RepositoryName / BranchName"
formula_skustringSKU string: "EXT_ID - branch_num"
order_fill_sizemeasurementFormatted fill size of a single unit, e.g. "100 kg"
order_quantitydecimalOrder quantity
order_total_sizemeasurementFormatted total order size, e.g. "100 kg"
total_costmoneyFormatted total order cost, e.g. "$500.00"
total_cost_per_unitmoneyFormatted cost per unit, e.g. "$5.00"
total_cost_per_kgmoneyFormatted cost per kg, e.g. "$5.00"
total_excess_cost_per_unitmoneyFormatted excess cost per unit, e.g. "$0.00"
finalized_bystringEmail of the user who finalized the formula
proceduretextFormula procedure text
notestextFormula notes text
phasesarrayArray of phase objects (see below)

Phases and Ingredients

The phases field is an array intended for use with [[#each phases]]. Each phase object contains:

FieldTypeDescription
namestringPhase name (e.g. "Oil Phase")
ingredientsarrayArray of ingredient objects (for nested [[#each ingredients]])

Each ingredient object contains:

FieldTypeDescription
namestringMaterial trade name
part_numberstringThe part number of the selected material
inci_namestringINCI name
supplier_namestringSupplier name
weight_percentagepercentageFormatted percentage, e.g. "25.00000000%"
weightmeasurementFormatted weight, e.g. "25 kg"
cost_per_kgmoneyFormatted cost, e.g. "$5.00"
cost_percentagepercentageCost as a percentage of total, e.g. "25%"
pack_sizemeasurementSmallest order pack size, e.g. "1 kg"
lead_timestringFormatted lead time, e.g. "7 days"
total_costmoneyTotal ingredient cost, e.g. "$5.00"
used_up_costmoneyThe cost of the used up amount of the ingredient
leftover_costmoneyCost of unused portion, e.g. "$0.00"
in_stockbooleanIs the ingredient in stock?
ordersarrayArray of order objects (see below)

Each order object contains:

FieldTypeDescription
quantityfloatOrder quantity
sizemeasurementFormatted order size, e.g. "1 kg"
pricemoneyFormatted order price, e.g. "$5.00"

Production Quote Templates

Used to generate reports for production quote resources.

Available fields

FieldTypeDescription
uuidstringUUID of the quoted branch
external_idstringExternal identifier
is_masterbooleanIs this the master branch?
version_namestringVersion name
distribution_typestringDistribution channel label
created_atdatetimeRecord creation timestamp
authored_atdatetimeWhen the branch was authored
updated_atdatetimeWhen the record was last updated
brand_namestringBrand name (constant: "Brand Inc")
formula_namestringFull formula name: "RepositoryName / BranchName"
formula_skustringSKU string: "EXT_ID - branch_num"
fill_sizemeasurementFill size of a single unit, e.g. "1 kg"
quantityintegerNumber of units quoted
overagepercentageOverage applied, e.g. "5%"
overage_costmoneyCost added by the overage, e.g. "$5.00"
total_order_sizemeasurementFormatted total order size, e.g. "10 kg"
total_costmoneyFormatted total order cost, e.g. "$55.00"
total_cost_per_unitmoneyFormatted cost per unit, e.g. "$5.00"
total_cost_per_kgmoneyFormatted cost per kg, e.g. "$5.00"
total_excess_cost_per_unitmoneyFormatted excess cost per unit, e.g. "$0.25"
total_used_upmoneyFormatted cost of the used up amount, e.g. "$52.50"
total_leftover_costmoneyFormatted leftover cost, e.g. "$2.50"
total_percentagepercentageTotal ingredient percentage, e.g. "100%"
finalized_bystringEmail of the user who finalized the formula
proceduretextFormula procedure text
notestextFormula notes text
phasesarrayArray of phase objects (see below)

Phases and Ingredients

The phases field is an array intended for use with [[#each phases]]. Each phase object contains:

FieldTypeDescription
namestringPhase name (e.g. "Oil Phase")
ingredientsarrayArray of ingredient objects (for nested [[#each ingredients]])

Each ingredient object contains:

FieldTypeDescription
namestringMaterial trade name
part_numberstringThe part number of the selected material
inci_namestringINCI name
supplier_namestringSupplier name
weight_percentagepercentageFormatted percentage, e.g. "25.00000000%"
weightmeasurementFormatted weight, e.g. "2.5 kg"
cost_per_kgmoneyFormatted cost, e.g. "$5.00"
cost_percentagepercentageCost as a percentage of total, e.g. "25%"
pack_sizemeasurementSmallest order pack size, e.g. "1 kg"
lead_timestringFormatted lead time, e.g. "7 days"
total_costmoneyTotal ingredient cost, e.g. "$5.00"
used_up_costmoneyThe cost of the used up amount of the ingredient
leftover_costmoneyCost of unused portion, e.g. "$0.00"
in_stockbooleanIs the ingredient in stock?
ordersarrayArray of order objects (see below)

Each order object contains:

FieldTypeDescription
quantityfloatOrder quantity
sizemeasurementFormatted order size, e.g. "1 kg"
pricemoneyFormatted order price, e.g. "$5.00"

Output

Generated reports are stored as a report with the output .xlsx file attached for download.


Notes

  • Cell formatting (font, colors, borders, alignment, text wrap) is preserved from the template in the generated output.
  • Merged cell regions and column widths are carried over.
  • Placeholders can be mixed with static text in a single cell (e.g. "Product: {{name}} as of {{formatDate updated_at}}").
  • {{numberFormat}} cells are stored as numbers with an Excel number format (e.g. percentages store the fraction with a 0.00% format), so they can be used in SUM and other formulas.
  • Multiple worksheets in a single template workbook are each transformed independently.
  • Any field that resolves to nil renders as an empty string.