> 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/reference/extending/modifiers.md).

# Custom Modifiers

Adding a value transformation of your own.

Modifiers change the values before they are read from or written to the source. Like [converters](/binding-system-3/reference/extending/converters.md) they are small to write, and unlike converters they are the extension point you will actually reach for, because most of what a connection needs is a change of value rather than a change of type.

{% hint style="success" %}
**Learn by doing:** [Writing Your First Modifier](/binding-system-3/tutorials/first-modifier.md) builds a Gamma modifier end to end, including the row summary and the inverse.
{% endhint %}

{% hint style="danger" %}
**A modifier must not change the output type.** Input and output are the same type, or at most the output is of a type derived from the input. Crossing a type boundary is a [converter](/binding-system-3/reference/extending/converters.md), and `IConverter<T, T>` is not one, so the two extension points do not overlap.
{% endhint %}

## The minimum

`BaseModifier<T>` gives you the read/write plumbing, so a modifier is one method:

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

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

    public override string Id => "Scale";

    // The one-line summary shown on a collapsed row.
    public override string ShortDataDescription => $"× {factor.ToString("factor")}";

    protected override float Modify(float value) => value * factor;

    // Only needed if the modifier can be written through.
    protected override float InverseModify(float output) => output / factor;
}
```

<table><thead><tr><th width="290">Member</th><th>What it is</th></tr></thead><tbody><tr><td><code>Id</code></td><td>The name in the menu, and the registry key. Unique across the project. It is <em>not</em> persisted with saved instances, so it can be changed later.</td></tr><tr><td><code>[TypeDescription]</code></td><td>The tooltip in the Add Modifier menu.</td></tr><tr><td><code>ShortDataDescription</code></td><td>The summary on a collapsed row. Use <code>bind.ToString("name")</code> so a bound parameter shows its name rather than a stale value.</td></tr><tr><td><code>Modify</code></td><td>The read pass.</td></tr><tr><td><code>InverseModify</code></td><td>The write pass. Defaults to returning the value unchanged.</td></tr><tr><td><code>ModifyMode</code></td><td>Which passes engage. Settable on the row.</td></tr></tbody></table>

## Numeric modifiers for every numeric type

Writing one modifier per numeric type is tedious. `NumericModifier` handles all of them through two overloads:

```csharp
[Serializable]
[TypeDescription("Returns the absolute value of the input numeric value.")]
public sealed class AbsoluteValueModifier : NumericModifier
{
    public override string Id => "Absolute Value";

    protected override long Modify(long value)     => value < 0 ? -value : value;
    protected override double Modify(double value)  => value < 0 ? -value : value;
}
```

That is the whole of the built-in **Absolute Value** modifier, and it works for `int`, `long`, `float`, `double` and the rest.

## The interfaces directly

When `BaseModifier<T>` does not fit, implement the interfaces yourself.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2Fgit-blob-8821d189c93890c2d9567abb4bb0f619510859de%2Fmodifier-interfaces.png?alt=media" alt="The modifier interfaces"><figcaption></figcaption></figure>

`IReadWriteModifier<T>` implements both `IReadModifier<T>` and `IWriteModifier<T>`. Implement just one of those if the modifier only works in one direction.

The smallest possible modifier is four members and one method:

```csharp
public class InvertModifier : IReadWriteModifier<bool>
{
    public string Id => "Invert";
    public string ShortDataDescription => "[Invert X]";
    public BindMode ModifyMode => BindMode.ReadWrite;

    public bool ModifyRead(in bool value) => !value;    // reading from the source
    public bool ModifyWrite(in bool value) => !value;   // writing to the source

    // The untyped fallback, used when the type is not known at compile time.
    public object Modify(BindMode modifyMode, object value) => !((bool)value);
}
```

This is the built-in shape for modifiers that fan a value out:

```csharp
public class PropagateBoolModifier : IReadWriteModifier<bool>
{
    [SerializeField] private BindMode _mode;

