Fill in the Backend Service fields

A backend service stores your save inside an envelope of its own, and the Backend Service fields of the Server (HTTP) manager describe that envelope. The address and body templates carry the following placeholders.

PlaceholderWhat it stands for
{url}The manager's own address, from Release Url.
{slot}The slot being read or written, which is usually the player id.
{id}The persistent asset's own id, which is what tells two saves of one player apart.
{token}Whatever your code put in the manager's Authorization, or in HttpPersistenceManager.DefaultAuthorization.
{payload}The save itself. Bodies only.

Response Payload Path is the dotted route to your save inside the service's answer, with numbers for positions in an array. Leave the load and clear bodies empty for a REST backend, and fill them in for an RPC, the way PlayFab and Nakama need.

Sign-in is your game's job. As soon as it returns, set HttpPersistenceManager.DefaultAuthorization to the session token, which is what {token} stands for.

What {slot} is free to hold depends on whether the service can name the player itself:

  • It takes the player from the token, the way PlayFab and LootLocker do. The address names the save alone, so slots are yours and work as they do anywhere else.
  • The address has to name the player, which is what the other recipes here do. Turn on Use Slot and set Slots.GlobalSlot to the player id, so {slot} carries it. That asset then holds one save per player.

To have both on a service of the second kind, put an endpoint of your own in front of it: it reads the player from the token, which leaves the slot to you.

Note: A token for one signed-in player is safe in a build. An account key or an admin key is not, and belongs behind an endpoint of your own.

PlayFab

FieldValue
Release Urlhttps://TITLEID.playfabapi.com
Load Url Template{url}/Client/GetUserData
Load Body Template{"Keys":["save"]}
Save Url Template{url}/Client/UpdateUserData
Save Body Template{"Data":{"save":"{payload}"}}
Clear Url Template{url}/Client/UpdateUserData
Clear Body Template{"KeysToRemove":["save"]}
Load / Save / Clear VerbPost
Response Payload Pathdata.Data.save.Value
Extra HeadersX-Authorization = {token}
Not Found MeansAn Error: PlayFab answers a successful, empty result for a save that is not there, so a 404 is always a fault
Empty Answer MeansNo Save: that empty result is what a first launch looks like

The session ticket names the player, so the slot names the save file rather than the player. To give a player several slots, vary the key and the response path together.

Note: A new title doesn't create the player for you. Titles made since 30 June 2025 answer 400 PlayerCreationDisabled. Create the player from your own server, or untick that box under Settings > API Features while you test.

Firebase Firestore

FieldValue
Release Urlhttps://firestore.googleapis.com/v1/projects/PROJECT/databases/(default)/documents
Load Url Template{url}/players/{slot}/saves/{id}
Save Url Template{url}/players/{slot}/saves/{id}?updateMask.fieldPaths=save
Clear Url Template{url}/players/{slot}/saves/{id}
Load VerbGet
Save VerbPatch
Clear VerbDelete
Save Body Template{"fields":{"save":{"stringValue":"{payload}"}}}
Response Payload Pathfields.save.stringValue
Extra Headersnone; the standard Authorization header carries Bearer <idToken>
Not Found MeansNo Save, the default: Firestore answers 404 for a document that does not exist
Warning: Write a security rule that matches request.auth.uid against the path. The test-mode ruleset makes every save public.

Supabase

Supabase serves a table through PostgREST. Create a saves table with an owner uuid primary key defaulting to auth.uid(), a save text column, and a row-level policy for the owner.

FieldValue
Release Urlhttps://PROJECT.supabase.co/rest/v1
Load Url Template{url}/saves?select=save, with row-level security returning only this player's row
Save Url Template{url}/saves?on_conflict=owner
Clear Url Template{url}/saves?owner=eq.{slot}, with the player id in the slot
Load VerbGet
Save VerbPost
Clear VerbDelete
Save Body Template{"save":"{payload}"}
Response Payload Path0.save
Extra Headersapikey = your anon key, and Prefer = resolution=merge-duplicates
Not Found MeansAn Error: PostgREST answers an empty array for a row that is not there, so a 404 is always a fault
Empty Answer MeansNo Save: that empty array is what a first launch looks like
Warning: Row-level security is the only thing keeping one player out of another's save. Ship the anon key, never service_role.

Firebase Realtime Database

FieldValue
Release Urlhttps://PROJECT-default-rtdb.firebaseio.com
Load / Save / Clear Url Template{url}/players/{slot}/saves/{id}.json?auth={token}
Load / Save / Clear VerbGet / Put / Delete, the defaults
Save Body Template{"save":"{payload}"}
Response Payload Pathsave
Extra Headersnone; the token rides in the address
Empty Answer MeansNo Save: a path holding nothing answers 200 with null
Warning: The envelope is not optional here. A raw body comes back as a quoted, escaped JSON string, which no longer decodes.

Nakama

FieldValue
Release Urlhttps://HOST:7350/v2
Load / Save Url Template{url}/storage
Clear Url Template{url}/storage/delete
Load / Save / Clear VerbPost / Put / Put
Load Body Template{"object_ids":[{"collection":"saves","key":"{id}","user_id":"{slot}"}]}
Save Body Template{"objects":[{"collection":"saves","key":"{id}","value":"{payload}","permissionRead":1,"permissionWrite":1}]}
Clear Body Template{"object_ids":[{"collection":"saves","key":"{id}"}]}
Response Payload Pathobjects.0.value
Empty Answer MeansNo Save: a key that was never written comes back as an empty objects list

