• Home
  • Blog
  • Migrating a Live Game from Local Saves to Cloud Saves Without Losing Player Progress

Migrating a Live Game from Local Saves to Cloud Saves Without Losing Player Progress

You shipped with local saves two years ago. The next update adds cloud progression. On first launch, the game finds a Level 47 character on disk and uploads it.

That sounds fine until the same player has already opened the new build on another machine and created a Level 3 cloud profile. Or the local save is six months old. Or it was written by a schema you retired last year. Or a platform cloud restore just puts an older copy back on disk. At that point, “upload the local save on first launch” is not a migration plan. It is one branch in a data-migration state machine.

For a live game, the hard part is not moving bytes. It is deciding which state is valid, who owns it afterward, and how to recover when that decision is wrong.

A safe migration needs to identify the player, understand the source save, transform and validate it, detect whether the player already has cloud state, move authority deliberately, and leave enough evidence to repair the player later. Uploading is just one step in the middle of that list.

The rest of this is how to do it gradually and reversibly, so the returning player, the offline player, and the player on the old client all keep what they earned.

Inventory the Save System You Already Have

You can’t migrate what you haven’t mapped, and a live game’s save footprint is always bigger than the one file you think about. Before any migration code, get honest about four things.

  • Where progression can physically live.
    Local disk is the obvious one. But your players also have saves in Steam Cloud, Xbox and PlayStation platform storage, Nintendo’s storage, mobile app sandboxes, OS backups, and under more than one user profile on the same machine. Each of those is a place a “current” save can come from, and some of them will hand you a stale copy at the worst moment. Deciding whether these platform-native copies should even feed a game-level backend is its own question; we covered the platform-native versus game-backend save tradeoff separately.

  • What a “save” actually is in your game.
    It’s rarely one monolithic blob. There are usually several save slots, separate character files, a settings file, checkpoint state, meta-progression, inventory, and economy state, and they may not all live together or share a format.

  • How many schemas are still in the wild.
    A live game accumulates save formats. v1.0, v1.2, v1.7, v2.0, all shipped, all still sitting on someone’s drive. The player who matters most here is the one who last opened your game eighteen months ago and is about to jump from a v1.0 local save straight to your current cloud format. Games like No Man’s Sky live on exactly this player, the one a big update pulls back after a year away. Your migration has to support that jump directly, not assume a tidy upgrade path.

  • Which fields you currently trust from the client.
    This one decides how hard the rest of the project is. If your current saves are client-written and you’re about to make some of that progression server-authoritative, you’re not just moving data, you’re changing who’s allowed to write it. Note every value that falls into that category now.

Decide the Target Owners Before You Migrate the Blob

The easiest implementation is to serialize the old save and put the whole thing into one cloud record. That preserves the existing shape, including every ownership decision you already know is wrong.

Use the migration to decide where each category of state belongs after the cutover:

  • Freeform player state is a natural fit for a general save store, though how a save store handles player data varies by backend (we compare the cloud save and player-data options separately).

  • But numerical values that other systems need to reason about, XP, ranks, an MMR, anything a leaderboard or achievement or matchmaker reads, don’t belong buried in a save blob.

  • Currency belongs in a server-side economy or ledger, not a client-trusted integer.

  • Owned items belong in an inventory system. Purchases and DLC are entitlements, with platform-store rules that earned progression doesn’t have.

  • And some things, graphics settings, key bindings, can honestly just stay on the device.

TARGET ARCHITECTURE One local save, several owners Legacy local save one file, one format Campaign checkpoint Character state XP / level Currency Inventory Achievements Audio settings Graphics settings Cloud Save Freeform player state: campaign, character, settings Statistics Numerical values: XP, ranks, MMR that other systems read Inventory Owned items, loadouts, consumables Economy / ledger Currency, server-authoritative balance Entitlements Purchases and DLC, platform-store rules apply Device-local Graphics and key bindings that can stay on the device The migration is your chance to give each kind of state a proper owner, not to copy one file into the cloud verbatim.


AccelByte Gaming Services (AGS) follows the same split: Cloud Save handles arbitrary player state, while numerical values that need to feed systems such as leaderboards, achievements, or matchmaking fit better in Statistics. Currency and ownership data should not stay buried in a client-trusted save blob.

Pick One Canonical Target and Test Every Legacy Path into It

Write down the target schema before migrating a player. Version it, and make the migration output explicit.

