Your game can save gold, and an update should add a "play count". You can create data for new players, but existing players' saves have no such field.
What matters is keeping the gold they had and deciding what value goes into the new field . The logic that shapes old data into a form your current game can use is called migration .
This article carries over the 300 gold from the Save Game introduction and adds a "post-update play count". We look at a format number, fill in values only when needed, and confirm the next load does not overwrite what we filled in.
What You'll Learn
- The relationship between fields missing from old data and defaults
- Distinguishing save formats with SaveVersion
- Migrating right after loading and confirming the save succeeded
- Testing a second load and an unsupported format
This builds on BP_DemoSave , which holds the saved values, and BP_SaveDemo , which calls save and load, from the Save Game introduction. Starting a new project means building gold saving and loading first.
Old saves do not contain the new field
The previous BP_DemoSave had only SavedGold. Adding PlayCount does not write a past play count into files already saved.
A default is the initial value each variable takes when a new save container is created. With UE's standard save format, fields missing from old data start from the class default and removed fields are ignored on load. Official documentation on object handling

What we consider here is not just "did it read without an error". It is how the game interprets that value .
Even with PlayCount at 0, the number alone cannot say whether it means "never played" or "we did not record it back then". Without a saved past count, the exact lifetime total cannot be recovered.
Here we define PlayCount as "plays since this update" . Old records are set to 0 at migration and counting starts from subsequent plays. So it is easy to confirm the absence in old data, we set the saved type's default to -1.
SaveVersion tells you "which format this is"
Give the save type an integer called SaveVersion . It marks "which fields and meanings this record was saved with". It is managed separately from your game's product version and UE's version.
Our handling is as follows.
| SaveVersion | Save format | Handling after reading |
|---|---|---|
| 1 | The old format, saving only SavedGold | Set PlayCount to 0 and migrate to format 2 |
| 2 | The current format, also saving PlayCount | Use the saved values as they are |
| Anything else | A format this implementation does not define | Show a message; do not apply or overwrite |

The Save Game introduction's saves have no SaveVersion either, so fix this variable's default at 1 . That treats numberless old data as 1.
When saving newly in the current format, explicitly set 2 before writing. Changing the default to 2 instead would make numberless old records look like format 2 and skip the migration they need.
Including SaveVersion from the start reduces the work of deciding how to treat that "numberless generation". The number alone does not change values, though. Fix up values → update the number → save is one set.
Preparation: keep an old save and add fields
1. Save in the old format before adding fields
Open BP_SaveDemo from the Save Game introduction and change SlotName's initial value to NotesMigrationDemo01 . We use a separate slot for the migration experiment.
Without adding new fields to BP_DemoSave yet, Play and press 1 for 300 gold and 2 to save. Confirm "Saved" and stop Play. That is the pre-update record.
Experimenting on PC, copy the project's Saved/SaveGames/NotesMigrationDemo01.sav into a test backup folder. Saving after migration updates the contents, so keeping the original lets you retry the same conditions repeatedly.
2. Add variables to the save type and the Actor
Add these two variables to BP_DemoSave. Do not change the existing SavedGold's name or type.
| Variable name | Type | Default |
|---|---|---|
| SaveVersion | Integer | 1 |
| PlayCount | Integer | -1 |
Next add these two to BP_SaveDemo.
| Variable name | Type | Initial value | Role |
|---|---|---|---|
| LoadedSave | BP_DemoSave Object Reference | None | Points at the save container just loaded |
| PlayCount | Integer | 0 | The post-update play count used in game |
Despite the shared name, BP_DemoSave's is the value kept in the file and BP_SaveDemo's is the value during play . Saving and loading copy between them. SaveRef, as in the Save Game introduction, is the reference to the container created when saving.
Compile and save both. Until the normal save logic is updated below, work without pressing 2 .
3. Display gold and the count together
In BP_SaveDemo's ShowGold, change Format Text's "Format" to this.
Gold: {Gold} / Plays since update: {Plays}
Connect Gold's Get to "Gold" as before and the Actor-side PlayCount's Get to the new "Plays". The Result-to-Print Text connection and ShowGold's white exec line stay as they are.

