> 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/custom-form-scripting.md).

# Custom Form Scripting

Scripts that run in the browser inside a custom field tab, through a JavaScript label field.

Most custom fields hold something a user types. A **JavaScript label** holds a script instead. It runs in the browser, on the tab it lives on, every time anything on that tab changes — which makes it the tool for anything that has to keep up with what the user is doing: showing and hiding fields, validating as they go, colouring a dropdown, blocking the save button, building a link out of what has been entered.

It is the narrowest of the scripting options, and deliberately so. The script sees the tab it is on, and what it produces is displayed rather than stored.

{% hint style="info" %}
Creating the field itself is covered in [JavaScript Label](/administrator-documentation/types-of-custom-field/javascript-label.md) in the administrator documentation. This section covers writing the script that goes in it.
{% endhint %}

## When it is not the right tool

A label's output lives on the page and nowhere else. Nothing it works out is written to the database, so none of it reaches a report, a merge field or the API — and it is gone when the user navigates away.

Two other features cover what a label cannot:

<table data-view="cards"><thead><tr><th>Instead</th><th>When</th><th data-card-target data-type="content-ref">Target</th></tr></thead><tbody><tr><td><strong>Calculated Fields</strong></td><td>The derived value has to exist outside the screen — in a letter, a report or the API. Runs on the server, sees the whole case, stores nothing.</td><td><a href="/developer-documentation/case-scripting/calculated-fields.md">Calculated Fields</a></td></tr><tr><td><strong>On Save Scripts</strong></td><td>The value has to be written and kept, or something has to happen — an email, a task, a webhook — or a save has to be refused.</td><td><a href="/developer-documentation/case-scripting/on-save-scripts.md">On Save Scripts</a></td></tr></tbody></table>

A label can put a value into another field's input box, and that value is stored when the user saves the form. But it is the user's save that stores it, not the script, so a value that must exist whether or not anybody presses Save belongs in an on save script.

The three work well together: a label for the live figure on screen, a calculated field for the same figure in the letter, an on save script for anything that has to be recorded.

## Writing a label

### The shape of a script

There is no function to define and no trigger line. Write bare statements, and AgileCase wraps them for you:

```javascript
var dob = CustomField(6040).Value();

ThisJSLabel().Input().html("Date of birth: " + dob);
```

Two things are set up before your code runs:

* `ScriptID` holds the numeric ID of the label field running the script.
* The whole script is wrapped in a `try`/`catch`.

Everything is then called once when the tab is rendered, and again on every `change` event from any input, select or textarea on the same form. There is no need to bind your own handlers, and no need for a ready wrapper. Write the script as though it works the answer out from scratch each time, because it will be asked to.

### Where the output goes

The label renders as an empty span with a predictable ID:

```html
<span id="CustomField_6039">&nbsp;</span>
```

That is where display text belongs. `ThisJSLabel()` is the same field wrapped in the helper, so these three lines are equivalent:

```javascript
$("#CustomField_" + ScriptID).html(text);
ThisJSLabel().Input().html(text);
document.getElementById("CustomField_6039").innerHTML = text;
```

Every other custom field on the tab follows the same pattern, so `#CustomField_6040` is the input for field 6040. Reading them through `CustomField(6040).Value()` is preferred over the raw selector, because the helper knows how each field type stores its value and falls back to preloaded data when the field turns out to be on another tab.

### Config labels

Plenty of JavaScript labels display nothing at all. They exist to drive the rest of the form, and their first line hides their own row:

```javascript
ThisJSLabel().HideRow();
```

This is the standard shape for a label whose whole job is to run a [ruleset](/developer-documentation/custom-form-scripting/rule-reference.md). Name it something like "Consent rules config", put it wherever is convenient in the group, and the users of the tab never know it is there.

### What the script can use

<table><thead><tr><th width="220">Available</th><th>Notes</th></tr></thead><tbody><tr><td>The scripting helper</td><td><code>CustomField</code>, <code>ThisJSLabel</code>, <code>ExecuteAllRules</code> and the rest, loaded on every page. See the <a href="/developer-documentation/custom-form-scripting/scripting-helper-api-reference.md">Scripting Helper API Reference</a>.</td></tr><tr><td>jQuery</td><td><code>$</code> is available, and your script already runs at the right moment.</td></tr><tr><td>moment.js</td><td>Used by the helper's date tests, and available to your own code.</td></tr><tr><td>Selectize</td><td>Loaded on every page, for turning a long dropdown into a type-to-search one. See <a href="#making-a-long-dropdown-searchable">below</a>.</td></tr><tr><td><code>ScriptID</code></td><td>The numeric ID of the label field running the script.</td></tr><tr><td><code>caseid</code></td><td>The numeric case ID. Present on a case page, which is what makes the <a href="/developer-documentation/case-data-api.md">Case Data API</a> reachable. It is not present on contact tabs.</td></tr><tr><td>Plain DOM and JavaScript</td><td><code>document.getElementById</code>, <code>window.location</code> and anything else the browser offers.</td></tr></tbody></table>

### Reading other tabs

