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

# Paths and Parameters

What a bind path can address, and how deep it can go.

A **path** is a route from the source object to a value. It is a string, it is dotted, and it goes as deep as you need.

## Syntax

<table><thead><tr><th width="330">Form</th><th>What it is</th></tr></thead><tbody><tr><td><code>x</code></td><td>A public or serialized field.</td></tr><tr><td><code>localPosition.x</code></td><td>A field of a field. Any depth.</td></tr><tr><td><code>localPosition.normalized</code></td><td>A property, including computed ones.</td></tr><tr><td><code>material.color.r</code></td><td>Mixed fields and properties.</td></tr><tr><td><code>items[3].name</code></td><td>An array or list element.</td></tr><tr><td><code>lookup.Item[key]</code></td><td>An indexer property.</td></tr><tr><td><code>GetSiblingIndex()</code></td><td>A method, read through its return value.</td></tr><tr><td><code>GetChild(Int32)</code></td><td>A method with parameters. The parameter type appears in the path.</td></tr><tr><td><code>Scale(Vector3)</code></td><td>A method used for <strong>writing</strong>: the value goes into one of its parameters.</td></tr><tr><td><code>Scale(Vector3)|ToString()</code></td><td>A read/write pair. The left side writes, the right side reads.</td></tr></tbody></table>

You never have to type any of this. The bind path menu builds the path for you, and the syntax is here so that a path you see in a diff, in a log, or in the [Dependencies](/binding-system-3/project-tools/diagnostics/dependencies.md) window makes sense.

