> 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/converters.md).

# Converters

Changing the type between the source and the field.

A converter changes the **type** of a value, and that is the whole of its job. It is the only stage of the pipeline you will often not notice, because most of the time the right one is found for you.

{% hint style="warning" %}
**A converter always goes from one type to a different type.** `IConverter<TFrom, TTo>` with `TFrom` and `TTo` the same is not a conversion and is never invoked: when the two ends of a binding already agree on the type, the pipeline builds no converter stage at all.

Changing a value while keeping its type, scaling it, clamping it, remapping it, converting meters to feet, is a [modifier](/binding-system-3/pipeline/modifiers.md). That distinction is the one thing worth getting right about this page: **converters cross type boundaries, modifiers work inside one.**
{% endhint %}

## Automatic conversion

Bind a `float` to a `string` field and the system finds the conversion, applies it and says nothing. Numeric widening, Unity type conversions, enum coercions, `ToString` and the rest are resolved from a registry keyed by the pair of types.

You see a converter in the Inspector in three cases:

1. **It has options.** A format string, a fallback colour, an enum mapping.
2. **There is more than one candidate**, so somebody has to choose.
3. **You picked one deliberately**, replacing the automatic choice.

The converters toggle on the bind row shows or hides the **Read Converter** and **Write Converter** fields.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FxUPSpQbDDLipoH5L8d5h%2FScreenshot%202026-09-28%20at%2011.30.09.png?alt=media&amp;token=e8c190f0-fd1b-4697-a3f2-e483f379c2cc" alt="" width="563"><figcaption><p>A binding with a Format String read converter</p></figcaption></figure>

## Read and write converters

A `ReadWrite` binding needs both directions, and they are separate objects, because a conversion is rarely symmetrical. `float` to `string` on the read side pairs with `string` to `float` on the write side, and the two have different options: a format on one, a fallback and a culture on the other.

## Safe and unsafe

Every converter declares whether it is **safe**.

<table><thead><tr><th width="180">Safe</th><th>What it means</th></tr></thead><tbody><tr><td>Yes</td><td>The conversion always succeeds, whatever the input. <code>float</code> to <code>string</code>, <code>int</code> to <code>float</code>.</td></tr><tr><td>No</td><td>The conversion can fail on some inputs. <code>string</code> to <code>Color</code>, <code>string</code> to <code>float</code>.</td></tr></tbody></table>

An unsafe converter is not a problem, it is a fact about the data. Most of them take a **fallback** value for the failing case, which is where the design pays off: the binding does not throw and does not log a stream of errors, it gives you the fallback.

## What ships in the box