A JavaScript label only sees the tab it is on. To read anything else it calls the [Case Data API](/developer-documentation/case-data-api.md), through one of two helpers:

```javascript
preloadCaseData = preLoadServerData('Custom.6311;Case.CaseReference;Contact.Client.Email');

var policyNumber = getCrossTabValueById(6313);
```

Both are **synchronous**: they block the browser until the server answers, on every run of the script, which means on every keystroke that fires a change. Use them for values that genuinely live elsewhere, ask for everything you need in one `preLoadServerData` call rather than several, and read fields on the current tab directly.

{% hint style="info" %}
The global name is part of the arrangement. Assign the preload to `preloadCaseData` **without `var`**, so it sits in global scope: when `CustomField(id).Value()` is asked for a field that is not on the tab, that is where it looks, and it will read the value out for you. `var preloadCaseData = ...` keeps it inside your own script instead.
{% endhint %}

### Doing the work once

Because a label re-runs on every change, anything expensive in it happens again on every change. Two guards keep that in hand, and both are common in existing scripts.

**Check whether you have already written the answer.** A label's span starts out holding `&nbsp;`, so its own output tells you whether this is the first run:

```javascript
if (ThisJSLabel().Input()[0].innerHTML == "&nbsp;") {
    preloadCaseData = preLoadServerData('Contact.Borrower1.Name;Contact.Borrower2.Name');
    // ... build and write the output
}
```

That suits a label showing something that does not change while the user is on the tab — a name from a contact record, a reference, a link.

**Cache on `window` when several labels need the same data.** A value parked on `window` outlives each run, so the request happens once per page rather than once per label per change:

```javascript
if (window.expensesData === undefined) {
    window.expensesData = preLoadServerData('CaseTable.1305');
}

var rows = $(window.expensesData.data.customTables).filter(getRowByName('Expenses'))[0].rows;
```

Pick a name specific enough not to collide with another label on the same page. And remember the cache lasts as long as the page: a value the user is editing on this tab should be read fresh, not cached.

### Calling the Case Data API directly

`preLoadServerData` is a thin wrapper over an AJAX call, and there is nothing stopping you making that call yourself when you want control over it:

```javascript
var uri = '../../api/case/' + caseid + '/data?getValues=CaseTable.1305;CaseTable.1302';

$.ajax({ url: uri, async: false, success: function (result) {
    if (result.status === 'success') {
        window.expensesData = result;
    }
}});
```

The response is the same envelope described in [Response Format](/developer-documentation/case-data-api/response-format.md), and the helpers that pick values out of it — `getRowByName`, `getById`, `simplifyRowContent` — work on it just the same.

Doing it by hand is worth it when you want to keep the raw result, hold it on `window`, or handle a failure rather than get an empty string back. Note that the URL is relative, so it has to suit the page: `preLoadServerData` picks between the case page and [Client Connect](/administrator-documentation/setting-up-client-connect.md) forms for you, which is a good reason to use it where you can.

### Calling another service

A label is ordinary browser JavaScript, so it can call an external API — address lookup, phone validation, a pricing service — and put the answer on the form.

{% hint style="warning" %}
A label's script is delivered to the browser as part of the page, so **anything in it is visible to anyone who can open the tab**, including an API key. Treat a key written into a label as published: use one restricted to the calls you need, and never one that can spend money or read data beyond that.

Where a key has to stay private, make the call from an [on save script](/developer-documentation/case-scripting/on-save-scripts/examples/calling-a-webhook-on-a-milestone.md) or a [webhook](/developer-documentation/webhooks.md) instead. Those run on the server, where the key is not sent to anybody.
{% endhint %}

Keep in mind too that the call repeats on every change unless you guard it, and that a third-party service being slow makes the tab feel slow, since a synchronous call blocks the browser.

### Table controls

A [table control group](/administrator-documentation/case-type-settings/using-table-controls-in-custom-field-groups.md) shows its rows in a grid, and opens one row at a time in an edit form. A JavaScript label in such a group is rendered in **both**, and the two are not the same environment.

<table><thead><tr><th width="180"></th><th width="290">The row edit form</th><th>The grid</th></tr></thead><tbody><tr><td>When it runs</td><td>When a row is opened, then on every change within the form</td><td>Once per row, when the grid is drawn</td></tr><tr><td>Element IDs</td><td><code>CustomField_6422</code>, with no row suffix</td><td><code>CustomField_6422_17</code>, ending in the row ID</td></tr><tr><td>What those are</td><td>The row's inputs</td><td>Table cells holding rendered text</td></tr><tr><td>The helper</td><td>Fully available</td><td>Plain DOM and jQuery; <code>CustomField()</code> and <code>ScriptID</code> belong to the edit form</td></tr><tr><td>Values</td><td>Read and written</td><td>Read, as rendered text</td></tr></tbody></table>

Nearly every table script is written for the edit form, where one row is on screen at a time and the helper behaves exactly as it does on an ordinary tab. Nothing there needs a row ID:

```javascript
var paymentType = CustomField(6422).Value();
```

The grid is the exception, and the token `{{n}}` belongs to it alone. Each copy of the script emitted into the grid has `{{n}}` replaced with that row's ID, which is what lets it address its own cells:

