> 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/examples/blocking-the-save-button.md).

# Blocking the Save Button

Disabling the Save button with a disable\_save rule, and explaining why.

A message tells a user something is wrong. A disabled Save button makes sure they deal with it. The `disable_save` action does both, and manages the recovery so the button comes back when the problem is fixed.

## The scenario

A club's membership record is meaningless without a membership type, and records saved without one have to be chased afterwards. Better to require it up front.

| Setting      | Value                               |
| ------------ | ----------------------------------- |
| Config label | `6425` Membership save gate, hidden |
| Trigger      | `6426` Membership type (dropdown)   |

## The script

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

var blockSaveUntilTypeChosen = [{
    "action": "disable_save",
    "trigger_id": 6426,
    "target_ids": [6426],
    "test_type": "IN_LIST",
    "test_values": ["", "Please Select"],
    "messages": ["Choose a membership type before saving this record."],
    "description": "Membership type is required"
}];

ExecuteAllRules(blockSaveUntilTypeChosen);
```

## target\_ids is not optional

{% hint style="info" %}
`disable_save` acts on the form's Save button rather than on fields, and it still takes a `target_ids` array: the entries are what decide how many times the rule runs.

Give it exactly one ID, conventionally the trigger field's own. That runs the rule once and shows its message once.
{% endhint %}

## Catching both empty states

An unanswered dropdown is either genuinely empty or still showing its "Please Select" prompt, depending on how it is configured. `IN_LIST` covers both in one rule, which is more robust than `EQUALS ""` and less trouble than two rules.

## How the button is managed

`ExecuteAllRules` treats the Save button as a property of the ruleset as a whole rather than of any one rule. On every run it:

1. Clears every message beside the button.
2. Disables the button if any `disable_save` rule in the array passed, and writes out each of their messages followed by a `Validation:` line.
3. Re-enables it if none passed.

Two things follow from that. The button recovers by itself, with no second rule to undo the block — worth having, since the equivalent in your own jQuery needs both a `prop('disabled', true)` and the matching call to bring it back. And several `disable_save` rules sit together happily: each contributes its message, and the button stays disabled while any of them holds.

{% hint style="info" %}
Because the button is settled across the whole ruleset, `disable_save` belongs in an `ExecuteAllRules` call — `ExecuteSingleRule` keeps a tally of its own that nothing else reads.
{% endhint %}

## Which button, and which form

The rule addresses the submit button of the form the config label belongs to. On an ordinary tab that is the tab's Save. Inside a table's row edit form it is the row's Save — see [Gating a Table Row](/developer-documentation/custom-form-scripting/examples/gating-a-table-row.md).

## It is a prompt, not a guarantee

The block exists in one browser, on one form. It stops somebody pressing Save without answering the question. It does nothing about data arriving through an upload, an integration or the API, and nothing about anybody who opens the browser's developer tools.

{% hint style="info" %}
Where the rule genuinely has to hold, put it in an [on save script](/developer-documentation/case-scripting/on-save-scripts/examples/blocking-a-save-with-validation.md) as well. That runs on the server, sees every save whatever caused it, and can refuse.

The two are complementary rather than redundant: the label stops the mistake being made, and the on save script stops it being stored.
{% endhint %}