<table><thead><tr><th width="290">Converter</th><th>What it does</th></tr></thead><tbody><tr><td><strong>Format String</strong></td><td>Any <code>IFormattable</code> to <code>string</code>, with a format passed to <code>ToString</code> and an invariant-culture switch. Caches its last result.</td></tr><tr><td><strong>String to Decimal</strong></td><td><code>string</code> to any numeric type.</td></tr><tr><td><strong>Decimal to Integer</strong></td><td>Rounding between numeric types.</td></tr><tr><td><strong>String to Color</strong></td><td>Colour names (<code>red</code>, <code>White</code>), hex (<code>#2e34f3</code>, <code>33BBEF</code>) and <code>RGBA(0.14, 0.94, 0.65, 0.5)</code>, with a fallback colour.</td></tr><tr><td><strong>Gradient To Color</strong></td><td>Samples a <code>Gradient</code> at a position.</td></tr><tr><td><strong>Color to Gradient</strong></td><td>A <code>Color</code> into a <code>Gradient</code>. Each stop of the gradient takes the colour, tinted if wanted, or a colour of its own, so the colour can sit anywhere along it. By default the whole gradient is the colour. Allocates nothing as the colour changes.</td></tr><tr><td><strong>Boolean</strong></td><td><code>bool</code> to almost anything: numbers, strings, vectors, colours.</td></tr><tr><td><strong>Numeric to Bool</strong></td><td>The other direction, with a threshold.</td></tr><tr><td><strong>Numeric to Vector</strong></td><td>A number into a <code>Vector2</code>, <code>Vector3</code> or <code>Vector4</code> component.</td></tr><tr><td><strong>Enum converters</strong></td><td><code>enum</code> to and from numbers and strings, with an explicit mapping editor. Works with typed and untyped enums.</td></tr><tr><td><strong>List / Array</strong></td><td>Between <code>List&#x3C;T></code> and <code>T[]</code>.</td></tr><tr><td><strong>StringBuilder to String</strong></td><td>For text built incrementally.</td></tr><tr><td><strong>Char Array to String</strong> and back</td><td>Mainly for TextMeshPro, which exposes text as a <code>char[]</code>.</td></tr><tr><td><strong>Unity Event</strong></td><td>Lets a <code>UnityEvent</code> participate as a binding target.</td></tr></tbody></table>

The enum converter has its own editor, so mapping `Difficulty.Hard` to `2` or to `"hard"` is a table you fill in rather than code you write.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FWgF3BX6TAMIYekuvSzbA%2FScreenshot%202026-09-28%20at%2014.07.19.png?alt=media&amp;token=6f4f003f-648c-4f48-86b4-b478ae61604b" alt="" width="563"><figcaption><p>The enum converter mapping editor</p></figcaption></figure>

## Saving one

A converter you have filled in can be saved under a name and offered back in every binding whose conversion it can perform. **Window ▸ Binding System ▸ Templates ▸ Converters**. See [Templates](/binding-system-3/pipeline/templates.md#converters).

## Writing one

Two properties and one method. The types have to differ, so pick a real conversion: here a duration in seconds becomes a clock string, which the built-in **Format String** converter cannot express.

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

[Serializable]
public class SecondsToClockConverter : IConverter<float, string>
{
    public string Id => "Seconds to Clock";
    public string Description => "Formats a duration in seconds as m:ss.";
    public bool IsSafe => true;

    public string Convert(float value)
    {
        var total = Mathf.Max(0, Mathf.FloorToInt(value));
        return $"{total / 60}:{total % 60:00}";
    }
}
```

Bind a `float` countdown to a `TMP_Text.text` field, pick this converter on the read side, and the label reads `1:07`.

Mark it `[Serializable]` so Unity can store it, add public fields for anything the user should configure (they are drawn in the Inspector, and they can be `Bind<T>` fields themselves), and it is picked up automatically.

{% hint style="info" %}
If you find yourself writing `IConverter<float, float>`, you want a [modifier](/binding-system-3/pipeline/modifiers.md) instead. A modifier has the same shape, is drawn in the same place, stacks with others, and can declare an inverse for the write direction.
{% endhint %}

See [Custom Converters](/binding-system-3/reference/extending/converters.md) for registration, templates, generics and the untyped form.

## Registration

Custom converters are found and registered for you when **Auto Register Converters** is on, which it is by default. The scan runs on import and after an upgrade.

To register explicitly, for example for a converter built at runtime:

```csharp
ConvertersFactory.Register<float, string>(new SecondsToClockConverter());

// Or from a lambda, when there is nothing to configure
ConvertersFactory.Register<Vector3, float>("Magnitude", v => v.magnitude);
```

Both type arguments are required, and they must differ. Registering a converter whose two types are the same has no effect, because no binding ever asks for that pair.

## Related pages

* [Modifiers](/binding-system-3/pipeline/modifiers.md): changing the value rather than the type. If the type does not change, this is the page you want.
* [Custom Converters](/binding-system-3/reference/extending/converters.md): the full extension story.
* [TextMeshPro and Unity UI](/binding-system-3/reference/integrations/unity-ui.md): the conversions those types need.
