Create a custom persistence manager

A custom manager saves to storage none of the built-in ones reach, such as a console's save container or a service of your own. To write one, subclass PersistenceManager and implement three methods:

protected override LoadResult  ReadPayloadSync (PersistenceSource source, string slot);
protected override SaveResult  WritePayloadSync(PersistenceSource source, string slot, byte[] payload);
protected override ClearResult ClearPayloadSync(PersistenceSource source, string slot);

Each one moves a byte[] payload, and none of them may touch Target. Override the async trio instead to run the work off the main thread.

The result you return decides what the package does next:

ResultWhat it means
InvalidThere is no save, which is the normal first run. The next save overwrites.
CorruptA save is there and wouldn't decode. The next save overwrites.
FailureStorage was unreachable. The target stays not ready, so nothing can overwrite it.
Note: If a slot name can come from the player, escape it before you use it as a file name or an address.

The base settings are virtual properties with no backing field. Override one with a [field: SerializeField] auto-property to expose it in the Inspector window, or with a plain getter to fix its value. Mark the fields that change where or how a save is written [Breaking(...)], so the Inspector window can lock them after release.

The following members cover what a manager can add beyond those three methods.

To do thisUse
Name it in the dropdown[InspectorDisplay("My Backend", "Saves to my service.", Order = 100, Category = "My Studio")].
Draw a custom InspectorInherit PersistenceManagerEditor and override the Draw hook you want, calling base to keep the standard fields.
Join Delete Local DataA static editor method marked [ClearLocalData(typeof(MyManager))] joins Delete Local Data.
Write a test manager[PersistenceManagerInfo(StorageKind = StorageKind.Test)]. It never ships: a release build fails while one exists. Storage that keeps nothing takes StorageKind.Transient.
Write to its logLog("Retrying upload", LogLevel.Warning) adds a line to the manager's log in its Inspector window and in the debug overlay, not to the console. It is thread safe, keeps the last 500 lines, and is stripped outside the Editor and development builds.
Forward to other managersOn, ComposedManager and DisabledReason, the way Platform (Routing) does.

Create a custom serializer

To save data in a format none of the built-in serializers write, subclass Serializer, mark it [Serializable], and implement SerializeToString and DeserializeFromString. For a binary format, override IsBinary and the two byte methods instead. Your class then appears in the Serializer dropdown.

Two more overrides cover what the package does around a payload:

To draw the serializer's own fields, inherit SerializerDrawer.

Warning: A serializer must never fail on valid data. Returning null or throwing disables persistence for that asset.

Create a custom remote manager

RemotePersistenceManager adds an offline cache, a push loop and conflict handling to a manager, so a remote service of your own gets all three. Implement FetchRemote, StoreRemote and DeleteRemote, and map your service's responses onto an Answer:

AnswerMap it from
SuccessThe service answered, and handed back what you asked it for.
NoSaveThe service answered, and holds nothing for this player.
UnreachableThe network or the service is down, and a retry can recover.
SetupErrorA permanent fault, such as no URL, which retrying can't fix.
Note: The payload arrives finished, with compression, encryption and the save lock already run. Store and return those bytes verbatim.

Add your own settings

Subclass SettingsEntry, and its public fields become a section of Project Settings > Persistent Asset. Settings.Get<T>() reads them back, in a player as well as in the Editor. For a layout of your own, inherit SettingsEntryDrawer.

Add a component codec

To save a component Scene Objects doesn't cover, or to replace one it ships, inherit SceneComponentCodec.

Restoring runs in four phases: switch off anything that would overwrite the value, write it, switch that back on, then, once the load has switched every object on or off, start what has to run live, such as a video resuming.

Add your own variable types

A codec is what turns your own type into a No-Code variable. A converter and a widget are optional on top of it.

Extension pointDescription
A variable typeA VariableTypeCodec<T> whose Write and Read turn your type into a string and back, registered with [VariableType].
A value converterThe package finds a ValueConverter<TFrom, TTo> subclass automatically, which lets a Binder drive a member of another type, one way per converter.
An Inspector widgetA VariableWidget<T> paired with a codec id through [VariableWidget] draws the value in the Inspector window.

A codec of your own has three rules to follow. It formats and parses with CultureInfo.InvariantCulture, it treats an empty string as the default value, and it never throws on corrupt input.

Note: Enums and project assets need no codec.
Note: A codec targets one value type. For a collection, use a list or a map variable instead.