LootLocker

FieldValue
Release Urlhttps://api.lootlocker.com/game/v1/player/storage
Load Url Template{url}?key=save_{slot}
Save Url Template{url}
Clear Url Template{url}?key=save_{slot}
Load / Save / Clear VerbGet / Post / Delete
Save Body Template{"payload":[{"key":"save_{slot}","value":"{payload}","is_public":false,"order":1}]}
Response Payload Pathpayload.value
Extra Headersx-session-token = {token}
Not Found MeansAn Error: a key nobody wrote answers 200, so a 404 is a real fault
Empty Answer MeansNo Save: a key nobody wrote answers 200 with "payload":null
Warning: The save body is a list, and its rows need an order. A flat body answers 200 and stores nothing.
Note: A clear stays invisible to the next load until LootLocker's cache lapses, so trust the result your Clear() returned over what a load reports.

PocketBase

FieldValue
Release Urlhttps://HOST/api/collections/saves
Load Url Template{url}/records?filter=owner="{slot}"&fields=save
Clear Url Template{url}/records/{slot}
Load / Clear VerbGet / Delete, the defaults
Response Payload Pathitems.0.save
Empty Answer MeansNo Save: a filter matching nothing answers 200 with an empty items list
Warning: PocketBase has no upsert, so the record has to exist before the first save. Either create it with the player account, after which PATCH {url}/records/{slot} serves every save, or add a route of your own that upserts.

Appwrite

FieldValue
Release Urlhttps://HOST/v1/tablesdb/DB/tables/TABLE
Load / Save / Clear Url Template{url}/rows/{slot}
Load / Save / Clear VerbGet / Put / Delete
Save Body Template{"data":{"save":"{payload}"}}
Response Payload Pathsave
Extra HeadersX-Appwrite-Project = your project id

Any GraphQL endpoint

FieldValue
Release Urlhttps://HOST/graphql
Load / Save / Clear Url Template{url}
Load / Save / Clear VerbPost
Load Body Template{"query":"query($o:String!){saves(owner:$o){save}}","variables":{"o":"{slot}"}}
Save Body Template{"query":"mutation($o:String!,$s:String!){insert_saves_one(owner:$o,save:$s){owner}}","variables":{"o":"{slot}","s":"{payload}"}}
Clear Body Template{"query":"mutation($o:String!){delete_saves(owner:$o)}","variables":{"o":"{slot}"}}
Response Payload Pathdata.saves.0.save
Warning: A GraphQL error is an HTTP 200 carrying no data, which reaches the payload path as nothing. Leave Empty Answer Means on Corrupt, so the load reports it instead of reading it as a first launch.

Any other REST backend

To fill in the fields for a service this page doesn't list, find four things in its own documentation:

  1. Which endpoint reads, writes and deletes one record, and with which verb.
  2. What a write body looks like, with the data in their example replaced by {payload} and nothing escaped.
  3. Where the data sits in a read answer, written with dots, and numbers for positions in an array.
  4. How the service wants the token. A standard Authorization header needs nothing extra.
Note: Every write replaces the whole record, and the manager sends no ETag or If-Match header, so two devices writing at once means last one wins, unless the offline cache is on.

Use your own database

Put an endpoint of your own in front of the database. The game never reaches MySQL, Postgres or anything else directly: a connection string in a build is in your players' hands, and WebGL and consoles can't open one at all.

Leave every Backend Service field empty, and the manager sends the payload verbatim to url/slot/id. Your endpoint then needs the three routes Server (HTTP) lists, keyed by the player, the asset and the slot. Verify the token on each of them, and take the player from the token, never from the request body.

Note: Store the payload as bytes, in a LONGBLOB or a BYTEA column. With compression or encryption on it is not text, and a column that re-encodes it produces corrupt saves.
Note: Bound the payload's size, and trust nothing in it.

Other services

Xano, Directus, Strapi, Parse Server, Back4App and Convex take the Appwrite shape; Hasura, Nhost and AWS AppSync take the GraphQL one. Steam and Unity Cloud Save have a manager of their own.

What an absent save looks like

Two settings decide how the manager reads an answer that carries no save. The first, Empty Answer Means, covers an answer that parsed but holds nothing at the payload path:

ValueAn answer holding nothing at the payload path
Corrupt (default) Reported: the load comes back Corrupt and On Load Failed fires. With an offline cache on, the cached save is kept.
No Save A first launch: the target starts on its defaults with no failure event. A mistyped payload path reads the same way.
Note: Either way, saving stays on, and the next save writes the authored values over the server's copy. Check the payload path against a real answer before you ship.

A 404 means both "the address is right and holds nothing" and "unknown route, rejected token". The second setting, Not Found Means, settles which one applies:

ValueA 404 to a load
No Save (default)There is no save. The target starts on its defaults, and an offline cache entry for that slot is dropped.
An ErrorSomething is wrong. The load fails and retries, and a cache entry is kept and stays playable.