> 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/on-save-scripts/examples/reacting-to-old-and-new-values.md).

# Reacting to Old and New Values

A field that has just changed carries both of its values. `Changed.CustomField(id)` gives you `OldValueAsText` for what was there before and `NewValueAsText` for what is being saved, and reading both lets a script respond to the change itself rather than to the resulting value.

This matters because the same end value can mean different things. A consent field reading No means one thing if it was Yes yesterday and something quite different if it had never been answered. Only the transition tells you which happened.

## Acting only when a value was entered

The simplest use is to check that the incoming value is not empty before doing anything, which also means the script does nothing when a user clears the field.

A club records the annual subscription payment, and the membership status should follow it.

| Setting       | Value                               |
| ------------- | ----------------------------------- |
| Trigger field | `5401` Subscription received (date) |
| Updated field | `5402` Membership status            |

```javascript
[Changed.CustomField(5401).Changed]

function on_save() {
    if (Changed.CustomField("5401").NewValueAsText !== "") {
        Service.UpdateCustomField("5402", "Current");
    }

    return true;
}
```

There is no `after_save` here, and nothing is sent out, so doing the work in `on_save` is fine. Note that clearing the subscription date leaves the status alone rather than resetting it. If you want it reset, handle the empty case explicitly instead of falling through.

## Clearing related fields when a flag is withdrawn

When a Yes/No flag is turned off, the fields that only made sense while it was on usually need clearing. Checking both values keeps this to the one transition you care about.

A charity withdraws a Gift Aid declaration, so the declaration date and the claimed amount should no longer be held.

| Setting        | Value                                                    |
| -------------- | -------------------------------------------------------- |
| Trigger field  | `5102` Gift Aid declaration (Yes/No)                     |
| Cleared fields | `5103` Gift Aid declaration date, `5104` Gift Aid amount |

```javascript
[Changed.CustomField(5102).Changed]

function on_save() {
    if (Changed.CustomField("5102").OldValueAsText == "Yes"
        && Changed.CustomField("5102").NewValueAsText == "No") {
        Service.UpdateCustomField("5103", "");
        Service.UpdateCustomField("5104", "0");
    }

    return true;
}
```

Passing an empty string to `UpdateCustomField` clears the field. The amount is set to `"0"` rather than emptied because a numeric field reads better as zero than as blank, but either works.

## Branching on the transition

Once you are reading both values you can treat each transition as its own case. Here every branch tells the data protection officer something different, so the wording is right without the recipient having to work out what changed.

| Setting       | Value                                                                                 |
| ------------- | ------------------------------------------------------------------------------------- |
| Trigger field | `5601` Marketing consent (`Yes` / `No` / `Please Select`)                             |
| Templates     | `MarketingConsent-Withdrawn`, `MarketingConsent-Declined`, `MarketingConsent-Granted` |

```javascript
[Changed.CustomField(5601).Changed]

function after_save() {
    var oldValue = Changed.CustomField("5601").OldValueAsText;
    var newValue = Changed.CustomField("5601").NewValueAsText;

    if (newValue == "No" && oldValue == "Yes") {
        Service.SendEmail("MarketingConsent-Withdrawn");
        Service.AddMessage("The data protection officer has been told consent was withdrawn.", "notify-success");
    } else if (newValue == "No" && oldValue == "Please Select") {
        Service.SendEmail("MarketingConsent-Declined");
        Service.AddMessage("The data protection officer has been told consent was declined.", "notify-success");
    } else if (newValue == "Yes" && oldValue == "No") {
        Service.SendEmail("MarketingConsent-Granted");
        Service.AddMessage("The data protection officer has been told consent was granted.", "notify-success");
    }

    return true;
}
```

Reading each value into a variable once keeps the conditions short enough to compare at a glance, which is worth doing as soon as you have more than a couple of branches.

The emails go out from `after_save` so that the templates render against the consent value that was actually stored. Had they been sent from `on_save`, a template quoting the consent field would have shown the previous answer.

{% hint style="info" %}
A dropdown's unanswered state is a real value, usually the literal text `Please Select`, and not an empty string. Check what your own field uses before matching on it, because the transition from unanswered to No is often the one that needs different handling.
{% endhint %}

## What to watch for

An unmatched transition falls through to `return true`, which lets the save proceed and sends nothing. That is almost always the behaviour you want: a script that only handles the cases it recognises is far easier to live with than one that treats anything unexpected as an error and blocks the save.

Be careful when the script writes to the field that triggers it, since the write can fire the script again. Guard against that by checking the current value before writing, as covered in [On Save Scripts](/developer-documentation/case-scripting/on-save-scripts.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.agilecase.com/developer-documentation/case-scripting/on-save-scripts/examples/reacting-to-old-and-new-values.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
