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

# Accessor Providers

Bindable paths for things that are not members.

{% hint style="warning" %}
This page is for experienced users. Everything else in the system can be extended without it.
{% endhint %}

By default a path is walked with cached accessors over real members, built by reflection once and then direct. Some things you want to bind are not members at all:

* A **shader property** on a material. `_Color` is a string key, not a field.
* A value behind an API that needs a call, not an access.
* A computed view over an object that the type does not expose.

An **accessor provider** contributes paths of its own, and supplies the accessor that reads and writes them. This is how material property binding works in the package.

## The interface

```csharp
public interface IAccessorProvider
{
    // Unique. A duplicate fails registration.
    string Id { get; }

    // Which paths this provider offers for a given source.
    IEnumerable<AccessorPath> GetAvailablePaths(object source);

    // The accessor for one of them.
    IAccessor GetAccessor(Type sourceType, string pathId);

    // Turn a bind-menu id back into a canonical path.
    bool TryConvertIdToPath(string id, string separator, out string path);
}
```

## A worked example

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using Postica.BindingSystem;
using Postica.BindingSystem.Accessors;
using UnityEngine;

public class TextureDataProvider : IAccessorProvider
{
    public string Id => "Texture Data Provider";

    public IEnumerable<AccessorPath> GetAvailablePaths(object source)
    {
        if (source is not Texture2D)
            return Enumerable.Empty<AccessorPath>();

        return new[]
        {
            new AccessorPath(this, "Width",  BindMode.Read, typeof(int)),
            new AccessorPath(this, "Height", BindMode.Read, typeof(int)),
        };
    }

    public IAccessor GetAccessor(Type sourceType, string pathId) => pathId switch
    {
        "Width"  => new ObjectTypeAccessor<Texture2D, int>(t => t.width,  null),
        "Height" => new ObjectTypeAccessor<Texture2D, int>(t => t.height, null),
        _ => throw new ArgumentException(nameof(pathId)),
    };

    public bool TryConvertIdToPath(string id, string separator, out string path)
    {
        path = id;
        return true;
    }
}
```

Auto registration picks it up, and the paths appear in the bind path menu under **Other Providers** whenever the source is a `Texture2D`.

<figure><img src="https://3048705056-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLUfBXR02sJ5i0MxwYbaV%2Fuploads%2Fu3IlbfRwqK2XAM1vQcaR%2FScreenshot%202026-09-28%20at%2018.23.55.png?alt=media&amp;token=3697f8aa-4e72-4b4c-99d7-647d25400082" alt="" width="292"><figcaption><p>A custom provider's paths in the bind path menu</p></figcaption></figure>

## Starting from the base class

`BaseAccessorProvider<T>` removes the type checking and the casting:

```csharp
public class TextureDataProvider : BaseAccessorProvider<Texture2D>
{
    public override string Id => "Texture Data Provider";

    public override IEnumerable<AccessorPath> GetAvailablePaths(Texture2D source)
    {
        yield return new AccessorPath(this, "Width",  BindMode.Read, typeof(int));
        yield return new AccessorPath(this, "Height", BindMode.Read, typeof(int));
    }

    protected override IAccessor GetAccessor(string path) => /* ... */;
}
```

It also handles the case where the source is *part of* a bigger object, through `TryGetPreciseObject`, which is what makes a provider on a `Component` work when the user drops a `GameObject`.

The package ships two of these: `ComponentAccessorProvider` and `MaterialsAccessorProvider`. They are worth reading.

## How provider paths are stored

A path handled by a provider is prefixed with `[P]` in the serialized data. That prefix is why the [build-time optimizer](/binding-system-3/project-tools/performance/optimized-accessors.md) skips these paths: the provider owns the resolution, so there is nothing for codegen to walk.

## Registration

```csharp
AccessorsFactory.RegisterAccessorProvider(new TextureDataProvider());
AccessorsFactory.UnregisterAccessorProvider(provider);
```

Auto registration is controlled by **Auto Register Providers** in [Settings](/binding-system-3/reference/settings.md#configuration).

For a type that registers accessors rather than providers, mark the entry point with `[RegistersCustomAccessors]` and the editor calls it at startup.

## Writing the accessor

<table><thead><tr><th width="290">Accessor</th><th>What it is for</th></tr></thead><tbody><tr><td><code>ObjectTypeAccessor&#x3C;S, T></code></td><td>A getter and a setter delegate over a reference-type source. Pass <code>null</code> for the direction you do not support.</td></tr><tr><td><code>ValueTypeAccessor&#x3C;S, T></code></td><td>The same for a value-type source, taking the source by <code>ref</code>.</td></tr><tr><td><code>IAccessor&#x3C;T></code></td><td>Implement it directly for full control.</td></tr></tbody></table>

Report your capability honestly through the accessor's `CanRead` and `CanWrite`. The bind path menu uses them to filter, and a read-only path offered as writable becomes a confusing runtime failure.

## Related pages

* [Paths and Parameters](/binding-system-3/overview/paths.md): the ordinary path syntax, and the `[P]` prefix.
* [Value Providers](/binding-system-3/reference/extending/value-providers.md): the neighbouring extension point, for parameter values.
* [Binding from Code](/binding-system-3/overview/code.md): using the accessor layer directly.