```javascript
var value = document.getElementById('CustomField_1164_{{n}}').innerHTML;
```

A grid script that leaves out `{{n}}` still runs once per row, but every copy then addresses row one's cells, so every row displays row one's answer.

{% hint style="info" %}
`{{n}}` is substituted in the grid and nowhere else, so it belongs only in a script written for the grid. The row edit form holds one row at a time and addresses its fields by ID alone.
{% endhint %}

`HideInTable(column, groupId)` hides a whole column of the grid, which is how config and audit columns are kept out of the user's way. Being about the grid rather than one row, it needs no row ID either.

The grid is an ordinary table, so it can be restyled the same way. Colouring a status column by its value is a common finishing touch:

```javascript
$('td:nth-child(8):contains("Withdrawn")', '#CustomFieldGroupTable' + groupId)
    .css({ 'color': 'red', 'font-weight': 'bold' });
```

The grid's ID is `CustomFieldGroupTable` followed by the group ID, and columns are counted from 1 — the same numbering `HideInTable` uses.

### Making a long dropdown searchable

A dropdown with two hundred options is quicker to type into than to scroll. Selectize is loaded on every page, so a label can upgrade one in a single line:

```javascript
CustomField(19781).Input().selectize();
```

The field keeps working exactly as before — same stored value, same behaviour from every rule that reads it — and the user gets a box they can type into to filter the list.

Call it once per field. The label re-runs on every change to the form, and Selectize leaves the original `<select>` in place beneath its own control, so a second call finds the field already converted and does nothing further.

### Knowing where the script is running

The same tab can be reached from two places: the case page, and a [Client Connect](/administrator-documentation/setting-up-client-connect.md) form, where the host name begins `connect.`. A label can tell them apart from the URL, which is how one config label serves an internal view and a client-facing one:

```javascript
var userType = window.location.href.indexOf("connect.") !== -1 ? "Ext" : "Int";

if (userType === "Ext") {
    ExecuteAllRules(hideInternalOnly);
}
```

This is the usual way to keep internal columns, notes and approval fields off the version a client sees, while leaving the same group in place for staff.

Two of the helpers behave differently across that boundary. `preLoadServerData` adjusts its URL for Client Connect and works in both; `getCrossTabValueById` builds its URL relative to the case page. `Case().CaseType()` and `Case().CaseReference()` read the case header, which is on the case page only.

### File upload buttons

A [file upload field](/administrator-documentation/types-of-custom-field/file-upload-control.md) renders its button with an ID of `uploadFilebtn` followed by the field ID, so a script can take the button away once a document is attached:

```javascript
if (CustomField(6587).Value() !== "") {
    $("#uploadFilebtn6587").css('display', 'none');
}
```

The uploaded file stays visible and downloadable; only the control for replacing it goes.

### Which JavaScript you can use

A label runs in the user's browser, so it can use whatever that browser supports — template literals, arrow functions and the rest are all fine, and appear in existing scripts.

A [calculated field](/developer-documentation/case-scripting/calculated-fields.md) is the other case: it runs on the server in V8, so browser APIs are not there, though the language itself is current.

### Errors

The wrapper catches everything and writes the message to the browser console. A broken script therefore looks like a field that stays empty, and nothing on screen says why.

{% hint style="warning" %}
If a JavaScript label does nothing, open the browser console before assuming the field is misconfigured. That is the only place its errors appear.
{% endhint %}

Because the script runs again on every change, keep it cheap and keep it repeatable. Anything that appends to the page — a message, a link, a row — has to remove its previous output first, or it stacks up as the user types.

### AI results

AgileCase can run an [AI template](/administrator-documentation/templates/ai-templates.md) over a case or an uploaded document, but not from here — the scripting helper is a browser library, and nothing in it calls a model.

The work is done by an [on save script](/developer-documentation/case-scripting/on-save-scripts/examples/running-an-ai-action-on-a-document.md), which runs the template once and stores the result in ordinary custom fields. A label then treats those like any other field: showing them when they hold something, hiding them when they do not, formatting the result for reading. From the form's point of view an AI result is a text field somebody else filled in.

## Reference and examples

<table data-view="cards"><thead><tr><th>Page</th><th>What it covers</th><th data-card-target data-type="content-ref">Target</th></tr></thead><tbody><tr><td><strong>Scripting Helper API Reference</strong></td><td>Every global a JavaScript label can call, with its arguments and its quirks.</td><td><a href="/developer-documentation/custom-form-scripting/scripting-helper-api-reference.md">Scripting Helper API Reference</a></td></tr><tr><td><strong>Rule Reference</strong></td><td>The declarative rulesets behind <code>ExecuteAllRules</code>: actions, tests and evaluation order.</td><td><a href="/developer-documentation/custom-form-scripting/rule-reference.md">Rule Reference</a></td></tr><tr><td><strong>Examples</strong></td><td>Twenty-nine worked scripts, indexed by what each one demonstrates.</td><td><a href="/developer-documentation/custom-form-scripting/examples.md">Examples</a></td></tr></tbody></table>
