# Importing and exporting recipes using a Breww recipe file

Breww recipe files allow you to share recipes between Breww accounts or edit recipe definitions outside of Breww using any text editor. The files use the YAML format (`.breww-recipe.yaml`) and contain a complete description of a recipe's stages, actions, input variables, and calculated variables.

This is particularly useful for:

- **Sharing recipes** between different Breww accounts (e.g. between brewery locations or with contract brewing partners)
- **Backing up recipes** in a human-readable format
- **Editing recipes in bulk** using a text editor before importing them
- **Creating recipes programmatically** from external tools or spreadsheets

> **Note:**
> **Renamed:** What used to be called **reporting tags** are now **input variables**, and **calculated fields** are now **calculated variables**. The Breww recipe file format has been updated to use the new key names (`input_variables`, `calculated_variables`, `input_variable_name`, `calculated_variable_name`). The old key names (`reporting_tags`, `calculated_fields`, `reporting_tag_name`, `calculated_field_name`) are still accepted on import for backwards compatibility, so existing recipe files will continue to work — but they're deprecated and exports now always use the new names. We recommend updating any recipe files you maintain to use the new names.

## Exporting a recipe

You can export any recipe as a Breww recipe file from its detail page.

1. Navigate to the recipe you want to export (via **Production** -> **View** -> **Recipes**, then select the recipe)
2. Click the **Actions & tools** button in the page header
3. Select **Export as Breww recipe file**

This will download a `.breww-recipe.yaml` file containing the recipe's current version. The file is named using the pattern `Recipe Name - v1.breww-recipe.yaml`.

You can also export a specific version of a recipe by navigating to that version's detail page (via **Actions & tools** -> **View version history** on the recipe, then selecting the version) and using the same **Export as Breww recipe file** option.

The exported file will contain:
- Recipe details (name, beer name, batch size, brewhouse efficiency, expected wastage)
- All stages with their type, category, duration, and settings
- All actions within each stage (ingredients, readings, temperature changes, checks, etc.), including any targets and threshold bands defined on reading or calculation actions
- Any input variables referenced by actions in the recipe
- Any calculated variables referenced by actions in the recipe (including their measurement type)

## Importing a recipe

To import a Breww recipe file, head to **Production** -> **View** -> **Recipes** and click either:
- **New recipe** -> **Import from Breww recipe file**, or
- The **Import Breww recipe file** button on the new recipe page

The import process uses a multi-step wizard to guide you through the process.

### Step 1: Upload file

Select the `.breww-recipe.yaml` file you wish to import. Breww will validate the file structure and report any errors found in the file.

> Note: Breww recipe files can be opened and edited with any text editor. For a better experience, we recommend Visual Studio Code (free, Windows/Mac/Linux) which provides syntax highlighting and formatting for YAML files.

### Step 2: Recipe details

Confirm or adjust the basic recipe details parsed from the file:

- **Recipe name** - Pre-filled from the file. Must be unique within your account.
- **Beer** - If the file contains a `beer_name` and a beer with a matching name exists in your account, it will be pre-selected. You can change this or leave it blank (you can link the recipe to a beer later).
- **Batch size** - Pre-filled from the file, converted to your account's production volume unit.
- **Brewhouse efficiency** - Pre-filled from the file if specified.
- **Expected wastage** - Pre-filled from the file if specified.

### Step 3: Stock item mapping

For each ingredient referenced in the recipe file, you need to map it to a stock item in your Breww account. Breww will automatically attempt to match ingredients by name:

- **Exact match found** (green badge) - The ingredient name in the file exactly matches a stock item name in your account.
- **Similar match found** (amber badge) - A stock item with a very similar name was found. Check that this is the correct item.
- **No match found** (red badge) - No matching stock item could be found. You will need to manually select the correct stock item from the dropdown.

Breww will also validate that the unit type of the selected stock item is compatible with the unit used in the recipe file (e.g. you cannot map a recipe ingredient specified in kilograms to a stock item tracked in litres).

If the recipe does not contain any ingredient additions, this step will be skipped.

### Step 4: Stage type mapping

For each stage type used in the recipe, you need to map it to a stage type in your Breww account. Breww will automatically match stage types by name within the same category (e.g. Brewing, Fermenting).

- **Exact match found** (green badge) - A stage type with the same name and category already exists in your account.
- **New stage type will be created** (blue badge) - No matching stage type was found. If you leave the field blank, a new stage type will be created automatically with the name and category from the file.

You can also select a different existing stage type from the dropdown if you prefer.

### Step 5: Input variables and calculated variables

This step only appears if the recipe file includes input variables or calculated variables.

For each input variable and calculated variable defined in the file:

- If a matching variable already exists in your account, it will show as **Use existing** and no action is needed.
- If no match exists, you can choose to **Create** it in your account or **Ignore from recipe** (which will skip any actions that reference it).

### Step 6: Review

The final step shows a summary of the import, including:

- Recipe name, beer, and batch size
- Total number of stages and actions
- Stock item mappings
- Stage type mappings
- Input variable and calculated variable actions

Review the summary and click **Import recipe** to create the recipe. The imported recipe will be created as a draft version, ready for you to review and tweak.

