• Home
  • Blog
  • How to Sync Player Progress Across Platforms

How to Sync Player Progress Across Platforms

A player finishes a session on PC at Level 24 and quits. That evening they sit down at their PlayStation and launch the same game. The happy path looks trivial: authenticate the player, pull down Level 24, drop them back in where they left off.

Now change one detail at a time and watch it stop being trivial.

The PC was offline for that last session, so the level the cloud knows about isn't the level the player actually reached. Or the PlayStation already has an older Level 20 profile from a weekend they played on a friend's couch. Or both machines got used before either one finished uploading. Or the PC build shipped a new progression field last patch that the console build, still waiting on cert, has never seen. Each of these is a normal thing that happens to real players, and each one turns "sync the latest save" into a question with no obvious answer.

That's the whole point. Cross-platform progress sync is not "upload the save on one device and download it on another." It's a state-reconciliation problem sitting on top of four decisions you have to make on purpose: who the player is across platforms, which system owns each piece of their progression, which version of the data each device last saw, and what to do when two legitimate histories disagree. Get those right and the sync is boring. Get them wrong and you find out in production, usually as a support ticket that reads "the game deleted my character."

The question this piece answers is the one an engineer actually asks: a player just closed the game on one device and opened it on another, so what has to be true before you can safely put them back in?

Where This Piece Sits

This is the deep dive, not the overview. We have a separate piece on the broader shape of the problem, designing cross-platform progression, which is about what makes a player's whole game state portable across platforms at all: identity, cloud save, stats, inventory, achievements, entitlements, and commerce. Read that one for the architecture and the account-linking politics.

This piece assumes you've already decided the player is one thing above the platforms, and goes down a level into the mechanism: the synchronization state machine and the failure cases around it. It stays deliberately light on commerce and entitlements, which the broader piece owns, and heavy on the parts that actually eat player data: revisions, conflict detection, offline authority, concurrency, idempotency, and recovery.

Three Ideas to Hold Before Anything Else

Almost every sync bug traces back to skipping one of three concepts. Get these straight and most of the design falls out of them.

  • Identity.
    Before you can synchronize anything, the backend has to answer one question: are these two platform users the same game player? Steam, Xbox, PlayStation, Epic, and Nintendo each identify people inside their own walls. None of them knows about the others. So progression can't hang off a platform account; it has to hang off a game-level identity that those platform accounts link to.

  • Authority.
    For every piece of progression, exactly one system is allowed to decide the current value. Graphics settings? The client can own that. A campaign checkpoint? Probably cloud-save state. XP, ranked rating, inventory, currency? The server, every time. When you can't name the single owner of a value, you don't have a sync design yet, you have a race.

  • Revision.
    The system needs to know what state a device last synchronized against. Without some notion of version or revision, all you know is "the device has data and the cloud has data." You have no idea how they relate. That gap is exactly how teams end up reaching for timestamps and last-write-wins, which is where the data loss starts.

Hold those three. Everything below is a consequence of them.

Before You Sync Progression, Resolve the Player

The first instinct is to ask where to store the save. That's the second question. The first is whose progression this even is.

A player might hold a Steam account, an Xbox account, and a PSN account. Those are three platform identities. Your game has to treat them as three doors into one player, with progression attached to the player behind the doors, not to any single door. Attach it to the door and "log in on a different platform" quietly means "become a different person with none of your stuff."

Here's the failure case that surprises teams, because it isn't a sync failure at all. A player puts 15 hours on Steam. Later, independently, they put ten hours in on PlayStation without linking anything. Then they notice the "link your account" prompt and tap it, expecting their stuff to merge. You don't have a synchronization problem here. You have two separate player profiles, both real, both with earned progress, and you now have to reconcile them before any normal sync can begin. That reconciliation (keep one, keep the other, merge, or hold both as switchable profiles) is a policy decision with no free option, and it's covered in depth in the broader progression piece. The thing to internalize here is that it has to happen before the sync machine ever runs, and that it's much cheaper to prevent than to untangle.