{
  "schemaVersion": 4,
  "playerLevel": 42,
  "campaign": { },
  "characters": [ ],
  "settings": { }
}

For a one-time legacy import, direct transforms from each supported source version into the current canonical format are often easier to test than forcing a returning v1 player through every intermediate schema you ever shipped.

SCHEMA VERSIONING Transform each old schema straight to canonical DO THIS Local v1 Local v2 Local v3 Canonical schema v4 one target, tested Each version has one direct transform. One hop, one thing to test. AVOID THIS v1 v2 v2.5 v3 v4 Every hop is another migration to write, test, and get wrong once. A returning player on v1 should not have to walk four transforms to reach v4.


That is a migration recommendation, not a universal rule. If your game already has a mature, well-tested chain of version-to-version migrations, reusing it may be perfectly reasonable. The requirement is that the full path from every supported historical save to the current representation is exercised against real fixtures.

Solve Identity Before You Touch Persistence

A local save belongs to the device profile that wrote it. A cloud save needs a durable player identity that survives reinstalls, new hardware, and—if you support it—another platform. So the front of the flow is identity, not upload:

Launch build
  -> Authenticate platform user
  -> Resolve or create the game account
  -> Associate the local save with that identity
  -> Begin migration

This gets painful when the player has already created separate game profiles on two platforms. At that point you are no longer importing one local save. You are reconciling two legitimate progression histories. Solve the identity model first, because every later decision depends on knowing who the cloud record belongs to.

Treat the Migration as a Handshake, Not a Write Call

The actual cutover should be a sequence of gates. The server should know whether the player already migrated before the client decides what to import. A safe migration checks identity and migration state before import, then verifies the cloud copy before declaring the player migrated.

THE MIGRATION HANDSHAKE Uploading is one step, not the migration Authenticate player Check migration state server-side, not the device Already migrated? Read cloud state done, no import Discover local save Validate reject / repair / clamp / flag Transform to canonical Cloud already has data? Reconcile * see conflicts guide Import (upload) Verify cloud copy read back and compare Mark complete write server marker Preserve local fallback yes no yes no * Divergent cloud and local progress is a reconciliation problem, not a migration step. HTTP 200 is not "done".


The important detail in that flow is the order: check server-side migration state, inspect local state only if migration has not happened, validate and transform it, check for existing cloud data, import only when the branch is safe, then verify the result before marking the migration complete. A successful API response tells you the write operation completed. It does not prove your transform was semantically correct, that every expected field survived, or that you should now trust the cloud over the disk.

The Rules that Make the Handshake Safe

A handful of rules turn that flow from “probably fine” into “safe to run on real players.”

  • Validate the source.
    A production save corpus contains corrupt files, impossible values, old bugs, edited saves, and records that only look invalid because the old schema is being interpreted incorrectly. Decide what you reject, repair, clamp, flag, or grandfather before rollout. In AGS, Cloud Save custom validation through Extend is one place to enforce game-specific rules at the persistence boundary.

  • Keep the migration marker on the server.
    A device-local migrationComplete flag disappears on reinstall, on a new device, or when platform storage restores an old copy. Store status, migration version, timestamp, and enough source metadata on the player’s backend account to answer one question consistently from every device: has this player already migrated?

  • Make the operation idempotent.
    The client can upload successfully and lose the response. A retry must not duplicate items, currency, rewards, or characters. Persist a stable migration/import ID server-side and make reapplying it a no-op.

  • Verify before you flip authority.
    Read the cloud representation back and validate the fields that matter: schema version, character count, progression markers, inventory counts, or whatever would make a bad migration obvious for your game.

  • Keep a known-good fallback.
    Where the platform storage model allows it, preserve the untouched pre-cloud save for a defined window. If local backup is not practical, preserve an equivalent server-side snapshot or recovery record. Don’t introduce new persistence systems and destroy the only known-good source in the same operation.

Move Authority Deliberately

Cloud-backed does not mean local-free. In many games the useful end state is straightforward: the cloud becomes the durable source of truth, while local storage remains a cache or offline working copy.

SOURCE OF TRUTH
  Migration moves authority, it does not delete local
Before
Local save
the source of truth
if the device dies, the progress dies with it
Cloud: none
migration
After
Cloud / server
the source of truth
authoritative, synchronized across devices
Local save
cache / offline working copy
fast load and offline play, no longer authoritative
Cloud-backed does not mean local-free. Local storage keeps serving fast load and offline play; it just stops being the authority.