## Breww recipe file format reference

A Breww recipe file is a YAML document with four top-level sections: `recipe`, `stages`, `input_variables`, and `calculated_variables`. Only `recipe` and `stages` are required.

Here is a minimal example:

```yaml
recipe:
  name: Simple Pale Ale
  batch_size: 1000 l

stages:
  - name: Mash
    type:
      name: Single Infusion Mash
      category: brewing
    actions:
      - type: add_ingredient
        trigger:
          type: start_of_stage
        stock_item_name: Pale Malt
        quantity: 200 kg
```

### The `recipe` section

The `recipe` section defines the top-level recipe details.

| Field                  | Required | Description                                                                                                                                     |
| ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                 | Yes      | The recipe name.                                                                                                                                |
| `beer_name`            | No       | The name of the beer this recipe is for. Used to auto-match to a beer in your account during import.                                            |
| `batch_size`           | Yes      | The batch size as a quantity with a unit (e.g. `1000 l`, `30 bbl`, `10 hl`). See the [units section](#units-on-quantities) for supported units. |
| `brewhouse_efficiency` | No       | Brewhouse efficiency as a percentage value (e.g. `75` for 75%).                                                                                 |
| `expected_wastage`     | No       | Expected wastage as a quantity with a unit (e.g. `5 l`, `0.5 bbl`).                                                                             |

### The `stages` section

The `stages` section is a list of recipe stages in order. Each stage represents a phase of the brewing process (e.g. Mash, Boil, Fermentation).

| Field                           | Required | Default | Description                                                                                             |
| ------------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------- |
| `name`                          | Yes      |         | The display name for this stage.                                                                        |
| `type`                          | Yes      |         | An object containing `name` (stage type name) and `category` (see below).                               |
| `duration`                      | No       |         | The duration of this stage (e.g. `1 hour`, `7 days`, `1 hour 30 minutes`). See [durations](#durations). |
| `repeated_per_turn`             | No       | `false` | Whether this stage is repeated for each turn in a multi-turn batch.                                     |
| `auto_allocate_stock`           | No       | `true`  | Whether to auto-allocate stock for ingredient additions in this stage.                                  |
| `include_in_pre_allocate_stock` | No       | `true`  | Whether to include this stage's ingredients in pre-allocation when starting a batch.                    |
| `actions`                       | No       | `[]`    | A list of actions to perform during this stage.                                                         |

#### Stage type categories

Each stage type must belong to one of these categories:

| Category value      | Description                                                              |
| ------------------- | ------------------------------------------------------------------------ |
| `preparation`       | Preparation stages (e.g. water treatment, starter preparation).          |
| `brewing`           | Brewing stages (e.g. mash, boil, whirlpool).                             |
| `fermenting`        | Fermentation stages (e.g. primary fermentation, secondary fermentation). |
| `post_fermentation` | Post-fermentation stages (e.g. conditioning, dry hopping).               |

### Actions

Each stage can contain a list of actions. Every action must have a `type` field and a `trigger` field. The `type` determines what kind of action it is, and the `trigger` determines when it should happen.

All action types support an optional `notes` field for additional free-text notes.

#### Action type: `add_ingredient`

Adds a stock item (ingredient) to the brew.

| Field                          | Required | Description                                                                                              |
| ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------- |
| `stock_item_name`              | Yes      | The name of the ingredient (mapped to a stock item during import).                                       |
| `quantity`                     | Yes      | The quantity to add, with a unit (e.g. `200 kg`, `500 g`, `2 units`).                                    |
| `ingredient_attributes`        | No       | A dictionary of attribute names and their decimal values (e.g. `{alpha_acid: 5.5}`).                     |
| `adjust_quantity_by_attribute` | No       | The name of an attribute to use for automatic quantity adjustment (e.g. `alpha_acid` for hop additions). |
| `notes`                        | No       | Additional notes for this action.                                                                        |

#### Action type: `add_water`

Adds water to the brew.

| Field      | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quantity` | Yes      | The volume of water to add, with a unit (e.g. `100 l`, `5 gal`, `750 ml`). If you use litres, hectolitres, gallons (`gal` / `imp gal`) or barrels (`bbl` / `imp bbl`), that unit is kept against the water addition and used whenever the amount is displayed in Breww, so a recipe written in gallons stays in gallons. Other volume units, such as `ml`, are converted and shown in your account's production volume unit. |
| `notes`    | No       | Additional notes for this action.                                                                                                                                                                                                                                                                                                                                                                                            |

#### Action type: `reading`

Records a reading (e.g. gravity, temperature, pH).

| Field                    | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reading_type`           | Yes      | The type of reading. Must be one of: `temperature`, `ph`, `gravity`, `time`, `duration`, `weight`, `volume`, `distance`, `pressure`, `co2`, `percentage`, `dissolved_oxygen`, `colour`, `bitterness`, `haze`, or `custom`.                                                                                                                                                                                                                                                                |
| `reading_unit`           | No       | The unit for the reading (e.g. `SG`, `plato`, `brix`, `c`, `f`, `ph`, `l`, `kg`). Only needed when `target` and `thresholds` values do not include a unit suffix. See [specifying units on reading values](#specifying-units-on-reading-values) below.                                                                                                                                                                                                                                    |
| `target`                 | No       | The target value for this reading (e.g. `"1.050 SG"`, `"65 °C"`, `"5.4 pH"`). When set, brewers see the target on the brew sheet, and any deviation can produce a warning or alert via the optional `thresholds` block. For `time` (a time of day) readings, the target is an `HH:MM:SS` string (e.g. `"01:30:00"`). For `duration` readings (an elapsed length of time), the target is either a plain number of minutes (e.g. `"90"`) or a number with a time unit (e.g. `"1.5 hours"`). |
| `expected_value`         | No       | **Legacy alias for `target`** — accepted on import for backwards compatibility with older recipe files. Exports always use `target`. We recommend updating any maintained recipe files to use `target`.                                                                                                                                                                                                                                                                                   |
| `thresholds`             | No       | An optional [threshold block](#threshold-blocks) defining `acceptable` and/or `warning` ranges for the reading. Values outside `acceptable` show a warning, and values outside `warning` show an alert on the brew sheet.                                                                                                                                                                                                                                                                 |
| `copy_to_batch_readings` | No       | Set to `true` to copy this reading to the batch's analysis tab. Defaults to `false`.                                                                                                                                                                                                                                                                                                                                                                                                      |
| `input_variable_name`    | No       | The variable name of an input variable to associate with this reading. Must be defined in the `input_variables` section or already exist on your account. The legacy alias `reporting_tag_name` is also accepted for backwards compatibility.                                                                                                                                                                                                                                             |
| `custom_reading_name`    | No       | A custom name for this reading (only used when `reading_type` is `custom`).                                                                                                                                                                                                                                                                                                                                                                                                               |
| `notes`                  | No       | Additional notes for this action.                                                                                                                                                                                                                                                                                                                                                                                                                                                         |

> Tip: For full background on what targets and threshold bands mean in Breww — including the difference between warnings and alerts, brewery-level defaults, and how thresholds are evaluated — see [Deviation thresholds and targets](https://breww.com/docs/deviation-thresholds-and-targets/).

##### Specifying units on reading values

For reading types that use units (`temperature`, `gravity`, `ph`, `volume`, `weight`, `distance`, `pressure`, `co2`, `dissolved_oxygen`, `colour`, `bitterness`, `haze`), the unit must be provided. There are two ways to do this:

**Approach 1: Include the unit in the value string (recommended)**

The simplest approach is to include the unit directly in the value, e.g. `"1.050 SG"`, `"65 °C"`, `"5.2 pH"`, `"2350 l"`, `"50 kg"`. This is the recommended approach because the unit is clear and self-contained.

```yaml
# Target only:
- type: reading
  trigger:
    type: start_of_stage
  reading_type: gravity
  target: "1.050 SG"

# Target with an acceptable threshold range:
- type: reading
  trigger:
    type: start_of_stage
  reading_type: gravity
  target: "1.050 SG"
  thresholds:
    acceptable:
      min: "1.045 SG"
      max: "1.055 SG"
```

**Approach 2: Use the `reading_unit` field**

Alternatively, you can specify the unit separately using the `reading_unit` field and provide plain numeric values. This works for `target` and any `thresholds` ranges:

```yaml
- type: reading
  trigger:
    type: start_of_stage
  reading_type: ph
  reading_unit: ph
  target: "5.3"
  thresholds:
    acceptable:
      min: "5.2"
      max: "5.4"
```

> Note: If you include units in the value strings **and** specify `reading_unit`, the two must agree. For example, using `reading_unit: Plato` with `target: "1.050 SG"` will produce an error.

When all values include a unit suffix, `reading_unit` is not needed. When values are plain numbers, `reading_unit` is required for reading types that use units.

All values within a single reading action - `target` and every threshold bound, whether written as `min` / `max` or as `min_offset` / `max_offset` - must use the same unit. For example, you cannot mix `"1.045 SG"` and `"10.5 Plato"` in the same action.

##### Supported reading units

The following units are supported in reading values and the `reading_unit` field:

**Temperature:**

| Unit | Aliases           |
| ---- | ----------------- |
| `°C` | `C`, `celsius`    |
| `°F` | `F`, `fahrenheit` |

**Gravity:**

| Unit    | Aliases |
| ------- | ------- |
| `SG`    | `sg`    |
| `Plato` | `plato` |
| `Brix`  | `brix`  |

**pH:**

| Unit | Aliases |
| ---- | ------- |
| `pH` | `ph`    |

**Volume:**

| Unit      | Aliases                                                  |
| --------- | -------------------------------------------------------- |
| `l`       | `litre`, `litres`, `liter`, `liters`                     |
| `hl`      | `hectolitre`, `hectolitres`, `hectoliter`, `hectoliters` |
| `gal`     | `gallon`, `gallons`                                      |
| `bbl`     | `barrel`, `barrels`                                      |
| `imp_gal` |                                                          |

**Weight:**

| Unit | Aliases                  |
| ---- | ------------------------ |
| `kg` | `kilogram`, `kilograms`  |
| `lb` | `lbs`, `pound`, `pounds` |

**Distance:**

| Unit | Meaning    | Aliases                    |
| ---- | ---------- | -------------------------- |
| `mm` | Millimetre | `millimetre`, `millimeter` |
| `cm` | Centimetre | `centimetre`, `centimeter` |
| `m`  | Metre      | `metre`, `meter`           |
| `in` | Inch       | `inch`                     |
| `ft` | Foot       | `foot`, `feet`             |

**Pressure:**

| Unit  | Meaning                |
| ----- | ---------------------- |
| `bar` | Bar                    |
| `psi` | Pounds per square inch |

**CO₂:**

| Unit  | Meaning              | Aliases |
| ----- | -------------------- | ------- |
| `gl`  | Grams per litre      | `g/l`   |
| `mgl` | Milligrams per litre | `mg/l`  |
| `vol` | Volumes              |         |

**Dissolved oxygen:**

| Unit  | Meaning              | Aliases |
| ----- | -------------------- | ------- |
| `ppm` | Parts per million    |         |
| `ppb` | Parts per billion    |         |
| `mgl` | Milligrams per litre | `mg/l`  |

**Colour:**

| Unit  | Meaning |
| ----- | ------- |
| `ebc` | EBC     |
| `srm` | SRM     |

**Bitterness:**

| Unit  | Meaning |
| ----- | ------- |
| `ibu` | IBU     |

**Haze:**

| Unit   | Meaning |
| ------ | ------- |
| `ebc`  | EBC     |
| `ntu`  | NTU     |
| `asbc` | ASBC    |
| `ftu`  | FTU     |

> Note: `percentage` readings are unit-less. Write the `target` and any `thresholds` values as plain numbers with no `%` suffix (for example `target: "4.5"`). Breww adds the `%` automatically when displaying the value, in the same way it does for calculation values.

`percentage`, `time`, `duration`, and `custom` reading types do not require a unit.

`time` reading targets (a time of day) are written as `HH:MM:SS` strings (e.g. `"01:30:00"`). `duration` reading targets (an elapsed length of time) are written either as a plain number of minutes (e.g. `"90"`) or as a number with a time unit (e.g. `"1.5 hours"`, `"90 minutes"`) — both are stored as minutes. Threshold ranges for both use signed offsets with a time unit - see [Threshold blocks](#threshold-blocks) below.

#### Action type: `change_temperature`

Sets a target temperature.

| Field    | Required | Description                                                                  |
| -------- | -------- | ---------------------------------------------------------------------------- |
| `target` | Yes      | The target temperature (e.g. `65 °C`, `149 °F`). The `°` symbol is optional. |
| `notes`  | No       | Additional notes for this action.                                            |

#### Action type: `check`

Adds a check (a yes/no verification step) to the brew sheet.

| Field                 | Required | Description                                                                                                                  |
| --------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `check_to_make`       | Yes      | The description of what should be checked.                                                                                   |
| `input_variable_name` | No       | The variable name of an input variable to associate with this check. The legacy alias `reporting_tag_name` is also accepted. |
| `notes`               | No       | Additional notes for this action.                                                                                            |

#### Action type: `custom_instructions`

Adds custom free-text instructions to the brew sheet.

| Field          | Required | Description                       |
| -------------- | -------- | --------------------------------- |
| `instructions` | Yes      | The instruction text.             |
| `notes`        | No       | Additional notes for this action. |

#### Action type: `take_notes`

Adds a note-taking prompt to the brew sheet.

| Field        | Required | Description                       |
| ------------ | -------- | --------------------------------- |
| `note_title` | Yes      | The title/prompt for the notes.   |
| `notes`      | No       | Additional notes for this action. |

#### Action type: `calculation`

Adds a calculated variable result to the brew sheet.

| Field                      | Required | Description                                                                                                                                                                                                                                                                                          |
| -------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `calculated_variable_name` | Yes      | The variable name of the calculated variable. Must be defined in the `calculated_variables` section or already exist on your account. The legacy alias `calculated_field_name` is also accepted.                                                                                                     |
| `target`                   | No       | The target value for the calculation result, as a plain number (e.g. `"75"`). Calculation targets and thresholds are always **unit-less** — never include a unit suffix. When set, a deviation from the target on the brew sheet can produce a warning or alert via the optional `thresholds` block. |
| `thresholds`               | No       | An optional [threshold block](#threshold-blocks) defining `acceptable` and/or `warning` ranges. Every bound must be a plain number with no unit suffix.                                                                                                                                              |
| `notes`                    | No       | Additional notes for this action.                                                                                                                                                                                                                                                                    |

### Threshold blocks

Reading and calculation actions can include a `thresholds` block that defines the bands within which a measurement is considered acceptable or only mildly off-target. When a brewer enters a value on the brew sheet that falls outside these bands, Breww will display a warning or an alert.

For the full feature overview - including the difference between warnings and alerts, brewery-level defaults, and how thresholds are evaluated against readings - see [Deviation thresholds and targets](https://breww.com/docs/deviation-thresholds-and-targets/).

#### Structure

A `thresholds` block has up to two bands - `acceptable` and `warning` - and at least one must be defined:

```yaml
thresholds:
  acceptable:
    min: "<lower bound>"
    max: "<upper bound>"
  warning:
    min: "<lower bound>"
    max: "<upper bound>"
```

- A reading inside the `acceptable` band is fine.
- A reading outside `acceptable` but inside `warning` shows a warning on the brew sheet.
- A reading outside `warning` shows an alert on the brew sheet.

You can define just `acceptable`, just `warning`, or both. If only `acceptable` is given, anything outside it shows an alert. If only `warning` is given, anything outside it shows an alert and there's no warning band in between. When you give both, `acceptable` must sit within `warning`.

[Screenshot needed: brew sheet showing a reading with an acceptable band, a warning band, and an alert state]

#### Absolute values or offsets: the keys decide

A band can be written in either of two forms, and the keys you choose are what say which one you mean:

| Keys                        | Meaning                                                                                                              |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `min` / `max`               | **Absolute values**, measured against the action's `target`.                                                         |
| `min_offset` / `max_offset` | **Signed offsets** from the action's `target`. `min_offset` must be 0 or below, and `max_offset` must be 0 or above. |

```yaml
# Absolute values, measured against the target
target: "1.050 SG"
thresholds:
  acceptable:
    min: "1.045 SG"
    max: "1.055 SG"

# The same band written as offsets, which needs no target of its own
thresholds:
  acceptable:
    min_offset: "-0.005 SG"
    max_offset: "0.005 SG"
```

A few rules follow from this:

- A band uses one pair or the other, never both and never half of one. `min` on its own, or `min` alongside `max_offset`, is an import error.
- Both bands of a `thresholds` block must use the same form. You cannot write `acceptable` as absolute values and `warning` as offsets.
- An absolute band must surround the target: `min` at or below the target, and `max` at or above it.
- An offset band must surround the target too, which here means `min_offset` at or below 0 and `max_offset` at or above 0.

#### A fixed range with no target

You can give an absolute band without giving a `target`, when what you want to say is "anywhere in this range is fine":

```yaml
- type: reading
  trigger:
    type: start_of_stage
  reading_type: gravity
  thresholds:
    acceptable:
      min: "1.010 SG"
      max: "1.030 SG"
```

Breww stores threshold bands only as offsets from a target, so on import it derives a target from the midpoint of the band. The action above imports with a target of `1.020` and offsets of `-0.010` and `+0.010`. The bounds you wrote are kept exactly, and exporting the recipe again gives you back the same 1.010 to 1.030 range.

The midpoint of `acceptable` is used as the anchor. If you give only a `warning` band, its midpoint is used instead.

> Note: A derived target is shown to brewers on the brew sheet, so the action will display a target nobody typed. The band Breww enforces is exactly the one you wrote, and the target is a by-product of storing bands as offsets. If the midpoint isn't the figure you want brewers aiming at, set a `target` yourself and write the band around it.

#### Use the offset keys when you mean offsets

Because `min` and `max` are read as absolute values, writing an offset in them on an action that does have a target will be misread. Usually this is caught for you, because an absolute band has to surround the target:

```yaml
# Rejected on import: the band doesn't surround the target
target: "1.050 SG"
thresholds:
  acceptable:
    min: "-0.005 SG"
    max: "0.005 SG"
```

It slips through only when the target happens to fall between the two numbers, which means targets at or near zero: calculations, percentages, and cold-crash temperatures at or below 0 °C. A calculation with `target: "2"` and a band of `min: "-5"` / `max: "5"` is accepted, and stored as 7 below the target to 3 above it rather than the symmetric band that was meant.

Written with the offset keys, the same band is read correctly whatever the target is:

```yaml
target: "2"
thresholds:
  acceptable:
    min_offset: "-5"
    max_offset: "5"
```

#### Units on threshold values (reading actions with a unit)

For reading types other than `time` and `duration` (`temperature`, `gravity`, `ph`, `volume`, `weight`, `distance`, `pressure`, `co2`, `dissolved_oxygen`, `colour`, `bitterness`, `haze`), each bound is written with a unit suffix, exactly the same way `target` is written. **All values within the action - `target` and every threshold bound, whichever pair of keys it uses - must use the same unit.** Mixing units within a single action (for example a `target` in `SG` and a threshold in `Plato`) will produce an import error.

Example - gravity reading with both an acceptable and a warning band:

```yaml
- type: reading
  trigger:
    type: start_of_stage
  reading_type: gravity
  target: "1.050 SG"
  thresholds:
    acceptable:
      min: "1.045 SG"
      max: "1.055 SG"
    warning:
      min: "1.040 SG"
      max: "1.060 SG"
```

Example - temperature reading using `°C`:

```yaml
- type: reading
  trigger:
    type: start_of_stage
  reading_type: temperature
  target: "19 °C"
  thresholds:
    acceptable:
      min: "18.5 °C"
      max: "19.5 °C"
    warning:
      min: "18 °C"
      max: "20 °C"
```

#### Units on threshold values (time of day reading actions)

`time` readings record a time of day, and their `target` is an `HH:MM:SS` string rather than a value with a unit. There's no numeric scale for an absolute band to be measured against, so bands on `time` readings are always signed offsets. Write them with `min_offset` and `max_offset`, each carrying a time unit (`second`, `minute`, `hour`, or `day`):

```yaml
- type: reading
  trigger:
    type: start_of_stage
  reading_type: time
  target: "01:30:00"
  thresholds:
    acceptable:
      min_offset: "-5 minutes"
      max_offset: "10 minutes"
```

In this example, the reading is acceptable from 5 minutes before the target up to 10 minutes after.

#### Units on threshold values (duration reading actions)

`duration` readings record an elapsed length of time rather than a clock time. The `target` is held as a number of minutes (optionally written with a time unit, which Breww converts to minutes), while the band carries a time unit of its own. As with `time` readings, that leaves no common scale for an absolute band, so the bounds are always signed offsets written as `min_offset` and `max_offset`:

```yaml
- type: reading
  trigger:
    type: start_of_stage
  reading_type: duration
  target: "90 minutes"
  thresholds:
    acceptable:
      min_offset: "-5 minutes"
      max_offset: "5 minutes"
```

In this example, the target is 1 hour 30 minutes, and a recorded duration is acceptable anywhere from `1h 25m` to `1h 35m`.

> Tip: For the difference between a `time` (time of day) and a `duration` reading, and how each behaves in a calculation, see [Input variables and calculated variables](https://breww.com/docs/input-and-calculated-variables/).

#### Percentage thresholds (unit-less)

`percentage` readings are unit-less. The `target`, and every bound in the `thresholds` block, must be a plain number with no `%` suffix - Breww adds the `%` automatically when displaying the value:

```yaml
- type: reading
  trigger:
    type: start_of_stage
  reading_type: percentage
  target: "4.5"
  thresholds:
    acceptable:
      min: "4.2"
      max: "4.8"
```

#### Calculation thresholds (unit-less)

Calculation actions are always unit-less. The `target`, and every bound in the `thresholds` block, must be a plain number with no unit suffix, and not `NaN` or `Infinity`:

```yaml
- type: calculation
  trigger:
    type: start_of_stage
  calculated_variable_name: apparent_attenuation
  target: "75"
  thresholds:
    acceptable:
      min: "70"
      max: "80"
```

The key rules above apply here unchanged: `min` / `max` for absolute values measured against the target, `min_offset` / `max_offset` for a band written relative to it.

Including a unit suffix on a calculation `target` or threshold value (e.g. `"75 percent"`) will produce an import error.

When the linked calculated variable's `measurement_type` is `time` (a time of day) or `duration`, the `target` and threshold values are still plain numbers, but they are expressed in **minutes**: minutes since midnight for a time of day (e.g. `"510"` for `08:30`), or elapsed minutes for a duration (e.g. `"90"` for `1h 30m`).

[Screenshot needed: editing a reading or calculation action's target and thresholds in the recipe editor]

#### What Breww writes when exporting

Exported files always use the form that can't be misread:

- An action that has a `target` exports its bands as `min` / `max` absolute values, which is what reads most naturally to a brewer.
- An action with no target exports its bands as `min_offset` / `max_offset`.
- `time` and `duration` readings always export as `min_offset` / `max_offset`, since their bands are offsets by nature.

#### Older recipe files

Recipe files written before the `min_offset` / `max_offset` keys existed still import as they always have, so there's nothing you need to change in a file you already have. Anything exported from Breww before this change keeps working too.

A legacy `min` / `max` band is read like this:

- On an action with a `target`, it's absolute.
- On an action with no target, it's settled by sign. A band that spans zero (for example `-0.005` to `0.005`) is read as the offsets older files carried there. Anything else is a fixed range, and gets a target derived from its midpoint as described above.

When you're writing or editing a file by hand, prefer the explicit keys. They say what you mean, and they don't depend on another field being present.

### Triggers

Every action requires a `trigger` that determines when the action should occur during the stage. The trigger is an object with a `type` field and, for some types, additional fields.

#### Trigger type: `start_of_stage`

The action occurs at the start of the stage. No additional fields required.

```yaml
trigger:
  type: start_of_stage
```

#### Trigger type: `immediately_after_previous_action`

The action occurs immediately after the previous action in the stage completes. No additional fields required.

```yaml
trigger:
  type: immediately_after_previous_action
```

#### Trigger type: `time_after_previous_action`

The action occurs a specified time after the previous action completes.

| Field      | Required | Description                                                        |
| ---------- | -------- | ------------------------------------------------------------------ |
| `duration` | Yes      | The delay after the previous action (e.g. `30 minutes`, `1 hour`). |

```yaml
trigger:
  type: time_after_previous_action
  duration: 30 minutes
```

#### Trigger type: `time_before_end_of_stage`

The action occurs a specified time before the end of the stage.

| Field      | Required | Description                                                     |
| ---------- | -------- | --------------------------------------------------------------- |
| `duration` | Yes      | The time before the end of stage (e.g. `15 minutes`, `1 hour`). |

```yaml
trigger:
  type: time_before_end_of_stage
  duration: 15 minutes
```

#### Trigger type: `time_after_start_of_stage`

The action occurs a specified time after the stage starts.

| Field      | Required | Description                                                           |
| ---------- | -------- | --------------------------------------------------------------------- |
| `duration` | Yes      | The time after the start of the stage (e.g. `2 hours`, `30 minutes`). |

```yaml
trigger:
  type: time_after_start_of_stage
  duration: 2 hours
```

#### Trigger type: `on_fermentation_gravity_reading`

The action is triggered when a gravity reading falls below a specified value.

| Field           | Required | Description                                    |
| --------------- | -------- | ---------------------------------------------- |
| `gravity_below` | Yes      | The gravity threshold value (e.g. `1.010`).    |
| `gravity_unit`  | Yes      | The gravity unit (e.g. `SG`, `plato`, `brix`). |

```yaml
trigger:
  type: on_fermentation_gravity_reading
  gravity_below: 1.010
  gravity_unit: SG
```

#### Trigger type: `end_of_stage`

The action occurs at the end of the stage. No additional fields required.

```yaml
trigger:
  type: end_of_stage
```

### Units on quantities

Quantity fields (such as `batch_size`, `quantity` on ingredients, and `expected_wastage`) must include a numeric value followed by a unit, separated by a space.

The following units are supported:

**Mass units:**

| Unit | Aliases                  |
| ---- | ------------------------ |
| `kg` | `kilogram`, `kilograms`  |
| `g`  | `gram`, `grams`          |
| `lb` | `lbs`, `pound`, `pounds` |
| `oz` | `ounce`, `ounces`        |

**Volume units:**

| Unit      | Aliases                                                  |
| --------- | -------------------------------------------------------- |
| `l`       | `litre`, `litres`, `liter`, `liters`                     |
| `hl`      | `hectolitre`, `hectolitres`, `hectoliter`, `hectoliters` |
| `ml`      | `millilitre`, `millilitres`                              |
| `gal`     | `gallon`, `gallons` (US gallons)                         |
| `bbl`     | `barrel`, `barrels` (US beer barrels)                    |
| `imp gal` | `imperial gallon`, `imperial gallons`                    |
| `imp bbl` | `imperial barrel`, `imperial barrels`                    |
| `fl oz`   | `floz`                                                   |

**Dimensionless units:**

| Unit   | Aliases |
| ------ | ------- |
| `unit` | `units` |

Examples: `200 kg`, `500 g`, `30 bbl`, `1000 l`, `5 gal`, `2 imp gal`, `2 units`, `3.5 lb`

> **Note:**
> `gal` and `bbl` are US gallons and US beer barrels. Use `imp gal` and `imp bbl` for British imperial gallons and barrels, so an imperial recipe can never be mistaken for a US one.

When exporting, Breww uses the display unit configured for each stock item (for ingredients) and your account's production volume unit for the batch size. Water additions are exported in the unit chosen against that addition in the recipe, falling back to your account's production volume unit where no unit was chosen, so a water addition entered in gallons exports and re-imports in gallons.

### Durations

Duration fields (such as stage `duration` and trigger `duration`) use natural language time expressions. Supported tokens are:

- `days` / `day`
- `hours` / `hour` / `hrs` / `hr`
- `minutes` / `minute` / `mins` / `min`
- `seconds` / `second` / `secs` / `sec`

Tokens can be combined: `1 hour 30 minutes`, `2 days 4 hours`.

Examples: `1 hour`, `7 days`, `30 minutes`, `1 hour 30 minutes`, `2 days 12 hours`

### Temperatures

Temperature values (in `change_temperature` target and `reading` target/threshold values for temperature readings) use the format:

```
<number> °C
<number> °F
```

The `°` symbol is optional, and full words (`celsius`, `fahrenheit`) are also accepted. Examples: `65 °C`, `149 °F`, `65 C`, `65 celsius`.

### The `input_variables` section

The `input_variables` section is an optional list of input variable definitions used by actions in the recipe. Each input variable is referenced by its `variable_name` from reading or check actions.

> **Note:**
> The legacy section name `reporting_tags` is still accepted on import for backwards compatibility, but exports always use `input_variables`. We recommend updating any existing recipe files you maintain to use `input_variables`.

| Field           | Required | Description                                                                                                                                                                                                                                     |
| --------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`          | Yes      | The display name of the input variable.                                                                                                                                                                                                         |
| `variable_name` | Yes      | The unique variable name used to reference this input variable in actions and formulas.                                                                                                                                                         |
| `action_type`   | Yes      | The type of action this input variable applies to. Must be `reading` or `check`.                                                                                                                                                                |
| `reading_type`  | No       | Required when `action_type` is `reading`. Must be one of: `temperature`, `ph`, `gravity`, `time`, `duration`, `weight`, `volume`, `distance`, `pressure`, `co2`, `percentage`, `dissolved_oxygen`, `colour`, `bitterness`, `haze`, or `custom`. |

Example:

```yaml
input_variables:
  - name: Original Gravity
    variable_name: og_reading
    action_type: reading
    reading_type: gravity
  - name: Clarity Check
    variable_name: clarity_check
    action_type: check
```

### The `calculated_variables` section

The `calculated_variables` section is an optional list of calculated variable definitions. These are formulas that compute values from input variables (and other calculated variables) and are displayed on the brew sheet.

> **Note:**
> The legacy section name `calculated_fields` is still accepted on import for backwards compatibility, but exports always use `calculated_variables`. We recommend updating any existing recipe files you maintain to use `calculated_variables`.

| Field              | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`             | Yes      | The display name of the calculated variable.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `variable_name`    | Yes      | The unique variable name for this calculated variable (can be referenced in other formulas).                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `formula`          | Yes      | The calculation formula, using input variable and calculated variable names.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `measurement_type` | No       | The kind of value this calculation produces. One of `temperature`, `gravity`, `ph`, `weight`, `volume`, `time`, `duration`, `distance`, `pressure`, `co2`, `percentage`, `dissolved_oxygen`, `colour`, `bitterness`, `haze`, or `unitless`. `time` means a time of day (a clock time) and `duration` means an elapsed length of time. Defaults to `unitless` when omitted. The measurement type controls how the calculated value is formatted on the brew sheet, and which unit is used when displaying any thresholds applied to the calculated variable. |

Example:

```yaml
calculated_variables:
  - name: Total Extract
    variable_name: total_extract
    formula: og_reading * batch_volume
    measurement_type: unitless
  - name: ABV Estimate
    variable_name: abv_estimate
    formula: (og_reading - fg_reading) * 131.25
    measurement_type: unitless
  - name: Mash Out Temperature
    variable_name: mash_out_temp
    formula: mash_temp + 10
    measurement_type: temperature
  - name: Mash Length
    variable_name: mash_length
    formula: mash_out_time - mash_in_time
    measurement_type: duration
  - name: Expected Knockout Time
    variable_name: expected_knockout_time
    formula: mash_in_time + duration("4h 30m")
    measurement_type: time
```

> **Warning:**
> The `measurement_type` should match what the `formula` actually produces. A formula that works out a clock time needs `time`, one that works out an elapsed length of time needs `duration`, and one that works out a plain number needs neither. A mismatch won’t stop the file importing, but the value will be displayed in the wrong format on the brew sheet, and Breww will ask you to correct it the next time you edit that calculated variable under **Settings & options** → **Calculated variables**. See [Input variables and calculated variables](https://breww.com/docs/input-and-calculated-variables/) for the rules on combining times and durations in a formula.

### Complete example

Here is a complete example showing all sections, action types, and trigger types:

```yaml
recipe:
  name: Full Test Recipe
  beer_name: My Test Beer
  batch_size: 500 l
  brewhouse_efficiency: 75
  expected_wastage: 5 l

stages:
  - name: Preparation Stage
    type:
      name: Starter Prep
      category: preparation
    duration: 1 hour
    repeated_per_turn: false
    auto_allocate_stock: true
    include_in_pre_allocate_stock: true
    actions:
      - type: add_ingredient
        trigger:
          type: start_of_stage
        stock_item_name: Water Treatment
        quantity: 10 g
        ingredient_attributes:
          alpha_acid: 5.5
        adjust_quantity_by_attribute: alpha_acid
        notes: Add carefully

      - type: add_water
        trigger:
          type: immediately_after_previous_action
        quantity: 100 l

      - type: reading
        trigger:
          type: time_after_previous_action
          duration: 30 minutes
        reading_type: gravity
        target: "1.050 SG"
        thresholds:
          acceptable:
            min: "1.045 SG"
            max: "1.055 SG"
          warning:
            min: "1.040 SG"
            max: "1.060 SG"
        copy_to_batch_readings: true
        input_variable_name: og_reading
        custom_reading_name: Original Gravity

      - type: change_temperature
        trigger:
          type: time_before_end_of_stage
          duration: 15 minutes
        target: 65 °C

      - type: check
        trigger:
          type: time_after_start_of_stage
          duration: 45 minutes
        check_to_make: Verify clarity
        input_variable_name: clarity_check

      - type: custom_instructions
        trigger:
          type: end_of_stage
        instructions: Clean all equipment thoroughly

      - type: take_notes
        trigger:
          type: time_after_start_of_stage
          duration: 2 hours
        note_title: Batch observations

      - type: calculation
        trigger:
          type: start_of_stage
        calculated_variable_name: total_extract
        target: "1500"
        thresholds:
          acceptable:
            min: "1450"
            max: "1550"

  - name: Fermentation
    type:
      name: Primary Fermentation
      category: fermenting
    duration: 7 days
    repeated_per_turn: true
    auto_allocate_stock: false
    include_in_pre_allocate_stock: false
    actions:
      - type: reading
        trigger:
          type: start_of_stage
        reading_type: temperature

input_variables:
  - name: Original Gravity
    variable_name: og_reading
    action_type: reading
    reading_type: gravity
  - name: Clarity Check
    variable_name: clarity_check
    action_type: check

calculated_variables:
  - name: Total Extract
    variable_name: total_extract
    formula: og_reading * batch_volume
    measurement_type: unitless
```