This is also why the account model matters more than it looks. For example, AccelByte Gaming Services (AGS) creates a headless game-level account the first time a player signs in with a platform identity, and linking a second platform to that same account is what lets their progression continue on the new platform. Two histories that were never one are genuinely hard to combine, whichever backend you're on.

Player Progress Isn't One Blob

The second instinct worth breaking is treating "progress" as a single thing you sync. A real game's progression is a pile of different kinds of state with different rules, and jamming them into one save file makes every later problem harder than it needed to be.

What player progress actually contains
 Different kinds of state, different homes
State Example Where it usually belongs
Save / checkpoint state quest step, character position, world flags freeform save store (Cloud Save style)
Numerical progression XP, level, ranked rating a stats / progression service
Inventory weapons, cosmetics, materials an inventory backend
Unlocks abilities, chapters, modes stats or a domain service
Achievements completed milestones an achievements service
Settings audio, controls, accessibility freeform save store (client-writable)
Loadouts selected items and configuration save store or inventory, depending
Currency soft and premium balances a wallet / ledger, server-owned
Entitlements owned DLC and items an entitlements service (out of scope here)
Split state by kind before you sync it.


The reason to split it isn't tidiness. It's that these categories have different authority needs, different conflict behavior, and different consequences when they're wrong. Freeform state like a campaign checkpoint or a settings blob is a natural fit for a save-record store: it's game-specific, it changes as a lump, and nobody else needs to query the individual fields. Numerical and economic state (XP, currency, inventory, rank) wants to live in a domain service instead, because those values need server authority, atomic updates, querying, analytics, and anti-cheat validation, none of which you get from a JSON blob in a save file.

Platforms that separate these make the split explicit. AccelByte, again as an example, points you at Cloud Save for arbitrary player data (settings, character attributes, save-game blobs, in JSON or binary) and specifically tells you not to put pure numerical values there: if a value needs to feed leaderboards, matchmaking, or achievements, it belongs in the Statistics service instead. That's the same file-versus-domain boundary, drawn at the product level. The storage side of that decision is its own topic, covered in platform-native versus game-backend cloud saves.

Give Every Domain Exactly One Authoritative Owner

Once the state is split by kind, give each kind a single source of truth. Synchronization gets dramatically simpler when every value has one owner, because "sync" stops meaning "reconcile two peers" and starts meaning "fetch from the owner."

One owner per value
 Who owns each value, and who may cache it
One source of truth per value. Everything else is a cache.
State Authoritative owner Device cache allowed?
Character checkpoint save store (Cloud Save) yes
XP / level stats / progression service yes, as a display cache
Inventory inventory backend yes, as a display cache
Premium currency wallet / ledger display only, never authoritative
Settings save store (client-writable) yes
Ranked rating server display only, never authoritative
If two systems own the same value, they will disagree.


The rule underneath the table: if two systems both think they own XP, they will eventually disagree, and then you're writing reconciliation code for a problem you created. One owner per value means there's never a question of which copy is right, only a question of getting the current value from the owner to wherever it's needed.

AccelByte makes this boundary a first-class setting rather than a convention, which is a useful pattern to copy regardless of what you build on. A Cloud Save record's write permission is either SERVER, where players can read the record but only the server can write it, or CLIENT, where players can modify it (that's the default). The Statistics service has the same idea: set a stat's update permission to the server and it enables server-side validation that stops the client from writing the value directly. That's the trust boundary drawn at the data layer. Low-stakes, offline-friendly state stays client-writable so it's cheap; valuable state is server-writable so a modified client can read it but never forge it.

What the Normal Sync Flow Actually Looks Like

Here's the whole handoff as a sequence, the thing you're really building. A player launches on platform B. Authenticate the platform identity. Resolve the game-level player behind it. Read the last revision this device synced against. Fetch the current authoritative state from the owner. Compare local against remote. If there's no conflict, apply to the remote state. If there is one, run your conflict policy. Initialize game state, let the player play, persist changes locally as needed, sync the authoritative changes back, and store the new revision. The next launch starts the loop again.

