Verse Wiki — the Verse handbook for Blueprint authors
Extension · EXTRA

weak_map and Cross-Session Persistence: Let Player Progress Outlive the Round

The moment the game ends, every score in a regular map turns to dust. Want leaderboards, player levels, a currency system? You need map's close cousin weak_map — Verse's vehicle for cross-session persistent data. This page is the bridge from the map lesson to real production.

1. map's Close Cousin: The Restricted-Everywhere weak_map

weak_map(k, v) and map are close cousins, but the spelling and the temperament both differ: you declare the type with parenthesesweak_map(player, my_data) — not map's square-bracket [k]v; the initial value, though, is the same familiar map{}. The real difference is on the ability list — a weak_map can't be roll-called row by row (no ForEach), can't tell you its row count (no .Length), and can't be merged whole; it knows exactly one trick: take a key, read or write that one entry.

Sounds like a "crippled map"? Quite the opposite: those very restrictions buy its superpower. Because the engine never has to clutch a "complete roster of every key," each weak_map entry can be read in and flushed to disk by the engine on demand — exactly the property a save system needs.

2. Cross-Session Saves: The Right Place + Marked Savable

The official recipe for keeping data across sessions has just two ingredients: first, declare the weak_map variable at module scope (outside every class — think of it as a public drawer anyone can reach into), with player as the key type; second, the value has to be a "savable" type — most commonly a data structure marked <persistable>, where every field uses a savable type and every field gets a default value. With both in place, the engine automatically saves the data per player, per project, across sessions (in UEFN, one project is one island) — you write zero lines of save code, even less work than manual Save/Load SaveGame in Blueprint.

player_progress.verse
using { /Verse.org/Simulation }

# Persistable player progress: final + persistable, every field has a default value
player_progress := class<final><persistable>:
    Level:int = 1
    Coins:int = 0

# Module-scope weak_map: UE6 auto-saves it per player, per island, across sessions
var PlayerProgress:weak_map(player, player_progress) = map{}

Reads and writes reuse all the muscle memory from the map lesson — key lookups get wired into a Branch, and writes get wired into a Branch too. Because a "savable" record is likewise "a whole you can't edit in place," the way to "change one field" is "build a new record modeled on the old one and overwrite the whole entry":

level_up.verse
using { /Verse.org/Simulation }

# Level a player up: read the old progress, write back a new instance
LevelUp(Target:player):void =
    if (Old := PlayerProgress[Target]):
        NewProgress := player_progress{Level := Old.Level + 1, Coins := Old.Coins}
        if (set PlayerProgress[Target] = NewProgress) {}

Graph read-through: LevelUp is a function that levels a player up. A first Branch looks the old progress up by the player key (Old); if found, a new record is built from the old values — level +1, coins copied over; then a second Branch writes the new record back under that player's name. Not one "edit in place" anywhere — it's all "read it out, build a new one, write the whole thing back."

Keep the quota in mind too: one weak_map record holds at most 256 KB per player. Sounds small, but for structured data like levels, currency, and quest progress it's plenty and then some — it's a save slot, not a warehouse.

3. When to Use It: Division of Labor and Version Upgrades

Draw the line of labor first: in-round stats use a regular map; only cross-session data calls in the weak_map. The official Team Elimination tutorial is the textbook demo — within a round, a regular "player → eliminations" map tracks everyone's eliminations, free to query and roll-call at will; only when you want "lifetime total eliminations" to accumulate across rounds do you move it into a savable weak_map. Don't save everything "just in case": a weak_map can't be roll-called — you can't even "list out every player's data."

Finally, the problem every aging project slams into: the game updates, and that "savable" data structure needs a new field — now what? The strategy from the official Verse Persistence Best Practices is always give new fields default values — when an old player's save is read back, the new fields auto-fill with their defaults, upgrading smoothly. Never delete a field or change what a field originally meant; that leaves old saves with nowhere to live. Pairing every field with a conservative default is the longevity secret of a save system.

Which of these can a weak_map NOT do?

Sources

Compiled from official Epic documentation and tutorials:

Using Persistable Data in Verse (Official Docs) ↗
Verse Persistence Best Practices (Official Docs) ↗
Team Elimination: Tracking Players Using Maps in Verse (Official Tutorial) ↗