{% hint style="info" %}
Three internal path forms show up in serialized data. `[P]` marks a path handled by an [accessor provider](/binding-system-3/reference/extending/accessor-providers.md), for example a material property. `[i]` marks an array element. `[T]` marks a [cast](#casting-along-the-path) to a derived type. All three are written by the menu, never by hand.
{% endhint %}

## Depth limits

The bind path menu walks the type graph to build its list, and type graphs in Unity are cyclic: a `Transform` has a `GameObject` which has a `Transform`. Three limits keep that finite.

<table><thead><tr><th width="290">Limit</th><th>What it controls</th></tr></thead><tbody><tr><td><strong>Max Bind Path Depth</strong></td><td>How deep the menu goes into a single type. Default <strong>3</strong>, adjustable in <a href="/binding-system-3/reference/settings.md#optimization">Settings ▸ Optimization</a>, capped at 4.</td></tr><tr><td>Total depth</td><td>A hard ceiling of 14 segments across the whole walk.</td></tr><tr><td>Repeated type and name</td><td>The same type reached under the same member name is not expanded twice, which is what stops <code>transform.gameObject.transform…</code>.</td></tr></tbody></table>

{% hint style="success" %}
If the bind path menu is slow to open on a big type, lower **Max Bind Path Depth** to 2. Existing deeper paths keep working: the limit is on browsing, not on binding.
{% endhint %}

A path that a limit trimmed is still reachable in two hops: bind to the intermediate object, [pin](/binding-system-3/overview/pinning.md) it, and continue from there.

## Nulls along the path

`material.color.r` fails differently depending on whether `material` is null or you simply have not assigned one yet. **Propagate Nulls**, in the bind menu under **Settings**, decides which behaviour you want.

<table><thead><tr><th width="180">Propagate Nulls</th><th>What happens</th></tr></thead><tbody><tr><td>Off (default)</td><td>An error is logged and the value is left unchanged. Loud, and correct while you are building.</td></tr><tr><td>On</td><td>The result is <code>null</code>, or the default for a value type, and nothing is logged. Correct when a null part of the path is a legitimate state.</td></tr></tbody></table>

## Parameters

When a path encounters a method or an indexer, the arguments become **parameters** on the binding, drawn under the row. Each parameter is a value field, and each one can itself be a binding.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2F7cp5lcewMf8AGwEpEkRz%2FScreenshot%202026-09-27%20at%2022.51.08.png?alt=media&amp;token=ae0eb892-4ae4-4b68-9885-ab727827e7a6" alt="" width="563"><figcaption><p>A method path with one constant parameter and one bound parameter</p></figcaption></figure>

```csharp
// Reads transform.GetChild(index), where index is itself bound to a slider.
public ReadOnlyBind<Transform> currentChild;
```

That is worth restating, because it is where a lot of the system's reach comes from: **a parameter is a full binding**, with its own source, path, converters and modifiers.

### The main parameter

A write through a method has to put the value somewhere. The **main parameter** is the argument that receives it; the others keep their configured values.

```
Scale(Vector3)  with main parameter 0   →   Scale(theValueYouWrote)
Set(String, Int32) with main parameter 1 →   Set("hp", theValueYouWrote)
```

The bind path menu picks one for you: the first argument whose type the bound value can be written into. You can change it whenever another argument would be the better choice.

#### Changing it in the Inspector

The main parameter is marked in the parameter list, so you can see which argument the value goes into without opening anything.

<div align="center"><figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2FBNyD3en1VajXLVTPdpIK%2FScreen%20Recording%202026-09-27%20at%2022.53.02.gif?alt=media&amp;token=b2e5df43-0434-492c-91ef-6e7a4e702ad5" alt="" width="563"><figcaption><p>The parameter rows of a bound method: the marked main parameter, and the marker offered on another argument that could take the value</p></figcaption></figure></div>

<table><thead><tr><th width="300">What you see</th><th>What it means</th></tr></thead><tbody><tr><td>A marker on a parameter row</td><td>This is the main parameter. The value being written goes here.</td></tr><tr><td>The same marker appearing when you hover another row</td><td>That argument could take the value too. Click the marker to move the main parameter to it.</td></tr><tr><td>No marker on any row</td><td>No argument can take the value, or you cleared it. See below.</td></tr></tbody></table>

Clicking the marker on the current main parameter clears it. A method with no main parameter is still called on a write, with all of its arguments as configured, and the value being written is discarded. That is sometimes what you want, for a method like `Refresh()` or `Play("hit")` that should simply run when the binding writes.

{% hint style="info" %}
The marker only appears while the binding can write, since it decides nothing on a read. A `Read` binding calls the method with its configured arguments and uses what comes back.
{% endhint %}

### Parameters in code

```csharp
// Constructor form: parameters follow the path.
myBoundTransform = new Bind<Transform>(transform, "GetChild(Int32)", 1);
```

## Casting along the path

A path only knows the **declared** type of each member it walks through. A field typed `Pet` hides the `barks` of the `Dog` it actually holds, and a `VisualElement` hides the `text` of the `Label` it actually is.

Every member whose type is a class that has derived types opens a **Cast To** branch in the bind path menu. Pick a derived type there and the path carries on as that type, into the members the declared type does not have:

```
pet  ▸  Cast To  ▸  Dog  ▸  barks        →    pet/[T]Dog@Assembly-CSharp/barks
```

The type is written with its assembly after an `@`, and with every dot of its namespace turned into a `|`, because a dot would split the path.

The cast is checked each time the value is read. When the object is not of that type, the result is null, or the default for a value type, and nothing is thrown.

A few roots are never offered a cast, because their derived types are most of a project: `object`, `UnityEngine.Object`, `Component`, `Behaviour`, `MonoBehaviour` and `ScriptableObject`. For a component, pick the component you want as the source instead. The branch is also left out when a type has more than 500 derived types, and at the start of a [static](/binding-system-3/overview/sources.md#static) path, where the source is a type rather than a value.

{% hint style="info" %}
A path with a cast is resolved at runtime and is never turned into [generated code](/binding-system-3/project-tools/performance/optimized-accessors.md).
{% endhint %}

## Methods in the bind path menu

Methods are the bulk of what a Unity type exposes, so listing them makes the menu long. They are on by default, and can be turned off in [Visualization ▸ Methods](/binding-system-3/reference/settings.md#visualization). Turning them off makes the menu much shorter and quicker to build on large types; bindings that already point at a method keep working.

## Hiding members from the menu

Two attributes keep noise out of the menu, from the type's own side:

```csharp
[HideMember]                                  // hide this member
public float internalCounter;

[HideMember(HideMemberAttribute.Hide.InternalsOnly)]  // keep the member, hide its insides
public ComplexThing thing;
```

The editor also applies global filters and overrides through `ReflectionFactory`, which is how, for example, `GameObject.transform` is hidden while `Transform` itself stays fully bindable. See [Attributes](/binding-system-3/reference/attributes.md).

## Related pages

* [Sources](/binding-system-3/overview/sources.md): what the path starts from.
* [Accessor Providers](/binding-system-3/reference/extending/accessor-providers.md): adding paths that do not exist as members.
* [Optimized Accessors](/binding-system-3/project-tools/performance/optimized-accessors.md): which paths a build can turn into direct code.