Keep Print Text as in the Save Game introduction with screen and log output enabled, Duration=10, and Key=GoldStatus. From here, the separately placed result-message Print Texts also use screen and log output with Duration=10, but with Key=None.
Implementation: read, fix up, save, then use
1. Gather the logic returning values to the game
First create ApplyLoadedSave with "Add Custom Event". No arguments. Its job is reading gold and the count from LoadedSave, returning them to the Actor, and displaying.
Drag from LoadedSave's Get to create Get Saved Gold and Get Play Count . With LoadedSave connected to each Target, connect as follows. Gets only read values, so no white exec line runs through them. "Pure" in the diagram means nodes that only return values like this.

| Value read from the save container | Where it is written on the Actor |
|---|---|
| Get Saved Gold | Set Gold's value |
| Get Play Count | Set PlayCount's value |
The white exec line runs ApplyLoadedSave → Set Gold → Set PlayCount → ShowGold.

Call this event after loading succeeds and the format is confirmed. Do not call it from BeginPlay; keep the startup display as the existing ShowGold. LoadedSave right after start is None, pointing at no container yet.
2. Hold the loaded container and branch by format
Keep the 3 key → Load Game from Slot → Cast To BP_DemoSave from the Save Game introduction, along with the Cast Failed message.
Disconnect the white line that went straight from Cast success into Set Gold and connect it to Set LoadedSave . Pass "As BP Demo Save" into LoadedSave's value. The previous post-load Set Gold and ShowGold calls now live in the event from step 1 and are not used on this path.

Place Switch on Int after Set LoadedSave. That node selects an exit based on an integer. Select the node, set "Start Index" to 1, and use "Add Pin" to create exits 1 and 2. Keep "Default" too.
Create Get Save Version from LoadedSave's Get and pass its integer output into the Switch's "Selection".

The exits connect as follows.
- 1 : to the migration logic built next
- 2 : to the ApplyLoadedSave call
- Default : Print Text
Unsupported save formatand stop there

A format 3 save made by a newer build can be opened by a game that only knows up to format 2. Rather than "use everything not below 2", we accept only the formats we know . Official Switch documentation
3. Update PlayCount and the number for format 1 only
Create Set Play Count and Set Save Version from LoadedSave's Get. Both Targets are LoadedSave.
From the Switch's 1, wire Set Play Count=0 → Set Save Version=2. What changes here is the loaded container's PlayCount, not the Actor's.

We do not change SavedGold. The previous 300 carries over and only the new count starts from 0. Format 2 records never take this path, so an already-saved count is not reset to 0.
4. Save the migrated container and use it on success
Connect Save Game to Slot after Set Save Version. Pass LoadedSave's Get into "Save Game Object", SlotName's Get into "Slot Name", and 0 into "User Index".

Connect its white output into a Branch and "Return Value" into "Condition".
True prints Migrated from v1 to v2 and saved and then calls ApplyLoadedSave. False prints Could not save the migration result and stops.

Connect the success Print Text's white output into the ApplyLoadedSave call. The Print Text in the diagram below is the same success-message node from the diagram above.

Changing the container's values and updating the file are separate. Without saving, the next load still finds the old format in the file. Here we save right after migrating and confirm the write succeeded.
Note that detecting a failed save does not guarantee the original file is intact. Keep the copy you made before experimenting until verification is done.
5. Have normal saves write format 2 too
Update the 2 key's save logic. Between the Set Saved Gold and Save Game to Slot from the Save Game introduction, add these two.
- Create Set Play Count from SaveRef's Get with the Actor-side PlayCount's Get as its value
- Create Set Save Version from SaveRef's Get with the value 2
The white line runs Set Saved Gold → Set Play Count → Set Save Version → the original Save Game to Slot. Both Set nodes' Target is SaveRef, the container being saved this time.

The container saved during migration is LoadedSave and the container newly created by a normal save is SaveRef. Mixing them up writes the values loaded last time while you think you are saving current progress.
Confirm: do values persist on a second load?
Prepare an operation that changes the count for confirmation. From the number key 5 's "Pressed", wire the Actor-side Set PlayCount=5 → ShowGold. That is for confirming saved values, not logic counting plays automatically.