    [WriteOnlyBind]
    public Bind<bool>[] targets = new Bind<bool>[0];

    public bool ModifyRead(in bool value)
    {
        for (int i = 0; i < targets.Length; i++)
            targets[i].Value = value;
        return value;                      // pass the value along untouched
    }

    public bool ModifyWrite(in bool value) => ModifyRead(value);
    public object Modify(BindMode mode, object value) => ModifyRead((bool)value);

    public string Id => "Propagate Boolean";
    public string ShortDataDescription
        => targets.Length == 1 ? "to another target" : $"to other {targets.Length} targets";

    public BindMode ModifyMode { get => _mode; set => _mode = value; }
}
```

Note the `in` parameter. That is what keeps value types from being boxed on the way through, and it is why a chain of five modifiers on a `float` allocates nothing.

<table><thead><tr><th width="290">Interface</th><th>What it is for</th></tr></thead><tbody><tr><td><code>IModifier</code></td><td>The root. <code>Id</code>, <code>ShortDataDescription</code>, <code>ModifyMode</code> and the untyped <code>Modify</code>.</td></tr><tr><td><code>IReadModifier&#x3C;T></code></td><td>Read pass only.</td></tr><tr><td><code>IWriteModifier&#x3C;T></code></td><td>Write pass only.</td></tr><tr><td><code>IReadWriteModifier&#x3C;T></code></td><td>Both. The usual choice.</td></tr><tr><td><code>IObjectModifier</code></td><td>Applies to derived types, with the target type set at runtime through <code>TargetType</code>.</td></tr><tr><td><code>IObjectModifier&#x3C;T></code></td><td>The typed form. Recommended for any modifier that works on instance values and their derived types. Reference types only.</td></tr><tr><td><code>ISmartModifier</code></td><td>Receives the owning <code>IBind</code>, and can push a value up the chain.</td></tr><tr><td><code>ISmartValueModifier&#x3C;T></code></td><td>The typed form, with a <code>SetValue</code> callback, so it can force a value into the pipeline even when nothing asked for one.</td></tr><tr><td><code>INullPropagatingModifier</code></td><td>Handles a null input itself instead of being skipped.</td></tr><tr><td><code>IRequiresAutoUpdate</code></td><td>Tells the binding it must update automatically. Anything time based needs this.</td></tr><tr><td><code>IDynamicComponent</code></td><td>Declares that the output can change without the input changing.</td></tr></tbody></table>

{% hint style="info" %}
`IModifier<T>` still exists and still works, but it is `[Obsolete]`. If you are porting a 2.x modifier, change it to `IReadWriteModifier<T>`. Nothing else about a 2.x modifier needs to change.
{% endhint %}

## Modifiers that need time

If your modifier animates, delays or smooths, it has to be re-evaluated even when the input has not changed. Two declarations do that:

```csharp
public class MyTweenModifier : IReadModifier<float>, IRequiresAutoUpdate, IDynamicComponent
{
    public bool ShouldAutoUpdate => true;   // the binding must update automatically
    public bool UpdateOnEnable   => true;   // and again when the context is re-enabled
    public bool IsDynamic        => true;   // do not cache my output