The normal flowBefore the player is back in the gameOne handoff from platform A to platform B, step by stepAuthenticate platformthe platform token is the loginResolve game-level playerone player above the platformsRead last-synced revisionwhat did this device last see?Fetch authoritative statethe current source of truthCompare local vs remoteConflict?no conflict: apply remote as-is · conflict: run per-domain policyInitialize game statePlay → changes occurPersist + sync writeslocal cache and authoritativeStore new revisioncloud advances to rev N+1next launch restarts hereThe lifecycle is a loop: every session ends by writing the next revision.


2 steps in that loop carry most of the weight and are the ones teams skip: "read the last-synced revision" and "store the new revision." Drop those and you've thrown away the only information that tells you how two saves relate. Keep them and most of the hard cases below become detectable instead of silent.

Don't Build Your Sync Model on Timestamps

The naive version of "which save is newer" is "whichever has the later timestamp wins." It looks reasonable and it's wrong often enough to lose real player data.

Device clocks are not trustworthy. They drift, they get set by hand, they're unsynced across a player's machines, they suspend and resume, they run offline for hours. But the deeper problem isn't clock accuracy. Even a perfectly accurate timestamp only tells you when a write happened, not how the two states relate. A save from 21:04 and a save from 21:02 might be the same lineage one step apart, or two branches that diverged an hour ago, and the clock can't tell you which.

What tells you is a server-issued revision: a monotonically increasing version number, an ETag, a write lock, a sync token, whatever you want to call it. The device effectively says "I'm modifying revision 41." If the cloud is already at revision 44, you instantly know this isn't an ordinary write, it's a stale one, and you can reject it or reconcile it instead of letting it clobber a newer state.

Why last-write-wins lies
 A wall-clock time cannot tell you how two saves relate
The same handoff, decided two ways
 
Timestamp wins
Console
saved 21:04 (local clock)
Steam Deck
saved 21:02 (local clock)
Which one is newer?
A later clock does not mean newer state.
Device clocks are unreliable. They can be:
wrong or drifting
set by hand
unsynced across devices
suspended / resumed
offline for hours
 
Server revision
Console
editing rev 41
Cloud
already at rev 44
41 < 44 → this write is stale
Reject it, or reconcile as a branch.
A revision says what state a write was based on, not just when it happened. That is the whole difference: a monotonic revision (or ETag) makes a stale write obvious. A timestamp never can.
Track revisions, not timestamps.