The harder case is a state that used to be client-written but should become server-authoritative. If the legacy file contains XP, currency, or inventory, you need a controlled one-time import boundary: validate the legacy state, import it once, then stop accepting direct client overwriting of that domain.

In AGS, Cloud Save can be configured so records are writable by the client or restricted to server writes, and Statistics supports server-side update controls for values that should no longer be client-owned. The exact cutover policy is game-specific, especially if players could edit the old local save. Decide in advance whether legacy values are grandfathered, capped, validated more aggressively, or sent to review.

The deeper design of client versus server authority is its own topic; we cover the tradeoffs in server-authoritative versus client-authoritative architecture.

If the Cloud Already Has Data, Stop and Classify the Case

Suppose the local save says Level 42 and the cloud says Level 7. A blanket rule—newest wins, cloud wins, local wins—will delete valid progress in some real scenario.

First ask why the cloud record exists. It may be a default profile, a migration from another device, a second platform’s progression, or the residue of a partial earlier attempt. So classify the situation instead of ranking timestamps:

  • If Cloud is empty/default: local import may be safe.

  • If Cloud already contains known migrated progression: cloud should normally remain authoritative.

  • If both sides contain meaningful divergent progress: this is a reconciliation problem, not an import rule.

Keep detailed merge and conflict policy in a dedicated conflict-resolution design. The migration layer only needs to recognize when it has reached that branch and avoid overwriting either side blindly.

Old Clients Can Fork a Player After Migration

Two facts about live games make a migration messier than a one-time script: not everyone updates at once, and not everyone is online. Both need a plan.

Start with the old client, because it’s the edge case most likely to bite.

THE EDGE CASE THAT BITES An old client can fork a migrated player Device A (updated to v3) Migrates to cloud local imported once Cloud record: migrated on v3, then advancing normally Device B (still on v2) Keeps writing local knows nothing of the cloud Player upgrades to v3 Conflict two truths for one player The server-side migration marker is what lets the upgraded v3 client notice the cloud already owns this player and refuse to blindly import. time


A player migrates on the new v3 client. Their progression is now in the cloud and advancing normally. Then they launch the old v2 client on a second machine. v2 knows nothing about the migration, so it happily writes new local progression. Later that machine updates to v3, and now the same player has current cloud progress and post-migration local progress that don’t agree. This is the scenario the server-side migration marker exists for: an upgraded client can ask whether the cloud already owns this player and refuse to blindly import, rather than forking them. From there it’s a policy choice, block old versions from connecting, have the new client reject post-migration local imports, prompt the player, or run reconciliation, but you need the marker before any of those are possible.

Offline players make the rollout long-lived too. Some players will migrate on day one; some will come back after six months and skip several client versions. Treat the importer as a compatibility path with an owner, monitoring, and an explicit removal policy—not as code you can forget after launch week.

Build a Save Graveyard, Then Roll out Like Infrastructure

Do not validate this migration with three clean QA saves. Keep a “save graveyard”: current saves, every supported historical schema, empty files, maxed characters, many-slot profiles, weird names, truncated files, bad checksums, impossible values, and known real-world edge cases.

Then add behavioral fixtures: crash after upload, lost response, retry, reinstall, second device, old client after migration, offline session, and cloud data already present. Run corpus against every migration build.

When the code is ready, stage the migration behind server-side rollout controls. The exact percentages are not sacred; the point is to expose progressively larger cohorts and stop automatically or operationally when the health signals say the migration is not safe.

Here’s an example rollout that moves through progressively larger cohorts with a health gate between each step and the ability to stop without shipping a new client.

ROLL OUT LIKE INFRASTRUCTURE
  Stage the rollout, gate every step
Each circle is a health gate: hold, do not advance, until the numbers are clean.
Internal
QA
1%
5%
25%
50%
100%
Halt the rollout if
validation failures climb past your threshold (say 0.5%)
unexpected progression resets start appearing
duplicate-entitlement rate crosses baseline
cloud read-back mismatches exceed your limit
support volume spikes above normal
Put it behind a flag
Server-side rollout controls, not a client build gate.
So you can pause migration the moment a dashboard turns red, without shipping and certifying a new client.


Useful health signals include migration success rate, validation failures, cloud readback mismatches, conflict rate, progression-reset reports, duplicate grants or entitlements, and support volume. Define the stop conditions before the first real cohort. A threshold invented during the incident is not a threshold.

Make Every Migration Traceable, and Give Support the Tools