Compile, Play, and press 3 before pressing 2 to save. Mind the order so you do not overwrite the old format with a new save before confirming it reads.
Migrated from v1 to v2 and saved appearing with Gold: 300 / Plays since update: 0 means you took the migration path. Then press 5 and 2 to save, restart Play, and read it back. As a table, the flow is as follows.
| Action | Expected result |
|---|---|
| Press 3 with the old format present | The migration message. Gold 300, count 0 |
| Press 5 | The count becomes 5 |
| Press 2 | The normal save succeeds |
| Stop Play, Play again, and press 3 | Gold 300, count 5. No migration message |

If the count returns to 0 on the second load, check SaveVersion=2 in the normal save and the connection copying PlayCount into the container. Also revisit the Target and SlotName when saving the migrated container.
In a real game, you would increment the Actor-side PlayCount when a play ends and then save. Which moment counts as "one play" is decided by your game's rules.
Test an unsupported number too
Stop Play and change SlotName to a different name, NotesMigrationUnknown . Temporarily set the SaveVersion written on normal save to 99, compile, and Play.
Press 1 for 300 gold, 2 to save, 4 to make it 900 gold, then 3 to read. Unsupported save format appearing with gold staying at 900 means an unsupported record is stopped without being applied. Press 3 again and confirm the same message.
After checking, stop Play, return the SaveVersion assignment to 2, and SlotName to NotesMigrationDemo01 . Do not change BP_DemoSave's default of 1. The Cast Failed for a missing save can also be confirmed with the unused-slot method from the previous article.
To redo from the old format, stop Play and restore the experiment .sav from your backed-up old file. Data newly saved with the updated type and merely renumbered to 1 does not constitute testing "an old file that genuinely lacks the new field".
Bonus: renames, type changes, and more generations
To rename or retype a variable, keep the old field
Adding, removing, renaming, and retyping fields each have different things to confirm.
| Change | What to confirm |
|---|---|
| Adding a field | Whether the default's meaning fits the game for old records |
| Removing a field | Whether the value needs carrying over and whether later migration uses it |
| Renaming | Whether the old name's value can be carried into the new field |
| Changing type or unit | Beyond readability, whether ranges, decimals, and units line up |
Some type combinations get converted by UE, but that alone does not preserve their meaning in the game. Changing milliseconds to seconds, for instance, reads fine numerically but needs a division by 1000.
To migrate readably in Blueprint, keep the old field's name and type and add the new field separately, copying values across . Deleting the old BestTimeMilliseconds first leaves nothing to read the source value from.

In this example, we convert to Float and divide by 1000.0 so integer division does not truncate the decimals. UE also has name mapping via Core Redirects, but confirm with a real old save when adopting it.
Moving to format 3 chains the conversions
Building format 3 means format 1 data needs both "1 → 2" and "2 → 3". Format 2 takes only the latter. Look at the entry number and advance through the stages you need.

Gathering each conversion into a function and entering at the matching stage keeps generation branches from scattering through your game. To read format 3, add a 3 exit and a 2-to-3 migration path to our Switch.
While you support users on an old format, keep that format's migration logic and a test save from it. Also confirm that loading the same format twice does not reset the count or grant a bonus twice.
Format migration and corruption handling are separate
SaveVersion marks how to treat a record you could read. It is not a feature repairing a file whose write was interrupted. For a shipped game, consider save methods that preserve the original, such as writing to a separate slot or keeping generational backups.
When loading fails, immediately overwriting the same slot with initial values can lose a record useful for recovery. Handle retrying, restoring from backup, and starting fresh separately.
Keeping settings such as volume and resolution separate from progress data lets settings survive deleting a save. The settings screen article covers destinations suited to different items. To organize what you save, structs also help.
Summary
- Old saves do not contain new fields; reading them yields defaults
- SaveVersion tells you "which format this is"
- After migrating, writing back completes the set . Without it, migration runs every time
- Always confirm values persist on a second load
The question to ask when adding a field is "what does this value become when reading an old save?" If you cannot answer, you need migration logic.
Saving itself is covered in Save Game and in-session memory in GameInstance.