> For the complete documentation index, see [llms.txt](https://docs.agilecase.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.agilecase.com/developer-documentation/case-scripting/calculated-fields.md).

# Calculated Fields

Scripts that run on the server to derive a field's value, every time that value is needed.

A calculated field holds a script instead of a value. Whenever the field's value is wanted — drawing the case tab, merging a document, answering the API — the script runs on the server and what it returns is the value.

Nothing is stored. There is no saved figure that can drift out of date, because there is no saved figure: the answer is worked out afresh each time it is asked for. That is the whole appeal, and it is also the constraint to design around.

{% hint style="info" %}
Unlike on load and on save scripts, a calculated field is not created under **Scripting** in the settings menu. It is a custom field type, configured on the field itself, as described in [Calculated Fields](/administrator-documentation/types-of-custom-field/calculated-fields.md) in the administrator documentation. This page covers writing the script that goes in it.
{% endhint %}

## The shape of a script

The script must define `calc()`, and `calc()` must return the value to display:

```javascript
function calc() {
    var amount = Service.CustomField(6304).ValueAsText
        .replace("£", "")
        .replace(/,/g, "");

    if (parseFloat(amount) >= 40000) {
        return String(((parseFloat(amount) * 0.03) + 60).toFixed(2));
    }

    return String((0).toFixed(2));
}
```

There is no trigger line and no `on_save`. `calc()` is the entry point and the only function AgileCase calls, but you can define as many others alongside it as the calculation needs — see [Building a Multi-Part Total](/developer-documentation/case-scripting/calculated-fields/building-a-multi-part-total.md).

## Where it runs

On the server, in the same V8 environment as an [on save script](/developer-documentation/case-scripting/on-save-scripts.md). There is no page, no jQuery, no `document` and no browser at all.

What it gets instead is `Service`, exactly as documented in the [Script API Reference](/developer-documentation/case-scripting/on-save-scripts/script-api-reference.md), plus the .NET `Convert` helpers.

Only the read side of `Service` is meaningful. A calculated field returns a value; it does not act on the case, and the methods that queue work — `AddTask`, `SendEmail`, `UpdateCustomField` and the rest — have no save to be carried out by.

<table><thead><tr><th width="290">Useful here</th><th>What it gives you</th></tr></thead><tbody><tr><td><code>Service.CustomField(name)</code> / <code>(id)</code></td><td>Any custom field on the case, not just ones on the same tab.</td></tr><tr><td><code>Service.Case</code></td><td>The case itself — reference, status, fee earner, client, case type.</td></tr><tr><td><code>Service.DateDiff(from, to)</code></td><td>Days between two dates.</td></tr><tr><td><code>Service.GetCurrentDate()</code></td><td>Today, on the server.</td></tr><tr><td><code>Convert</code></td><td>.NET conversion helpers, such as <code>Convert.ToInt32</code>.</td></tr></tbody></table>

Because it reads through `Service` rather than off a page, a calculated field sees the whole case at once. There is no cross-tab problem to solve and nothing to preload.

## Where the result appears

This is the reason to choose a calculated field over a [JavaScript label](/developer-documentation/custom-form-scripting.md):

* On the case tab, where it renders read-only.
* In document, email and SMS templates, wherever its merge field appears.
* In reports.
* In the [Case Data API](/developer-documentation/case-data-api.md), and so in JavaScript labels that preload it.

A label's total exists only while somebody is looking at the screen. A calculated field's total can be put in a letter.

The trade is that it does not react while a user types. It is worked out when the tab is rendered, so a change to one of its inputs shows in the result once the form has been saved and reloaded. Where a running total has to update as the user works, that is a [JavaScript label](/developer-documentation/custom-form-scripting.md) — and there is nothing stopping a tab having both, one for the live figure and one for the merge field.

## Returning a value

`calc()` has to return something the server can turn into text. Returning nothing, or an object, produces an error.

{% hint style="warning" %}
An error in `calc()`, or a return value the server cannot render, shows in the field as **Calculated Field Error** followed by the message. Unlike a JavaScript label, whose failures go quietly to the browser console, this is visible to users — and it appears inside a generated document if the merge field is in one.

The most common cause is a branch with no `return`. Make sure every path out of `calc()` returns something, including the "nothing applies" case.
{% endhint %}

Wrapping the answer in `String(...)` is the usual habit. `toFixed(2)` does the same job for money and fixes the decimal places at the same time, so `56` is quoted as `56.00`.

## Reading values

`Service.CustomField(id).ValueAsText` is a **property**, with no brackets. The browser-side helper's `CustomField(id).ValueAsText()` is a function. Logic moved between a label and a calculated field usually needs adjusting for exactly this.

{% hint style="warning" %}
`ValueAsNumber` reads whole numbers only, so a field holding `12.5` comes back as `0`. For anything that can carry a decimal point, read `ValueAsText` and convert with `parseFloat`, stripping the currency symbol and any thousands separator first.

Use `replace(/,/g, "")` rather than `replace(",", "")` — the second removes only the first separator, so a seven-figure amount keeps its second one and reads short.
{% endhint %}

## Comments

Newline characters are stripped from the script before it runs, so a `//` comment can take the rest of the script with it. Use `/* ... */` in a calculated field.

## AI, and why not here

`Service.AIAction(...)` is on the same `Service` object, so it is technically within reach. A calculated field is the wrong place for it: the script re-runs every time the value is needed — each tab load, each template merge, each API read — so an AI call there is repeated on all of them, with the latency and cost each one carries, and no two answers guaranteed to match.

Run the AI once in an [on save script](/developer-documentation/case-scripting/on-save-scripts/examples/running-an-ai-action-on-a-document.md), store the result with `Service.UpdateCustomField`, and read that stored field wherever it is needed.

## Examples

<table data-view="cards"><thead><tr><th>Example</th><th>What it shows</th><th data-card-target data-type="content-ref">Target</th></tr></thead><tbody><tr><td><strong>A Simple Calculated Multiplier</strong></td><td>The smallest useful calculated field — read one value, return a formatted number.</td><td><a href="/developer-documentation/case-scripting/calculated-fields/a-simple-calculated-multiplier.md">A Simple Calculated Multiplier</a></td></tr><tr><td><strong>Banding a Calculated Amount</strong></td><td>Tiered arithmetic, and stripping currency before converting.</td><td><a href="/developer-documentation/case-scripting/calculated-fields/banding-a-calculated-amount.md">Banding a Calculated Amount</a></td></tr><tr><td><strong>Building a Multi-Part Total</strong></td><td>Several inputs, a tier, VAT and fixed charges composed into one total.</td><td><a href="/developer-documentation/case-scripting/calculated-fields/building-a-multi-part-total.md">Building a Multi-Part Total</a></td></tr></tbody></table>
