Getting started

Save a value, read it back with a fallback, and do the reading at the only moment the answer is real.

Updated 2026-09-08

Save something

Save.Set("coins", 25);
Save.Set("name", "ada");
Save.Set("spawn", transform.position);

One verb for every type. The value picks which one is used, so there is no SetInt to get wrong. Writing always applies to the player at this client, because that is the only data a client is allowed to change.

Read it back

int coins = Save.Get("coins", 100);
string name = Save.Get("name", "friend");
Vector3 spawn = Save.Get("spawn", Vector3.zero);

The second argument is what you get when nothing is saved, and it is required. That is the whole point: a returning player gets their 25 coins, a new one gets 100, and the code says which is which without a TryGet and an if.

It is also what you get when the key holds a different type than you asked for, so two systems that both picked the name "level" degrade to a default rather than to nonsense.

Read at the right moment

public class Shop : SaveBehaviour
{
    private int coins;

    public override void OnSaveReady()
    {
        coins = Save.Get("coins", 100);
    }
}

Do not read in Start. Saved data arrives shortly after the player joins, and anything read before then is the fallback. This is the mistake worth spending a paragraph on, because it does not look like one: in the Editor there is often nothing saved anyway, so the world behaves exactly as intended right up until a real player with real progress walks in.

Deriving from SaveBehaviour gives you OnSaveReady, which runs once, for the player at this client, at the moment their data is readable.

Somebody else's data

public override void OnPlayerSaveReady(VRCPlayerApi player)
{
    int theirs = Save.Get(player, "best", 0);
}

You can read anybody's saved values, and their data becomes readable at a different moment from your own — when they arrive, which may be an hour later. That is why it is a separate method: a behaviour that reloaded its own state every time a stranger joined would throw away everything done since.

Telling a first visit from a returning one

if (!Save.Has("visits"))
{
    // Hand out the welcome pack.
}

Save.Has answers whether a key has ever been written. Most of the time a fallback says the same thing more directly; reach for this when the absence itself is the information.

Two limits worth knowing

Nothing can delete a key. VRChat can overwrite one but not remove it, so the set of keys a world uses is worth deciding once rather than letting it grow.

Keys are shared across the whole world. Two systems that both save "level" are using the same key. Give them prefixes.

Something went wrong Reload