> For the complete documentation index, see [llms.txt](https://postica.gitbook.io/binding-system-3/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://postica.gitbook.io/binding-system-3/pipeline/modifiers.md).

# Modifiers

Changing the value on the way, without changing either end.

A modifier changes the **value**, not its type. Clamp it, remap it, smooth it, format it, log it, fan it out to other bindings, or watch it cross a threshold.

{% hint style="success" %}
**Learn by doing:** [A Health Bar with No Driver Class](/binding-system-3/tutorials/health-bar.md) adds a Normalize Value modifier and binds one of its own parameters.
{% endhint %}

This is the stage where most of the work happens in practice, because it is where the mismatch between two components usually lives. A slider gives 0 to 1 and a spinner wants degrees per second: that is not the slider's problem, and it is not the spinner's problem.

{% hint style="info" %}
**Modifier or converter?** Same type in, same type out is a modifier. Different type out is a [converter](/binding-system-3/pipeline/converters.md). Meters to feet, celsius to fahrenheit, a percentage to a ratio: all `float` to `float`, all modifiers. A converter's two type arguments must differ, so it cannot express any of those.

The rule is strict in one direction only: a modifier may return a type **derived from** its input type, but never an unrelated one.
{% endhint %}

## Adding one

Open the bind menu and pick from **Add Modifier**. The catalogue is filtered to modifiers that fit the type and the mode, so anything listed will work.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2Fw0BFotsE0JcL8RX3gtQr%2FScreenshot%202026-09-28%20at%2014.10.10.png?alt=media&amp;token=4e21cc57-16a4-417b-9f65-e6460c67d343" alt="" width="563"><figcaption><p>The Add Modifier group</p></figcaption></figure>

When a converter is present, a second group appears: **Add POST-Conversion Modifier**. See [which side of the converter](/binding-system-3/pipeline.md#which-side-of-the-converter).

Modifiers appear stacked under the bind row. Each row has:

<table><thead><tr><th width="230">Control</th><th>What it does</th></tr></thead><tbody><tr><td>Title and summary</td><td>The modifier's name plus a one-line summary of its current settings, so a collapsed stack is still readable.</td></tr><tr><td>Mode button</td><td>Whether this modifier runs on read, on write, or both.</td></tr><tr><td>▲ ▼</td><td>Reorder. Order matters: clamp then remap is not remap then clamp.</td></tr><tr><td>‒</td><td>Remove.</td></tr><tr><td>Foldout</td><td>The modifier's own fields.</td></tr></tbody></table>

A modifier that is incompatible with the current bind mode is marked as such rather than silently ignored.

## Parameters are bindings

Almost every modifier parameter is itself a bind field, which is where a lot of the system's reach comes from.

```
Health.current  →  Clamp Value [min: 0, max: ← Difficulty.maxHealth]  →  Bar.fill
```

The clamp's upper bound is not a number typed into the modifier, it is a binding to another value. Change the difficulty and every clamp that reads it follows.

## Read, write, or both

<table><thead><tr><th width="230">Interface</th><th>When it runs</th></tr></thead><tbody><tr><td><code>IReadModifier&#x3C;T></code></td><td>Runs when the value is read from the source.</td></tr><tr><td><code>IWriteModifier&#x3C;T></code></td><td>Runs when the value is written to the source.</td></tr><tr><td><code>IReadWriteModifier&#x3C;T></code></td><td>Both, usually as a transform and its inverse.</td></tr></tbody></table>

`ModifyMode` on the instance decides which passes actually engage, and it is the mode button on the row. A **Clamp** that only clamps on write is a validation rule; the same clamp on read is a display rule.

## Special kinds

<table><thead><tr><th width="290">Kind</th><th>What it is</th></tr></thead><tbody><tr><td><code>ISmartModifier</code></td><td>Knows the binding that owns it, and can push a value <em>up</em> the chain. This is how <strong>Set Value</strong> and <strong>Link To Bindable</strong> work.</td></tr><tr><td><code>IObjectModifier&#x3C;T></code></td><td>Applies to a type and everything derived from it, rather than one exact type.</td></tr><tr><td><code>INullPropagatingModifier</code></td><td>Handles a null input itself rather than being skipped. <strong>Null Check</strong> is one.</td></tr><tr><td><code>IRequiresAutoUpdate</code></td><td>Tells the binding it must update automatically. Anything time based, such as <strong>Value Delayer</strong> or the <a href="/binding-system-3/pipeline/tweening.md">tweening</a> modifiers, needs this: there is no point easing a value that is only read once.</td></tr></tbody></table>

{% hint style="info" %}
That last one is why adding a **Tween Animation** modifier can turn on automatic updating for the binding. It is not a side effect, it is the modifier saying that it cannot do its job otherwise.
{% endhint %}

## Multi-controlled values

One field, several controls, no arbitration code. **Link To Bindable** and **Set Value** let several sources write into the same value through the modifier chain, which is the pattern behind "change max speed from a UI slider, an input field and a joystick".

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FK4PKnq7tTJBo8V3p6X9M%2FScreenshot%202026-09-28%20at%2014.19.28.png?alt=media&amp;token=2bfd9ca9-0c63-4bbb-8cd9-711c75469a51" alt="" width="563"><figcaption><p>One value driven from three places</p></figcaption></figure>

## Watching a value go past

Four families watch the value without changing it, so a threshold or a match is something the binding itself can notice:

<table><thead><tr><th width="290">Modifier</th><th>What it does</th></tr></thead><tbody><tr><td><strong>Value Events</strong></td><td>Invokes a <code>UnityEvent</code> when the value matches, passing the value along untouched.</td></tr><tr><td><strong>Value Conditions</strong></td><td>Tells boolean bindings and events whether the value matches, passing the value along untouched.</td></tr><tr><td><strong>Numeric</strong> variants</td><td>The same, with numeric comparisons: greater than, in range, and so on.</td></tr><tr><td><strong>String</strong> variants</td><td>The same, with pattern matching on text.</td></tr></tbody></table>

"Passing the value along untouched" is the important part: these are observers in the chain, not transformations, so you can drop one in without changing what the binding does.

## Writing one

```csharp
using Postica.BindingSystem;
using Postica.BindingSystem.Modifiers;

[Serializable]
[TypeDescription("Multiplies the value by a bindable factor.")]
public class ScaleModifier : BaseModifier<float>
{
    public ReadOnlyBind<float> factor = 1f.Bind();

    public override string Id => "Scale";
    public override string ShortDataDescription => $"× {factor.ToString("factor")}";

    protected override float Modify(float value) => value * factor;
    protected override float InverseModify(float output) => output / factor;
}
```

`BaseModifier<T>` gives you the read/write plumbing. `Modify` is the read pass, `InverseModify` the write pass when mode is `ReadWrite`, so a read happens first, otherwise it uses `Modify` just like the read pass, `Id` the name in the menu, and `ShortDataDescription` the one-line summary shown on a collapsed row.

See [Custom Modifiers](/binding-system-3/reference/extending/modifiers.md) for the whole story, including the untyped form, object modifiers, and `[ModifierOptions]`.

## Related pages

* [Modifier Catalogue](/binding-system-3/pipeline/modifier-catalogue.md): every built-in modifier with its description.
* [Tweening and Control](/binding-system-3/pipeline/tweening.md): easing and damping.
* [Templates](/binding-system-3/pipeline/templates.md): saving a configured modifier.
* [Standard Modifiers for Your Types](/binding-system-3/pipeline/standard-modifiers.md): getting the generic ones for your own types.
