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

Design Your Save Data Like a Production System: The Official Persistence Best Practices, Closely Read

Save data isn't just another variable — it's production data that will live alongside your players for years. Epic wrote a dedicated page called Verse Persistence Best Practices; this article breaks it down into four actionable design principles, plus a pre-launch checklist.

1. Load Failure = Locked Out: Understand This Safety Line First

The sentence in the official docs that's easiest to skim past — yet most likely to change how you design — is this: before a player joins, the engine loads their persistent data first; if that load fails, the player is blocked from joining. At first glance it feels brutal — why not let them play on default values in the meantime? Epic's explanation: it's a safeguard against saves being overwritten. Imagine if "load failed, treat as new player" were allowed — one network hiccup could let a veteran player walk in with a blank save, your code would dutifully write "gold: 0" back, and their real save would be legally flushed away.

Two corollaries for creators. First, you don't need (and shouldn't) write defensive code for a "half-loaded save" state: if a player is present, their data loaded completely. Failing to find a player's record only ever means "they're new", never "the load broke". Second, the leaner and more robust your save structure, the lower the odds of a load failure — which leads straight into the next section's size budget.

2. Spending the 256 KB Budget: Keep It Small, Write It Rarely

Each player's record under a single persistent variable is capped at 256 KB; exceed it and the save fails with a Verse runtime error. 256 KB sounds roomy, but a pattern like "push the whole match's event log into a persisted array" can chew through it within a few dozen rounds. Epic's advice comes in two prongs:

write_discipline.verse
# ✗ Anti-pattern: stuffing the position into a persisted array every frame
# loop:
#     Sleep(0.0)
#     if (set TravelLog[Player] = ...) {}    # high-frequency writes + unbounded growth

# ✓ The right way: write the "conclusion" once, when the round ends
OnRoundEnd(Player:player, RoundScore:int):void =
    var Best:int = RoundScore
    if (Old := BestScore[Player], Old > RoundScore):
        set Best = Old
    if (set BestScore[Player] = Best) {}

Reading the code, Blueprint-style: the OnRoundEnd in the lower half is the "write once, only when the round ends" pattern done right — first drop this round's RoundScore into a variable Best; then use a Branch to look up this player's historical best, and if it exists and the old score beats this round's, swap Best back to the old one (keep the higher score); finally use a single Set to write Best back into the save table. The whole routine runs exactly once at round end — nothing is wired to every frame.

3. Leave a Door for the Future: class Is the Only Container That Can Grow

Section five of the base lesson covered publish lock-in: the moment you publish, "what type goes in each slot" of your save table is enrolled in a lifelong backward-compatibility check. The best-practices page promotes that rule into a design principle: any data that might evolve across versions should be wrapped in a class (a Blueprint class) from day one — never a naked int, struct, or tuple. Because among all persistable types, only class supports "appending fields with defaults after publishing" — store just an int and later want "int + timestamp"? There's no path; your only option is opening a whole new save table, and each project has a hard cap on save tables. That budget is more precious than you think.

Epic's own Speedway Race template is this principle in the wild: it saves race results through PlayerStatsMap using a persistable stats class, and when Epic later added new features to the template, they did it precisely through the "append fields with defaults" evolution path. The theory lives on the best-practices page, the worked example lives in the template — reading them side by side pays off the most.

4. Pre-Launch Checklist

The whole best-practices page, condensed into four questions to tick off before you publish:

A player's persistent data fails to load as they join. Under the official design, what happens?

Sources

This article draws on Epic's official documentation and blog: