Templates reference
This is a full reference for templates and how to build them in Excel.
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
| Code | Value | Output |
|---|---|---|
{{field_name}} | 2024-06-01 12:34:56 UTC | 2024-06-01 12:34:56 UTC |
{{dateFormat field_name}} | 2024-06-01 12:34:56 UTC | 2024-06-01 |
{{dateFormat field_name format="full"}} | 2024-06-01 12:34:56 UTC | Saturday, June 1, 2024 |
{{dateFormat field_name format="long"}} | 2024-06-01 12:34:56 UTC | June 1, 2024 |
{{dateFormat field_name format="medium"}} | 2024-06-01 12:34:56 UTC | Jun 1, 2024 |
{{dateFormat field_name format="short"}} | 2024-06-01 12:34:56 UTC | 6/1/24 |
{{dateFormat field_name format="%m/%d/%Y"}} | 2024-06-01 12:34:56 UTC | 06/01/2024 |
Numbers
| Code | Value | Output |
|---|---|---|
{{field_name}} | 1234.56 | 1234.56 |
{{numberFormat field_name "integer"}} | 1234.56 | 1,235 |
{{numberFormat field_name "integer" roundingMode="up"}} | 1234.50 | 1,235 |
{{numberFormat field_name "integer" roundingMode="down"}} | 1234.50 | 1,234 |
{{numberFormat field_name "decimal" precision=2}} | 1234.56 | 1,234.56 |
{{numberFormat field_name "decimal" precision=2 roundingMode="up"}} | 1234.567 | 1,234.57 |
Currency
| Code | Value | Output |
|---|---|---|
{{field_name}} | 1234.56 | 1234.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
| Code | Value | Output |
|---|---|---|
{{field_name}} | 0.1234 | 0.1234 |
{{numberFormat field_name "percentage"}} | 0.1234 | 12.34% |
{{numberFormat field_name "percentage" precision=2}} | 0.1234 | 12.34% |
{{numberFormat field_name "percentage" precision=2 roundingMode="up"}} | 0.12345 | 12.35% |
Measurements
| Code | Value | Output |
|---|---|---|
{{field_name}} | 125.5 g | 125.5 g |
{{numberFormat field_name "integer"}} | 125 g | 125 |
{{numberFormat field_name "decimal" precision=2}} | 125 g | 125.00 |
{{measurementFormat field_name}} | 125.5 g | 125.5 g |
{{measurementFormat field_name precision=2}} | 125 g | 125.00 g |
{{measurementFormat field_name precision=2 unit="kg"}} | 125 g | 0.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"
This is useful for say splitting the INCI name of a formula ingredient on each line:
{{#each inci_names join="\n"}}{{name}}{{/each}} -> "Aqua\nGlycerin"
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:
| Field | Type | Description |
|---|---|---|
brand_name | string | Brand name (constant: "Brand Inc") |
formula_name | string | Full formula name: "RepositoryName / BranchName" |
distribution_type | string | Distribution channel label |
formula_sku | string | SKU string: "EXT_ID - branch_num" |
authored_at | datetime | Branch authorship timestamp |
quantity | decimal | Batch quantity |
unit | string | Unit of measurement |
total_weight | measurement | Formatted total batch weight, e.g. "100 kg" |
total_percentage | percentage | Formatted total batch percentage, e.g. "100.00000000%" |
position | integer | Batch position number |
started_at | datetime | When production started |
completed_at | datetime | When production completed (nullable) |
notes | text | Batch notes |
procedure | text | Procedure text |
initial_appearance | string | Initial appearance observation |
initial_color | string | Initial color observation |
initial_ph | string | Initial pH reading |
initial_gravity | string | Initial specific gravity |
initial_odor | string | Initial odor observation |
initial_viscosity_cps | string | Initial viscosity in cP |
hour24_appearance | string | 24-hour appearance observation |
hour24_color | string | 24-hour color observation |
hour24_ph | string | 24-hour pH reading |
hour24_gravity | string | 24-hour specific gravity |
hour24_odor | string | 24-hour odor observation |
hour24_viscosity_cps | string | 24-hour viscosity in cP |
range_ph_min | string | pH specification minimum |
range_ph_max | string | pH specification maximum |
range_gravity_min | string | Gravity specification minimum |
range_gravity_max | string | Gravity specification maximum |
range_viscosity_cps_min | string | Viscosity spec minimum (cP) |
range_viscosity_cps_max | string | Viscosity spec maximum (cP) |
total_plate_count_specification | string | TPC specification limit |
total_plate_count_test_method | string | TPC test method |
total_plate_count_test_result | text | TPC test result |
yeast_mold_count_specification | string | YMC specification limit |
yeast_mold_count_test_method | string | YMC test method |
yeast_mold_count_test_result | text | YMC test result |
viscosity_procedure | string | Viscosity test procedure |
viscosity_tool | string | Viscosity measurement tool |
phases | array | Array of phase objects (see below) |
created_at | datetime | Record creation timestamp |
updated_at | datetime | Record update timestamp |
Phases and Ingredients
The phases field is an array intended for use with [[#each phases]]. Each phase object contains:
| Field | Type | Description |
|---|---|---|
name | string | Phase name (e.g. "Oil Phase") |
ingredients | array | Array of ingredient objects (for nested [[#each ingredients]]) |
Each ingredient object contains:
| Field | Type | Description |
|---|---|---|
name | string | Material name, e.g. "Aqua" |
sku | string | Material SKU captured when the batch was created. If it has changed, includes the current SKU, e.g. "123 (now 456)" |
inci_name | string | INCI name |
inci_names | array | Each part of the INCI name, split on (and), as { name } objects |
supplier_name | string | Supplier name |
weight_percentage | percentage | Formatted percentage, e.g. "25.00000000%" |
weight | measurement | Formatted weight, e.g. "25 kg" |
lot_number | string | Material lot number |
position | integer | Ingredient position in phase |
phase | string | Phase name (e.g. "Oil Phase") |
part_number | string | Material part number captured when the batch was created. If it has changed, includes the current part number, e.g. "202-128 (now 202-129)" |
The sku and part_number fields reflect the values captured when the batch was created. When the material's identifiers have changed since then, the field renders the captured value followed by the current value in parentheses, e.g. 202-128 (now 202-129).
Formula Templates
Used to generate reports for formula resources.
Available fields
| Field | Type | Description |
|---|---|---|
uuid | string | UUID |
external_id | string | External identifier |
is_master | boolean | Is this the master branch? |
version_name | string | Version name |
distribution_type | string | Distribution channel label |
authored_at | datetime | When the branch was authored |
created_at | datetime | Record creation timestamp |
updated_at | datetime | When the branch was last updated |
brand_name | string | Brand name (constant: "Brand Inc") |
formula_name | string | Full formula name: "RepositoryName / BranchName" |
formula_sku | string | SKU string: "EXT_ID - branch_num" |
order_fill_size | measurement | Formatted fill size of a single unit, e.g. "100 kg" |
order_quantity | decimal | Order quantity |
order_total_size | measurement | Formatted total order size, e.g. "100 kg" |
total_cost | money | Formatted total order cost, e.g. "$500.00" |
total_cost_per_unit | money | Formatted cost per unit, e.g. "$5.00" |
total_cost_per_kg | money | Formatted cost per kg, e.g. "$5.00" |
total_excess_cost_per_unit | money | Formatted excess cost per unit, e.g. "$0.00" |
finalized_by | string | Email of the user who finalized the formula |
procedure | text | Formula procedure text |
notes | text | Formula notes text |
phases | array | Array of phase objects (see below) |
impurities | array | Array of impurity objects (see below) |
total_impurity_percentage | string | Combined weighted concentration of all impurities, e.g. "1% - 2%" |
Phases and Ingredients
The phases field is an array intended for use with [[#each phases]]. Each phase object contains:
| Field | Type | Description |
|---|---|---|
name | string | Phase name (e.g. "Oil Phase") |
ingredients | array | Array of ingredient objects (for nested [[#each ingredients]]) |
Each ingredient object contains:
| Field | Type | Description |
|---|---|---|
name | string | Material name, e.g. "Aqua" |
sku | string | Material SKU, e.g. "123" |
part_number | string | The part number of the selected material |
inci_name | string | INCI name |
inci_names | array | Each part of the INCI name, split on (and), as { name } objects |
supplier_name | string | Supplier name |
weight_percentage | percentage | Formatted percentage, e.g. "25.00000000%" |
weight | measurement | Formatted weight, e.g. "25 kg" |
cost_per_kg | money | Formatted cost, e.g. "$5.00" |
cost_percentage | percentage | Cost as a percentage of total, e.g. "25%" |
pack_size | measurement | Smallest order pack size, e.g. "1 kg" |
lead_time | string | Formatted lead time, e.g. "7 days" |
total_cost | money | Total ingredient cost, e.g. "$5.00" |
used_up_cost | money | The cost of the used up amount of the ingredient |
leftover_cost | money | Cost of unused portion, e.g. "$0.00" |
in_stock | boolean | Is the ingredient in stock? |
orders | array | Array of order objects (see below) |
Each order object contains:
| Field | Type | Description |
|---|---|---|
quantity | float | Order quantity |
size | measurement | Formatted order size, e.g. "1 kg" |
price | money | Formatted order price, e.g. "$5.00" |
Impurities and Sources
The impurities field is an array intended for use with [[#each impurities]]. Each impurity object contains:
| Field | Type | Description |
|---|---|---|
inci_name | string | INCI name of the impurity |
weight_percentage | percentage | Formatted concentration of the impurity in the formula |
cas_numbers | array | CAS numbers as { value } objects |
ec_numbers | array | EC numbers as { value } objects |
sources | array | Materials contributing this impurity (see below) |
Each source object contains:
| Field | Type | Description |
|---|---|---|
material_name | string | Name of the material that contains the impurity |
material_slug | string | Slug of the source material |
concentration_range | percentage range | Formatted concentration of the impurity in the material |
weight_percentage | percentage | Formatted amount of the material in the formula |
Production Quote Templates
Used to generate reports for production quote resources.
Available fields
| Field | Type | Description |
|---|---|---|
uuid | string | UUID of the quoted branch |
external_id | string | External identifier |
is_master | boolean | Is this the master branch? |
version_name | string | Version name |
distribution_type | string | Distribution channel label |
created_at | datetime | Record creation timestamp |
authored_at | datetime | When the branch was authored |
updated_at | datetime | When the record was last updated |
brand_name | string | Brand name (constant: "Brand Inc") |
formula_name | string | Full formula name: "RepositoryName / BranchName" |
formula_sku | string | SKU string: "EXT_ID - branch_num" |
fill_size | measurement | Fill size of a single unit, e.g. "1 kg" |
quantity | integer | Number of units quoted |
overage | percentage | Overage applied, e.g. "5%" |
overage_cost | money | Cost added by the overage, e.g. "$5.00" |
total_order_size | measurement | Formatted total order size, e.g. "10 kg" |
total_cost | money | Formatted total order cost, e.g. "$55.00" |
total_cost_per_unit | money | Formatted cost per unit, e.g. "$5.00" |
total_cost_per_kg | money | Formatted cost per kg, e.g. "$5.00" |
total_excess_cost_per_unit | money | Formatted excess cost per unit, e.g. "$0.25" |
total_used_up | money | Formatted cost of the used up amount, e.g. "$52.50" |
total_leftover_cost | money | Formatted leftover cost, e.g. "$2.50" |
total_percentage | percentage | Total ingredient percentage, e.g. "100%" |
finalized_by | string | Email of the user who finalized the formula |
procedure | text | Formula procedure text |
notes | text | Formula notes text |
phases | array | Array of phase objects (see below) |
Phases and Ingredients
The phases field is an array intended for use with [[#each phases]]. Each phase object contains:
| Field | Type | Description |
|---|---|---|
name | string | Phase name (e.g. "Oil Phase") |
ingredients | array | Array of ingredient objects (for nested [[#each ingredients]]) |
Each ingredient object contains:
| Field | Type | Description |
|---|---|---|
name | string | Material name, e.g. "Aqua" |
sku | string | Material SKU, e.g. "123" |
part_number | string | The part number of the selected material |
inci_name | string | INCI name |
inci_names | array | Each part of the INCI name, split on (and), as { name } objects |
supplier_name | string | Supplier name |
weight_percentage | percentage | Formatted percentage, e.g. "25.00000000%" |
weight | measurement | Formatted weight, e.g. "2.5 kg" |
cost_per_kg | money | Formatted cost, e.g. "$5.00" |
cost_percentage | percentage | Cost as a percentage of total, e.g. "25%" |
pack_size | measurement | Smallest order pack size, e.g. "1 kg" |
lead_time | string | Formatted lead time, e.g. "7 days" |
total_cost | money | Total ingredient cost, e.g. "$5.00" |
used_up_cost | money | The cost of the used up amount of the ingredient |
leftover_cost | money | Cost of unused portion, e.g. "$0.00" |
in_stock | boolean | Is the ingredient in stock? |
orders | array | Array of order objects (see below) |
Each order object contains:
| Field | Type | Description |
|---|---|---|
quantity | float | Order quantity |
size | measurement | Formatted order size, e.g. "1 kg" |
price | money | Formatted 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 a0.00%format), so they can be used inSUMand other formulas.- Multiple worksheets in a single template workbook are each transformed independently.
- Any field that resolves to nothing renders as an empty string.