This is one place where some platforms genuinely lead, and it's worth being honest about. PlayFab and Nakama both ship real version-based concurrency control for saves, which is exactly the right primitive for this. AccelByte's Cloud Save takes a different route: it leans on server authority (server-only records that the client can't write) to protect integrity, and it added a distributed lock so concurrent processes don't corrupt the same record, but it doesn't expose the same client-facing, version-based conflict-resolution primitive that PlayFab and Nakama document. If fine-grained, client-driven conflict resolution is central to your game, those two lead, and we say so in our own
cloud saves and player data comparison. The general lesson is: attach a revision to every write, and check it before you accept one.

When Two Histories Are Both Legitimate

The nastiest case isn't stale data. It's two writes that are both correct.

Device A downloads revision 10, goes offline, and plays up to Level 16. Device B downloads the same revision 10, stays online, plays up to Level 20, and uploads revision 11. Then Device A reconnects. It now holds legitimate progress derived from revision 10. The cloud holds legitimate progress also derived from revision 10. Neither is a stale copy of the other. Level 16 is not an old version of Level 20; they're siblings.

Two legitimate historiesWhen both devices are right, you have a branch to reconcileRevision 10both devices start hereDevice A · offlinestarts from rev 10plays to Level 16never uploads (no connection)Device B · onlinestarts from rev 10plays to Level 20uploads → Revision 11Revision 11Device A reconnectsBoth histories are real and both descend from rev 10. Level 16 is not a stale copy of Level 20.This is a branch to reconcile, not an old file to overwrite. Last-write-wins deletes one of them.A branch has no single "latest." It has a policy.


So what should the game do? That depends entirely on what kind of state diverged, which is the next decision.

Conflict Resolution, per Domain, Not per Profile

There are a handful of standard strategies, and the mistake is picking one for the whole player. Pick per kind of state instead.

  • Last write wins.
    Dead simple, and it silently erases legitimate progress. It's fine only where the state is low value, updates don't overlap meaningfully, and losing one version is genuinely acceptable, which is a narrow set: audio settings, maybe. Not XP.

  • Highest progression wins.
    "Keep the higher level" sounds fair until you realize one scalar can't describe "better progress." The lower-level branch might hold a rarer item, a quest the other branch never touched, or a different story choice. Collapsing that to a single number throws real state away.

  • Field-level merge.
    Merge independent values: take the max XP, the union of achievements, the latest settings. This is great for values that don't depend on each other and dangerous for values that do. Merge "quest step: 8" from one branch with "boss: alive" from the other and you can produce a save state the game's own rules say is impossible.

  • Domain-specific merge.
    This is usually the one that holds up. Different rules for different state: XP takes the max (or comes from the server), achievements take the union, local settings take the latest device, the campaign save picks one coherent branch rather than blending, premium currency comes only from the server ledger. It's more work to specify, and it's the version that doesn't corrupt anyone.

  • The player chooses.
    When automatic merging risks corrupting meaning, ask. Show both plainly, "PC, Level 16, last played Aug 18" against "PlayStation, Level 20, last played Aug 20," and let the player pick. This is what the Steam Cloud conflict dialog does, and what PlayFab does with its keep-local or keep-cloud prompt. PlayFab does one more thing worth copying: it preserves the branch you didn't choose so it can be rolled back later. Choosing isn't the same as deleting.

The single most useful rule in this whole piece: don't design one conflict strategy for the entire player profile. Design it per data domain, because the right answer for currency and the right answer for graphics settings have nothing in common.

Your Save Boundaries Are Your Conflict Boundaries

How you structure the save decides how often two devices collide in the first place. This one is subtle and it pays off.

If everything lives in one player_save.json, then any divergent change anywhere produces a conflict involving the entire state. Two devices touching completely unrelated things still collide, because there's only one unit to collide over. Split the save into independent domains instead, and independent changes stop fighting.

Player/
├── Campaign/
├── Settings/
├── Loadouts/
└── Characters/
    ├── Character_A/
    └── Character_B/

Now a settings change on one device and a campaign change on another don't conflict at all, because they're different units.

Save boundaries are conflict boundaries
 How you split the save decides how often two devices collide
 
One save blob
player_save.json
campaign checkpoint
settings
XP + level
loadouts
inventory
one file, one atomic unit
Change anything → conflict on everything
Two devices editing unrelated fields still collide.
 
Domain-scoped
Player/ ├── Campaign/ ├── Settings/ ├── Loadouts/ └── Characters/ (A, B)
Edit different domains → no conflict
Independent units sync on their own; only the same unit changed on both sides conflicts.
Too coarse = needless conflicts. Too fine = risk of assembling incompatible pieces.
PlayFab models this literally: each root-level save folder is one atomic unit for conflict detection.


This isn't theoretical. PlayFab's Game Saves models it directly: it treats each root-level save folder as an atomic unit for conflict detection, so a conflict only fires when both the local device and the cloud changed something inside the same unit. Edit different units and both sync cleanly with no prompt. It's a clean, real implementation of exactly this idea, and the tradeoff cuts both ways: too coarse and you generate conflicts that didn't need to exist, too fine and you risk stitching together pieces of state that don't belong to the same coherent moment. Pick the boundary that matches how your game's state actually diverges.

Offline Play Needs Its Own Authority Policy

Offline is not "online sync, but later." Treating it that way is how a game becomes unplayable on a train or silently accepts progress it should have rejected. For each domain, answer one question up front: can this legitimately change while the player is disconnected?

Some things obviously can (a campaign checkpoint, local settings, a cosmetic loadout). Some things obviously can't (ranked rating, premium currency, a server-issued reward, an online inventory transaction). And some are genuinely game-dependent (XP earned in an offline campaign might be fine, or might be an exploit vector). The point is to decide, not to inherit a default.

Then the reconnect path is its own small pipeline, and it's not the same as a normal sync. Authenticate. Discover the remote revision. Detect whether you've diverged. Validate the offline state. Reconcile the domains that were allowed to change offline, and reject or re-fetch the ones that weren't. Write a new revision.

Reconnecting after offline playOffline is not "online sync, but later"AuthenticateDiscover remote revisionDetect divergenceValidate offline stateReconcile permitted (client-safe) domainscampaign checkpointlocal settings and accessibilitycosmetic loadoutReject / re-fetch server-authoritativeXP and ranked ratingpremium currency and walletonline inventory transactionsWrite new revisionValidate what happened offline; never trust the client on valuable state.


PlayFab is a good reference for how this feels in practice: its save system keeps working offline, and cloud operations simply return a "disconnected from cloud" error until you're back, so the game can carry on locally and reconcile on reconnect instead of blocking. Whatever you build, the offline policy is a design artifact you write down per domain, not a behavior you discover after launch.

Server Authority Changes What "Sync" Even Means

For anything competitive or economic, the honest answer to "which device owns the truth" is none of them. The server does, and that quietly makes the whole problem smaller.

If XP is server-authoritative, Device A doesn't really own XP = 12,540. It owns a cached view of a value the server owns. Moving to Device B isn't "upload A's XP, resolve a conflict, download to B." It's just "authenticate, fetch XP." There's nothing to reconcile because the devices were never sources of truth to begin with; they're input devices for the same server. That's fundamentally simpler and safer, and it's the direction to push every valuable value.

The general principle: the more valuable a piece of progression is, the less you should treat the client's copy as authoritative. XP, currency, item grants, ranked rating, competitive achievements. If the client can write them, a modified client can forge them, and cross-platform makes that worse because now there are more client types to exploit and more sync paths to race.

AccelByte's Statistics service is a compact example of the mechanics. You can mark a stat server-authoritative so it can only be updated from your dedicated server (handy for writing ELO or MMR when a match ends), and there's a server-side validation option that prevents a stat from ever decreasing, which is exactly what you want for something like lifetime XP that should only climb. That's authority enforced at the service, not hoped for in the client. It isn't free, of course: server authority needs connectivity for anything authoritative, it needs retry handling and idempotency (more on that next), and it adds a round trip to actions that used to be local. That tradeoff is the real subject of server-authoritative versus client-authoritative architecture; for sync, the takeaway is to sort your state into "cheap, safe for the client to own" and "valuable, must be server-owned" early, because moving a currency system to server authority after launch is its own migration.

A Reconnect Can Grant the Reward Twice

Here's a backend problem that cross-platform sync makes worse, not because sync causes it but because device handoffs and reconnects are exactly the conditions that trigger it.

A player finishes a mission. The client sends "grant 500 XP." The connection drops before the acknowledgement comes back, so the client, doing the sensible thing, retries. Without protection, the server applies the grant twice and the player is now 500 XP richer than they should be. Do that with currency instead of XP and you've either minted money or, on the failure path, charged someone twice.

The fix is idempotency. Attach a stable identifier to the operation (an idempotency key, a transaction ID, an event ID) and keep a record of which ones you've processed. When the retry arrives carrying the same key, the server recognizes it, says "event 82F7 has already been applied," and returns the original result instead of doing the work again.

A reconnect can grant the reward twice
 The same reward, granted once or granted twice
 
No idempotency key
Mission complete
client → "grant 500 XP"
ack lost, client retries
server grants again
XP = 1000 (duplicated)
 
With idempotency key
Mission complete
client → grant 500 XP [event 82F7]
ack lost, client retries [82F7]
server: 82F7 already applied
XP = 500 (correct)
Idempotency keys make a retry safe: apply once, ignore the rest.


This matters most for anything with lasting value: currency, inventory grants, rewards, achievements, and progression events. It's the same mechanism payment systems use to keep a retried charge from billing twice, and it belongs on any progression write that a flaky connection or a device handoff might cause to run more than once.

An Old Build Can Quietly Delete Newer Progress

One more that ambushes technical directors, usually during certification. Console cert and staggered storefront rollouts mean your platforms will not all be on the same build at the same time. Steam might be on v1.8 while PlayStation is still finishing cert on v1.7, and both are reading and writing the same shared progression data.

Say v1.5 introduced a "prestigeLevel": 3 field and v1.4 has never heard of it. The player moves from the v1.5 PC to the v1.4 console. The console loads the save, and later, on its own next write, saves the record back out minus the field it didn't understand. prestigeLevel is gone. The old client did nothing wrong by its own logic, it just deleted a newer system's data because it wrote back a shape it didn't know was incomplete.

That's why cross-platform sync and schema versioning are the same conversation. The defenses are known: make schema changes additive so new fields don't break old readers, tag records with a version, teach clients to preserve unknown fields instead of dropping them on write, keep migration functions that upgrade old records forward, and define a minimum-compatible version below which a client is refused rather than allowed to corrupt data. Microsoft flags this exact staggered-version risk in its own save guidance, so it's not a niche worry. It's also a whole discipline of its own, deeper than this piece, and worth its own read (we have a dedicated schema-evolution piece planned; link it here when it's live). The one thing to carry: an old client silently destroying newer data is one of the worst bugs in this space precisely because the old client is behaving exactly as written.

The First Sync on a New Platform Is Special

When a player launches on a new platform for the very first time, don't just run the normal loop. There's an ordering that keeps you from destroying progress: authenticate the platform account, determine whether it's already linked, locate the shared player identity, detect any existing local or platform progression, fetch the current authoritative state, decide whether reconciliation is required, explain the result to the player before anything destructive happens, establish the local cache, and only then begin normal sync.

The rule that matters most: never silently overwrite preexisting progression during a first link or first sync. If the player has two profiles, show them what they're choosing between. Google's Play Games save guidance makes the same point for the same reason, recommending you prompt rather than auto-overwrite when a guest account with local progress links to an account that already has cloud progress. A silent overwrite here is the difference between a player who links happily and a player who watches hours disappear and never trusts the feature again.

When Sync Fails, Decide the UX on Purpose

No sync is perfect, so design the failure states as deliberately as the success path. There are four you'll hit.

Backend unavailable at startup:

Do you block play, allow an offline mode, run from a cached state, or allow read-only gameplay? Upload fails on shutdown: do you queue a retry, sync in the background, or retain the dirty local revision until you can? Download fails on the second device: use the stale local cache, block progression, or retry? Conflict can't be resolved automatically: fall back to a player choice, or escalate to support?

Whatever you pick, the game has to clearly distinguish "sync pending" from "sync complete," because players make decisions based on that trust. Baldur's Gate 3 is a useful, sympathetic example of how hard this is even for a top studio. Its cross-platform save is a separate layer from Steam Cloud, and players regularly confuse the two. More to the point, when its cross-save upload got stuck in a loop, the game couldn't save or load, and anyone who kept playing past the stuck point lost everything after it, exactly the "sync pending versus complete" trap. At launch the cross-save backend also buckled under the crowd, with enormous numbers of players uploading saves at once. None of that means the design was careless; it means this is genuinely hard, and the failure of UX is where "hard" becomes visible to players. The save loop itself can help here too: a well-worn trick is double-buffering, alternating between two on-disk save files so there's always a known-good fallback if a write dies mid-way. That's independent of cloud saves and it's cheap insurance against corruption during an abnormal exit.

Recovery and Rollback Are Part of the Design

Assume something will eventually go wrong: a release ships a bad save transformation, a player picks the wrong branch on the conflict screen, a malformed client writes invalid progression. A reliable sync design plans for the cleanup instead of treating it as an emergency.

You need historical revisions, a rollback path, an audit trail, support tooling that can look a player up, and a way to make a manual correction. The one question that tells you whether you've built this: can support see what a given player's previous valid state was? PlayFab, again, is a good reference for the shape of it, preserving the discarded branch on a conflict and supporting version rollback specifically to recover players from a bad state. On the AccelByte side, the same instinct shows up in different mechanics: server-only records and a distributed lock protect integrity at write time, and AccelByte Extend, the hosted custom-logic layer, is a natural place to put game-specific validation that runs before a progression write is accepted, so a bad write gets rejected at the boundary instead of becoming a recovery ticket later. Wherever the tools live, the discipline is the same: the previous good state has to be recoverable, and support has to be able to reach it without an engineering escalation every time.

Watch the Sync System, Not Just the 200s

A successful API response tells you a write happened. It doesn't tell you whether your sync system is healthy. Watch the aggregate, because the failures you can't see are the ones that turn into refund requests.

The metrics that earn their place: sync attempts, success rate, and upload and download failure rates; conflicts per thousand syncs, broken down by type and by the platforms involved, plus how often players pick local versus cloud and how often automatic merges succeed; stale-write rejection rate and average revision lag; account-link failures, duplicate-profile detection, and reconciliation rate; and on the human side, rollback frequency, progression support-ticket volume, and manual-correction rate.

One metric is worth calling out specifically: conflict rate by platform pair. PC and Steam Deck (same player, same ecosystem, easy to have both active) will look nothing like Xbox and PlayStation. That single cut tells you where your real sync pressure is coming from, and it's usually not where you'd guess.

The Whole Thing on One Page

Put it together and the architecture is legible. Two platforms, each with a local cache, both flowing through platform authentication into one game-level identity. That identity splits into two persistence concerns: the sync/cloud state (campaign, settings, save revisions) and the authoritative services (XP, inventory, economy, ranking). Both feed a version/revision check, then conflict detection, which resolves to one of three outcomes (accept the remote, merge per domain, or ask the player) and hands a reconciled result back to the device. And running across every layer, not bolted on at the end: offline policy, schema versions, idempotency, rollback, and telemetry.

A reference sync architectureOne page: identity in, a reconciled revision outPlatform Alocal cachePlatform Blocal cachePlatform authenticationGame-level identitySync / cloud statecampaign, settings, save revisionsAuthoritative servicesXP, inventory, economy, rankingVersion / revisionConflict detectionaccept remotemerge per domainplayer choiceBack into the gameACROSS EVERY LAYER:offline policy · schema versions · idempotency · rollback · telemetryIdentity in at the top; a reconciled revision back to the device at the bottom.

The One Table Your Architecture Has to Fill In

If you take one artifact from this, take this one. For every progression domain in your game, you should be able to fill in every column. If you can't, that blank is a design decision you haven't made yet, and a blank here is where the support tickets come from.

The table your architecture has to fill in
 One row per domain. No blank cells.
Domain Authority Offline write? Conflict policy Revision strategy
Audio / accessibility settings client / save store yes latest device per-record version
Campaign checkpoint save store yes pick one coherent branch, or player choice monotonic save revision
Achievements server limited union, server validates server-owned
XP / level server maybe (offline events, validated) max, server validates server-owned
Ranked rating server no server wins server-owned
Inventory server no server wins server-owned
Premium currency wallet / ledger no ledger and transaction history only server-owned
Character cosmetics hybrid usually merge where safe per-record version
A blank cell is a decision you have not made yet.


Fill that in for your actual domains before you write the sync code, not after. The teams that ship cross-progression cleanly did this table first.

The Traps, in One Place

The failures in this space are a small set, and they show up over and over.

  • Using a platform account as the player identity, which breaks the moment the player switches.

  • Treating all progress as one save blob, which turns every unrelated change into a conflict.

  • Last-write-wins everywhere, which silently deletes legitimate progress.

  • Letting the client authoritatively write a valuable progression, which hands cheaters a write API.

  • Skipping revision checks, which makes a stale write indistinguishable from a real one.

  • Supporting offline mode by accident, so reconnect behavior exposes a design you never made.

  • Assuming only one device is ever active. Assuming every platform runs the same build.

  • Retrying rewards without idempotency, which duplicates progression.

  • And designing sync with no recovery path.

Design the Reconciliation Before the Tickets

Here's the reframe to leave with. The real question was never "how do I sync the save." It's which of these failure modes you design for on purpose, and which one a player finds for you at 2am during launch week. Identity, one owner per domain, a revision on every write, and a conflict policy chosen per kind of state: that's the whole machine. Everything else is what happens when you skip one of them.

The teams that do this well aren't the ones with the fanciest sync. They're the ones who decided what "sync pending" versus "sync complete" means, and what to do with a branch, before a player ever produced one. Decide the reconciliation policy before the tickets, not after.

Build cross-platform sync on one player identity

You've seen that the sync isn't the hard part; the identity, ownership, and revision decisions underneath it are. If you'd rather adopt those foundations than build them from scratch during launch week, AccelByte Gaming Services gives you the game-level account model, Cloud Save for freeform state with an explicit client-versus-server write boundary, Statistics for server-authoritative numerical progression, and Extend for the custom validation that runs before a write is accepted, as one platform where identity and progression were designed to fit together. It's free on the public cloud until your game hits 30 CCU, with full access to the platform and every backend feature, the same infrastructure behind shipped titles. Start building your real save-and-load flow against it now, or talk to us to pressure-test your conflict and offline policy before you commit to it.

You're almost signed up!

Verify your account by following the instructions sent to .

If you still haven't received an email, please check your spam folder.

Please provide a valid name.

Please provide a valid email address.

Please provide a valid studio name.

You must agree to the policy to continue.

Talk to us

FAQ

Attach progression to a game-level identity that platform accounts (Steam, Xbox, PlayStation) link to, not to any single platform. On each launch: authenticate the platform, resolve the game-level player, read the revision the device last synced against, fetch the authoritative state, compare, apply or reconcile, then store a new revision. The sync is a loop that reads and writes a version each session, not a one-time upload and download.

Because it silently deletes legitimate progress. Device clocks are unreliable (they drift, get set by hand, run offline), and even an accurate timestamp only tells you when a write happened, not how two saves relate. Two saves can be one step apart on the same lineage or two diverged branches, and a clock can't tell the difference. A server-issued revision can, which is why real sync systems track versions instead of timestamps.

A conflict is when the same state changed on two devices since they last synced, and both changes are legitimate. The right resolution depends on the kind of state: last-write-wins only for low-value data, max or union for independent values like XP and achievements, "pick one coherent branch" or a player choice for campaign saves, and server-only for currency and rank. The key rule is to choose a policy per data domain, not one policy for the whole player profile.

No. Save files are good for freeform, game-specific state (checkpoints, quest flags, settings). Numerical and economic values like XP, currency, inventory, and rank want a domain service instead, because they need server authority, atomic updates, querying, and anti-cheat validation that a JSON blob can't provide. Platforms that separate these (a cloud-save store for blobs, a statistics or wallet service for values) make the split explicit for exactly this reason.

Decide per domain whether it can legitimately change offline: campaign checkpoints and settings usually can, ranked rating and premium currency can't. On reconnect, authenticate, discover the remote revision, detect divergence, validate the offline state, reconcile the domains that were allowed to change, and reject or re-fetch the ones that weren't, then write a new revision. Offline is a policy you write down per domain, not "online sync but later."

Idempotency. Give each reward or currency operation a stable identifier (an idempotency key or event ID) and record which ones you've processed. When a dropped acknowledgement makes the client retry, the server recognizes the repeated ID, returns the original result, and doesn't apply the grant again. This matters most for currency, inventory, rewards, and achievements, where a duplicate is either a dupe or a double charge.

Certification and staggered rollouts mean your platforms won't all be on the same build, and both may touch the same shared progression data. The dangerous case is an old client rewriting a save and dropping fields it doesn't understand, deleting newer data. Defend with additive schema changes, version tags on records, unknown-field preservation, migration functions, and a minimum-compatible version below which a client is refused rather than allowed to corrupt data.

In practice you need a game-level account. Platform accounts are authentication, not ownership, and none of them knows about the others, so syncing platform saves directly has no correct answer when they disagree. A shared game identity that platform accounts link to is what lets progression follow the player. Without it, "log in on another platform" quietly means "be a different player with none of your progress."

Table of Contents

Bring your first player online today.

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