> For the complete documentation index, see [llms.txt](https://help.tillit.cloud/tillit/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.tillit.cloud/tillit/knowledge-base/setting-up-tillit/activity-templates/activity/scripts.md).

# Scripts

Activity Templates support two kinds of custom script, used to pull in data that isn't already on the form and calculate values from it.

* **OnLoad Script** — set once per template, in the template's *Scripts* panel. Runs automatically when a form based on the template opens, and fetches data before the operator can start filling anything in.
* **Calculation Script** — set per item, in that item's *Calculation* property. Computes the value displayed on that one field.

The two work together: an OnLoad Script fetches data and stores it, and a Calculation Script reads it back out to display on a field. Getting this pairing right depends on a few rules that aren't obvious from the editor alone.

{% hint style="info" %}
For a beginner-friendly walkthrough of the Calculation property using the built-in tool (no scripting knowledge required), see [Evaluate Data types & Dynamic Note & Calculations](/tillit/knowledge-base/setting-up-tillit/activity-templates/activity/elements/data-types/calculation-and-read-only-note.md). This page covers the JavaScript-based advanced use of both script types.
{% endhint %}

### OnLoad Script

Open the template's *Scripts* panel to set the OnLoad Script. A common use case is fetching data from elsewhere in TilliT and storing it on `$scope`, so a Calculation Script elsewhere on the form can display it:

```javascript
const result = await $tillitApi.get('core/some-endpoint', {someParam: $form.order.id});
$scope.myData = result;
```

**Do not** wrap this in your own async function:

```javascript
// Don't do this
(async () => {
  const result = await $tillitApi.get('core/some-endpoint', {someParam: $form.order.id});
  $scope.myData = result;
})()
```

The platform already runs your OnLoad Script inside its own `async function($form, $scope, $formItems, $tillitApi) { ... }`, and waits for that function to finish before letting the form open. Wrapping your code in a second async function starts a separate promise that the platform doesn't wait for — the outer function returns immediately, handing back an empty `$scope`, before your fetch has resolved. A Calculation Script reading `$scope` at that point sees nothing, and once TilliT freezes `$scope` for that render, your fetch has nowhere left to write its result. If this happens, the browser console shows:

```
Uncaught (in promise) TypeError: Cannot add property myData, object is not extensible
```

Write flat statements instead — the platform's wrapper is already there, so you don't need one of your own.

{% hint style="warning" %}
`$form` is always frozen and cannot be written to, by design — this stops one script accidentally mutating shared form state. Read from it (`$form.order.orderNumber`, `$form.asset.id`), but store anything you fetch on `$scope` instead.
{% endhint %}

### Calculation Script

Set on an item's *Calculation* property (Item Properties → Calculation), this computes just that item's displayed value. Unlike the OnLoad Script, it runs once when the item first renders and again every time any field on the form changes.

To display data fetched by an OnLoad Script:

```javascript
(() => {
  return $scope.myData ? String($scope.myData.length) : 'Empty';
})()
```

**Do** wrap this one in a self-invoking function — the opposite rule to the OnLoad Script above, and easy to get backwards. The platform inserts your Calculation text directly after a `return`, so a single expression works unwrapped, but anything needing more than one statement (a `const`, an `if`) needs the wrapper to work at all.

A Calculation Script can read `$scope`, but not write to it — it's frozen for the same reason `$form` is. It also doesn't have access to `$tillitApi` or `$formItems`; any fetching has to happen in the OnLoad Script first.

If a Calculation Script's result doesn't match the item's Data Type (e.g. it returns a string on an Evaluate Number field), TilliT shows a soft, recoverable error rather than crashing the form: *"Invalid return type from string/number/boolean/datetime evaluation."* Seeing this means the script ran fine, but returned the wrong shape of value for the field.

### Testing and troubleshooting

The editor's **Preview** button runs your script against a stripped-down context that may not include a real order, asset, or another script's result — so a script can fail in Preview and work correctly on a live form, or vice versa. Test against a real activity form: save the script, open a live activity, and check the browser console and Network tab for what actually ran and what came back.

If a saved change doesn't seem to take effect, reopen the field to confirm it saved — the editor can occasionally appear to save without persisting the change. Pulling the live form's JSON payload from the Network tab (the `scripts.onLoad` or item `calculation` fields) shows exactly what's running.
