# Calculations and piped answers

> Turn answers into numbers such as prices and scores, and show answers or results inside later questions and the ending of a trulyform form.

## What you need to know

- A calculation is a named number computed from answers, such as a price, a score or a count.
- It is a small expression tree: read answers with `answer`, add up choice scores with `option_score`, and combine values with operations that each take exactly two arguments.
- If an input has not been answered, the calculation has no value yet.
- Piping shows an earlier answer, or a calculation's result, inside a later question or on the ending screen.
- Calculations and piping are available on every plan.
- trulyform does not take payments. A calculation can show an amount, but collecting it happens elsewhere.

## Calculation format

Calculations live in the definition's `calculations` list:

| Key | Notes |
| --- | --- |
| `id` | A UUIDv7, unique within the form. |
| `name` | Required, up to 100 characters. The form owner sees it next to each response. |
| `decimals` | Required. Decimal places of the result, from 0 to 6. |
| `expression` | The root expression node. |

## Expression nodes

| Node | Shape | Value |
| --- | --- | --- |
| `constant` | `{"type": "constant", "value": "1.5"}` | A decimal number, written as a string. |
| `answer` | `{"type": "answer", "ref": "<question id>"}` | The answer to a `number`, `nps`, `opinion_scale` question. |
| `option_score` | `{"type": "option_score", "ref": "<question id>"}` | The total `score` of the options chosen in a `single_choice`, `dropdown`, `multiple_choice`, `ranking`, `matrix` question. |
| `calculation` | `{"type": "calculation", "ref": "<calculation id>"}` | The result of another calculation. |
| `add` | `{"type": "add", "args": [a, b]}` | a + b |
| `subtract` | `{"type": "subtract", "args": [a, b]}` | a − b |
| `multiply` | `{"type": "multiply", "args": [a, b]}` | a × b |
| `divide` | `{"type": "divide", "args": [a, b]}` | a ÷ b, to 6 decimal places. Dividing by zero gives no result. |
| `min` | `{"type": "min", "args": [a, b]}` | The smaller of a and b. |
| `max` | `{"type": "max", "args": [a, b]}` | The larger of a and b. |
| `round` | `{"type": "round", "args": [a, {"type": "constant", "value": "0"}]}` | a rounded half up to the number of decimal places in the second argument, 0 to 6. |

Operations take exactly two arguments, and an expression can be nested up to 12 levels deep. A form can have up to 100 calculations.

## How results are worked out

- Arithmetic is exact decimal arithmetic: 0.1 + 0.2 is 0.3.
- Every step keeps 6 decimal places, rounded half up. Division also stops at 6 places, so 1 ÷ 3 is 0.333333.
- The final result is rounded half up to the calculation's `decimals`.
- A `calculation` node uses the referenced calculation's unrounded value, not the result shown with its `decimals`. If one shows 1.5 as 2, another that doubles it gets 3. To build on the shown value, wrap the reference in a `round` node.
- Results must stay below 10^18 in size.
- An option without a `score` counts as 0. For multiple choice the scores of every chosen option are added; for a matrix, the score of the chosen column in every row.
- A calculation has no value while an input is unanswered or hidden, after dividing by zero, or when a reference is missing. `get_response` reports the reason as the calculation's `error`.
- A choice question with nothing chosen counts as unanswered, not as 0. For optional extras that feed a price, make the question required and add an option such as "No extras" with a score of "0".

## Example

The [workshop registration](https://trulyform.com/docs/examples.md#workshop-registration) prices a booking: each ticket option has a `score` (its price), and each "number of people" option a score (the count). The calculation multiplies the two.

```json title="Workshop registration: calculations"
[
    {
        "id": "01990000-0000-7000-8000-000000000131",
        "name": "Total price",
        "decimals": 2,
        "expression": {"type":"multiply","args":[{"type":"option_score","ref":"01990000-0000-7000-8000-000000000103"},{"type":"option_score","ref":"01990000-0000-7000-8000-000000000104"}]}
    }
]
```

The [customer feedback](https://trulyform.com/docs/examples.md#customer-feedback) survey averages a matrix: the scores of the three rows are added up and divided by 3, with one decimal place.

## Piped answers

An `answerToken` node shows an answer or a calculation result inside rich text:

```json title="Thanks, followed by the name the person typed"
{
    "type": "paragraph",
    "content": [
        { "type": "text", "text": "Thanks, " },
        {
            "type": "answerToken",
            "attrs": { "source": "answer", "ref": "01990000-0000-7000-8000-000000000001", "fallback": "there" }
        },
        { "type": "text", "text": "!" }
    ]
}
```

- `source` is `answer` with a question ID in `ref`, or `calculation` with a calculation ID.
- `fallback` is shown when there is nothing to show yet. It is required and may be an empty string. Write the text around a token so it still reads well with the fallback, such as "Estimated price (USD): " with the fallback "to be confirmed".
- A piped answer must come from an earlier question, and never from a statement. A piped calculation may only use questions that come earlier.
- The welcome screen comes before every question, so it cannot pipe answers. The ending can pipe any answer or calculation.

Piped answers appear as respondents see them: choice labels, several choices separated by commas, a consent answer as Yes or No, and a score followed by its comment. A piped calculation shows exactly `decimals` places, such as 120.00, with no thousands separator or currency symbol; write the symbol in the text around it.

The workshop registration's ending greets the person by name and shows the price:

```json title="Workshop registration: ending"
{
    "title": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"You are booked in, "},{"type":"answerToken","attrs":{"source":"answer","ref":"01990000-0000-7000-8000-000000000101","fallback":"friend"}},{"type":"text","text":"!"}]}]},
    "description": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Total to pay on the day (USD): "},{"type":"answerToken","attrs":{"source":"calculation","ref":"01990000-0000-7000-8000-000000000131","fallback":"to be confirmed"}},{"type":"text","text":"."}]}]}
}
```


---

Previous: [Logic](https://trulyform.com/docs/logic.md) · Next: [Design](https://trulyform.com/docs/design.md)

All pages: https://trulyform.com/llms.txt. Connected over MCP? Call read_docs with a page name instead.