When a migration goes wrong, the question is always “what happened to this one player,” and you can only answer it if you instrumented for it. Emit a migration event with enough metadata to reconstruct the journey:

playerId, platform, clientVersion,
sourceSaveVersion, targetSchemaVersion, migrationId, sourceHash,
migrationStartedAt, transformationResult, validationResult,
cloudWriteResult, cloudVerificationResult, migrationCompletedAt

Capture the metadata, not the raw save contents (you don’t want sensitive player data sitting in your logs), but enough to trace exactly where a given migration succeeded, failed, or diverged. Support should be able to see migration status, source/target versions, previous known-good state or recovery snapshot, and the audit trail, with a defined restore-or-escalate path.

A migration is not done when the upload works. It is done when you can explain and repair the player for whom it did not.

Also separate migration rollback from player-state repair. If migration succeeds Monday, the player earns ten levels Tuesday, and you discover an inventory transform bug Wednesday, restoring Monday’s entire save destroys valid Tuesday progress. In practice you often need a targeted repair or compensation path, not a full rollback.

What “Ready” Actually Means

None of this is exotic. It’s identity, idempotency, versioning, validation, telemetry, and a recovery story, applied to the awkward fact that every one of your players is holding a different piece of production data you can’t see until they launch. The teams that migrate cleanly are the ones that decided the failure modes before players found them. Before you write migration code, you should be able to answer these. If you can’t, that’s your to-do list, not a sign you’re behind:

  • Do you know every save format still in the wild, and do you have real fixtures for each?

  • Can every local save be tied to one durable game-level player identity?

  • Which data goes to a save store, which moves into stats, inventory, economy, or entitlements, and which system is authoritative afterward?

  • Can every supported source schema transform directly into the target, and do you validate the result?

  • Is the migration idempotent, backed by a server-side marker, and does it verify the cloud copy before flipping authority?

  • Do you preserve a known-good source for a defined window?

  • What happens when the cloud already holds meaningful data, and when an old client produces new local progress after migration?

  • Can migration be enabled by cohort and halted without a new build, and can support inspect and repair a single player?

If those answers exist and are written down, you’re migrating. If they are not, the implementation is still relying on the happy path.

FAQ

On a small game with a clean format and a mandatory-online build, a one-shot first-launch upload can work. On a live game it usually loses progress, because the same player may already have a cloud record from a second device, the local save may be corrupt or written by an old schema, and a lost network response can trigger a retry that duplicates everything. Upload is one step in a longer handshake: authenticate, check server-side migration state, validate, transform, check for existing cloud data, upload, verify, and only then mark it done.

Don't apply a blanket rule like newest-wins or cloud-always-wins; each silently deletes someone's progress in some case. Classify instead: if the cloud record is empty or default, importing the local save is usually safe; if the cloud already holds migrated progression, keep the cloud; if both hold meaningful, divergent progress, that's a reconciliation problem with its own rules, not something a timestamp comparison should decide.

Not in the same operation. Keep the original as an untouched backup (for example savegame.precloud.bak) for a defined safety window, because transform bugs, serialization bugs, corrupt writes, and dropped fields often surface days later, and the untouched original is what lets you recover. Deleting your only known-good copy at the same moment you introduce a new persistence system removes your fallback exactly when you're most likely to need it.

 This is what the server-side migration marker protects against. After a player migrates on the new client, an old client on another device can create fresh local progress and, on upgrade, collide with the cloud. Because the marker lives on the server, an upgraded client can check whether the cloud already owns the player and refuse to blindly import, and from there you can block old versions, prompt the player, or reconcile, but you need the marker first.

Treat it like a risky backend change, not a content drop. Stage it (internal, QA, 1%, 5%, 25%, 50%, 100%) with a health gate between cohorts, watch migration success rate, validation and transform failures, cloud readback mismatches, conflict rate, and support volume, and decide your abort thresholds before you're watching a live dashboard. Put the rollout behind a server-side flag so you can halt it without shipping a new client.

Freeform player state (campaign progress, character layout, settings) fits a general save store like Cloud Save. Numerical values that other systems read (XP, ranks, MMR for leaderboards, achievements, or matchmaking) belong in a statistics service so they're readable outside the save, currency belongs in a server-side economy or ledger, and purchases belong in entitlements with their platform-store rules. AccelByte's own Cloud Save guidance makes this split explicit and points numerical values at Statistics.

Table of Contents

Bring your first player online today.

Get started for free, and scale as your game grows.