    // ...
}
```

Without `IRequiresAutoUpdate`, the modifier only runs when something reads the binding. Without `IDynamicComponent`, [phased bindings](/binding-system-3/project-tools/performance/phased-bindings.md) may cache a value that should have moved.

## Drawing options

<table><thead><tr><th width="290">Attribute</th><th>What it does</th></tr></thead><tbody><tr><td><code>[OneLineModifier]</code></td><td>Draw the modifier on one line rather than as a foldout. Right for a modifier with a single field.</td></tr><tr><td><code>[ModifierOptions]</code></td><td>Controls how the modifier is offered: a forced <code>ModifierMode</code>, whether similar modifiers for base types are also allowed, and whether it applies to derived types.</td></tr><tr><td><code>[TypeDescription]</code></td><td>The description in the menu.</td></tr></tbody></table>

## What the system requires of your class

Four rules, all of them enforced by the registration scan rather than by the compiler, so getting one wrong means the modifier simply does not appear in the menu.

<table><thead><tr><th width="290">Rule</th><th>Why</th></tr></thead><tbody><tr><td>A <strong>parameterless constructor</strong></td><td>Instances are created with <code>Activator.CreateInstance</code>. No constructor arguments are ever passed.</td></tr><tr><td>Not <code>abstract</code>, not an interface</td><td>Base classes of your own are fine, they are simply not offered themselves.</td></tr><tr><td>Not an open generic</td><td><code>MyModifier&#x3C;T></code> needs a closed subclass, or a registered template. See <a href="#generic-modifiers-and-your-own-types">below</a>.</td></tr><tr><td>Marked <code>[Serializable]</code></td><td>Otherwise Unity cannot store the configured instance inside the binding.</td></tr></tbody></table>

To keep a modifier **out** of the bind menu, put `[HideMember]` on the class. The auto-registration scan skips it, and nothing else changes.

{% hint style="info" %}
A newly written modifier sometimes needs one more recompilation round before it shows up in the bind menu. If it is missing right after you save, trigger another compile before going looking for the cause.
{% endhint %}

## Registration

Auto registration finds your modifier. To register explicitly:

```csharp
ModifiersFactory.Register<ScaleModifier>();

// A configured instance, offered as-is
ModifiersFactory.RegisterModifier(new ScaleModifier { factor = 2f.Bind() });

// A generic modifier, instantiated on demand
ModifiersFactory.RegisterTemplate(typeof(MyModifier<>));
```

`Register<T>(bool registerForBaseTypes)` is worth knowing about. Pass `false` for a modifier specialised on one of your types, so that its base types keep offering the generic modifiers they would otherwise inherit.

{% hint style="warning" %}
`RegisterModifier` **throws** when the `Id` already exists, unlike converter registration which overwrites. Ids are unique across the project.
{% endhint %}

{% hint style="danger" %}
**Do not rename or move the class once instances exist.** Modifiers are serialized with `[SerializeReference]`, which persists the assembly, namespace and class name. Renaming the class, changing its namespace or moving it to another assembly orphans every instance already saved in a scene, a prefab or an asset, and [Refactoring](/binding-system-3/project-tools/diagnostics/refactoring.md) cannot repair it, because it tracks serialized member paths rather than modifier types.

This is why the deprecated `StringFormatModifier` and `StringConcatModifier` are still sitting in their original files, unregistered, rather than being deleted. The `Id` carries no such constraint and is safe to change.
{% endhint %}

## Generic modifiers and your own types

A modifier written as `MyModifier<T>` cannot be serialized by Unity until some closed `MyModifier<YourType>` exists. That is the same problem the built-in generic modifiers have, and it has the same solution: see [Standard Modifiers for Your Types](/binding-system-3/pipeline/standard-modifiers.md).

## Testing one

```csharp
[Test]
public void Scale_Multiplies()
{
    var modifier = new ScaleModifier { factor = 3f.Bind() };
    Assert.AreEqual(6f, ((IReadModifier<float>)modifier).ModifyRead(2f));
}
```

## Read the built-in ones

Most of the built-in modifiers ship with their source visible, under `Runtime/Modifiers/`. They are meant to be read, copied and changed: if one of them is nearly what you need, starting from its code is faster than starting from this page.

## Related pages

* [Modifiers](/binding-system-3/pipeline/modifiers.md): how the stack behaves.
* [Modifier Catalogue](/binding-system-3/pipeline/modifier-catalogue.md): check before you write.
* [Standard Modifiers for Your Types](/binding-system-3/pipeline/standard-modifiers.md): the generic ones, closed for your types.
