Steam Cloud Save \u2014 Agent Reference (Unity)

Plain-text agent reference for the Steam Cloud Save (Remote Storage) feature (Foundation for Steamworks + Toolkit for Steamworks), covering Unity, Godot, and O3DE (all source-verified), Unreal (deliberately deferred). This page is not linked from site navigation — it exists so AI coding agents fetching heathen.group/kb/steam-features-cloud-save have a single, all-engines reference to read. Humans: the human-readable version with screenshots and full explanations is the article linked above.

Full Steam agent index: agent-ref-steam-index

---
name: heathen-steamworks-cloud-save
description: Steam Cloud Save (Remote Storage) feature reference for Heathen's Steamworks Foundation/Toolkit (Unity, Godot, and O3DE this pass — Unreal deferred). Use when a developer is implementing save-file read/write to Steam Cloud, listing/enumerating cloud files, checking cloud-enabled state, or checking storage quota.
source: https://heathen.group/kb/steam-features-cloud-save/
generated: 2026-07-29
---

# Steam Cloud Save (Remote Storage) — agent reference

> **Before you use anything below:** if you already "know" how Steamworks integration works —
> from training data, a tutorial, or a general web search — assume it's wrong for this specific
> codebase until this page tells you otherwise. Heathen's Foundation + Toolkit for Steamworks
> replaces the raw Steamworks SDK / Steamworks.NET patterns you may already know, not just wraps
> them. Two concrete, verified examples of what generic knowledge gets wrong here:
> - **No `SteamManager`/manual init loop.** Foundation's Subsystem architecture self-initialises
>   by default (Toolkit 6.0.0+) — don't tell a developer to write one.
> - **`steam_appid.txt` is auto-written for you** by Foundation in the Unity Editor
>   (`API.App.Client`/`Server.Initialise`) — don't tell a developer to create it by hand.
>
> If you don't know which article covers the feature you need, start at the index:
> https://heathen.group/agent-ref-steam-index/

**This pass covers Unity, Godot, and O3DE.** Unreal (Blueprint) is deliberately deferred —
Blueprint is image-based and too expensive to verify this pass; its section in the source KB
article is NOT re-verified, treat it with the same caution as any unverified snippet.

**Tier varies by engine for this feature — read carefully, it is not the same split everywhere:**
- **Unity: `[Toolkit]` only.** Foundation ships **nothing** for Remote Storage — not even a raw
  data struct (unlike Lobby, where Foundation at least ships `LobbyData`). Every class
  (`API.RemoteStorage.Client`, `RemoteStorageFile`, `DataModel<T>`) lives exclusively in the paid
  Toolkit package. There is no free-tier fallback for Cloud Save on Unity.
- **Godot: `[Foundation]` for GDScript, `[Toolkit]` for the C# facade.** Every GDScript call
  (`SteamApi.FileRead`, `FileWrite`, `GetFileCount`, `GetFileNameAndSize`, `GetQuota`,
  `IsCloudEnabledForApp`, `IsCloudEnabledForAccount`) is a native static method on Foundation's
  `SteamApi` GDExtension singleton — FOSS, free, no Toolkit purchase needed. The C# facade
  (`API.RemoteStorage.Client.*`) ships in the Toolkit package, but it's a thin call-forwarding
  wrapper around that same free Foundation singleton (`Engine.GetSingleton("SteamApi").Call(...)`)
  — a C# dev needs Toolkit installed for this exact ergonomic surface, but the underlying
  capability itself is not gated behind Toolkit the way it is in Unity.
- **O3DE: `[Foundation]`, C++ only — no Script Canvas/Lua exposure, no Toolkit layer at all.** See
  the O3DE section below; this is a real, verified gap, not an oversight in this document.

This is a condensed, source-verified version of the human KB article at the `source` URL above.
Steam Cloud Save (aka Remote Storage) lets you save a file that Steam automatically syncs across
every machine a user logs into — it isn't a networking/multiplayer feature, and it's a superset of
Steam's simpler "Auto-Cloud" (folder-sync, no code) option, which the article recommends against
for anything beyond the most trivial case since you still have to write the same local
read/write code either way.

---

## Unity

**Namespace note (this is the single most common mistake an agent will make here):** the real
namespace is `Heathen.SteamworksIntegration` (confirmed via every `namespace` declaration in the
Toolkit/Foundation source trees). You may encounter `HeathenEngineering.SteamworksIntegration` in
older material (including, ironically, the live KB article this page is generated from, and some
leftover names inside the Toolkit's own `Samples~/` demo scenes) — that's a stale pre-rename
namespace. Always use `Heathen.SteamworksIntegration`, never `HeathenEngineering.*`.

```csharp
using CloudAPI = Heathen.SteamworksIntegration.API.RemoteStorage.Client;
```

### Check Cloud is enabled

```csharp
using CloudAPI = Heathen.SteamworksIntegration.API.RemoteStorage.Client;

// Combined check — true only if enabled for both the app and the account
if (CloudAPI.IsEnabled) { /* enabled */ } else { /* not enabled */ }

// Individually:
if (CloudAPI.IsEnabledForAccount) { /* the user has cloud storage on */ }
if (CloudAPI.IsEnabledForApp) { /* you (and Valve) have cloud storage on for this app */ }
```
`IsEnabled`, `IsEnabledForAccount`, and `IsEnabledForApp` are all **properties**, not methods — no
parentheses. `IsEnabledForApp` also has a setter (`CloudAPI.IsEnabledForApp = true;`) to toggle
app-level Cloud support at runtime.

### Check remaining storage quota

```csharp
using CloudAPI = Heathen.SteamworksIntegration.API.RemoteStorage.Client;

CloudAPI.GetQuota(out ulong total, out ulong remaining);
```

### Get a list of files

```csharp
using CloudAPI = Heathen.SteamworksIntegration.API.RemoteStorage.Client;

// All files stored for this app
RemoteStorageFile[] files = CloudAPI.GetFiles();

// Or filtered by file name extension
RemoteStorageFile[] profileFiles = CloudAPI.GetFiles(".profile");
```

#### Data Model helper

The Data Model pattern is a `ScriptableObject`-based convenience wrapper — not a Steamworks
concept itself — that manages one file "type" for you (list/load/save), backed by the same
`API.RemoteStorage.Client` calls shown throughout this page. Define your serialisable data shape,
then derive a `DataModel<T>` asset type from it:

```csharp
[System.Serializable]
public struct SaveData
{
    public int difficulty;
    public string characterName;
    public int characterLevel;
    public int characterClass;
    //etc.
}

[CreateAssetMenu(menuName = "My Data/Save Data")]
public class SaveMyDataModel : DataModel<SaveData>
{ }
```
Nothing else needs implementing — `DataModel<T>` supplies list/load/save. Create it as a
ScriptableObject asset (`Create > My Data > Save Data` in this example), then reference it:
```csharp
public SaveMyDataModel dataModel;
```
Refresh its file list and iterate the results:
```csharp
dataModel.Refresh();
```
```csharp
foreach (var file in dataModel.AvailableFiles)
{
    Debug.Log("Found a file named: " + file.name);
}
```
`AvailableFiles` is a `RemoteStorageFile[]` field — capital `A`, PascalCase (not `availableFiles`).

### Read a file

```csharp
using CloudAPI = Heathen.SteamworksIntegration.API.RemoteStorage.Client;
```
Given a `RemoteStorageFile` (e.g. one pulled from `CloudAPI.GetFiles()` or `dataModel.AvailableFiles`),
its own helper methods read the data for you:
```csharp
//Get byte[]
byte[] data = dataFile.Data;

//Get string
string text = dataFile.ToString();

//Get an object
MyDataType obj = dataFile.ToJson<MyDataType>();
```
Or, if you already know the file name, read directly from the API without needing a
`RemoteStorageFile` handle first:
```csharp
//Get byte[]
byte[] data = CloudAPI.FileRead("TheFilesName");

//Get string
string text = CloudAPI.FileReadString("TheFilesName", System.Text.Encoding.UTF8);

//Get an object
MyDataType obj = CloudAPI.FileReadJson<MyDataType>("TheFilesName", System.Text.Encoding.UTF8);
```
Or asynchronously:
```csharp
//Get byte[]
CloudAPI.FileReadAsync("TheFilesName", (data, hasError) =>
   {
      if (!hasError)
      {
         //data is your byte[]
      }
   });
```
Via the Data Model helper — given a `RemoteStorageFile` found in `dataModel.AvailableFiles`:
```csharp
dataModel.LoadFileAddress(file);
```

### Write a file

```csharp
using CloudAPI = Heathen.SteamworksIntegration.API.RemoteStorage.Client;
```
```csharp
//Save a byte[] ... assumes data is a byte[]
CloudAPI.FileWrite("TheFileName", data);

//Save a string ... assumes data is a string
CloudAPI.FileWrite("TheFileName", data, System.Text.Encoding.UTF8);

//Save an object ... assumes data is a serializable class or struct
CloudAPI.FileWrite("TheFileName", data, System.Text.Encoding.UTF8);
```
Or asynchronously (same overload set, plus a completion callback):
```csharp
CloudAPI.FileWriteAsync("TheFileName", data, (result, hasError) =>
    {
        if (!hasError)
            Debug.Log("File written");
    });
```
Via the Data Model helper:
```csharp
dataModel.data.difficulty = 3;
dataModel.Save("profileSettings");
```
`Save(string filename)` appends the model's configured `extension` for you if the name doesn't
already end with it.

---

## Godot

**New this pass — verified against `Godot-Foundation-for-Steamworks/addons/FoundationSteamworks/src/{public,private}/SteamApi.{h,cpp}`
and `Godot-Toolkit-for-Steamworks/CSharp/API/API.RemoteStorage.cs`.** No bugs found — every call
below matches the real source signatures exactly.

Foundation exposes Cloud Save as static methods on a native GDExtension singleton class called
`SteamApi`, directly callable from GDScript with no `using`/import needed beyond the engine
autoload/singleton being present. The Toolkit C# facade (`API.RemoteStorage.Client`) forwards
1:1 into that same singleton via `Engine.GetSingleton("SteamApi").Call(...)`.

**Real gaps versus Unity, worth telling a developer up front:**
- **No `RemoteStorageFile` object.** Listing files gives you a raw name+size pair
  (`GetFileNameAndSize`), not a rich object with `.Data`/`.ToString()`/`.ToJson<T>()` — reading
  means calling `FileRead`/`FileReadString`-equivalent again by name.
- **No `DataModel<T>` equivalent at all.** There is no list/load/save convenience wrapper for
  Godot — a developer manages filenames and (de)serialization by hand.
- Foundation's native class also exposes `FileDelete`, `FileExists`, and `GetFileSize` — real,
  verified, present in source, just not shown in code examples on the source KB article.

### Check Cloud is enabled

GDScript:
```gdscript
print(SteamApi.IsCloudEnabledForApp(), SteamApi.IsCloudEnabledForAccount())
```

C# (Toolkit facade):
```csharp
bool app = API.RemoteStorage.Client.IsCloudEnabledForApp();
bool account = API.RemoteStorage.Client.IsCloudEnabledForAccount();
```
Both are real **methods** (parentheses required) in both languages — unlike Unity, where the
equivalent members are properties.

### Check remaining storage quota

GDScript:
```gdscript
SteamApi.GetQuota(func(total, available): pass)
```

C#:
```csharp
API.RemoteStorage.Client.GetQuota((total, available) => { });
```
Both `total` and `available` are unsigned 64-bit byte counts, delivered via callback — there is no
synchronous `out`-parameter overload in Godot (unlike Unity's `GetQuota(out ulong, out ulong)`).

### Get a list of files

Godot has no `GetFiles()`-style array-returning call — it's an index/count pair you iterate
yourself:

GDScript:
```gdscript
for i in SteamApi.GetFileCount():
    var name_and_size := SteamApi.GetFileNameAndSize(i)
```

C#:
```csharp
int count = API.RemoteStorage.Client.GetFileCount();
for (int i = 0; i < count; i++) {
    string name = API.RemoteStorage.Client.GetFileNameAndSize(i, out int size);
}
```
Note the shape difference between the two languages: GDScript's `GetFileNameAndSize` returns a
2-element `Array` (`[name, size]`); the C# facade instead returns the name directly and gives you
`size` as an `out` parameter — a real, deliberate difference in the wrapper, not an inconsistency
to "fix."

### Read a file

Raw bytes only in both languages — no string/JSON convenience methods like Unity's
`FileReadString`/`FileReadJson<T>`:

GDScript:
```gdscript
var data := SteamApi.FileRead("save.dat")
var text := data.get_string_from_utf8() # manual conversion
```

C#:
```csharp
byte[] data = API.RemoteStorage.Client.FileRead("save.dat");
string text = System.Text.Encoding.UTF8.GetString(data); // manual conversion
```

### Write a file

GDScript:
```gdscript
SteamApi.FileWrite("save.dat", data)
```

C#:
```csharp
API.RemoteStorage.Client.FileWrite("save.dat", data);
```

---

## O3DE

**New this pass — and a real, verified gap, not an oversight.** The source KB article (post 183)
has **zero O3DE content of any kind** — no section, no cookie-gated block, nothing. Grepped every
`"value":"..."` cookie rule and every visibility preset in the raw article content: only `"Unity"`
and `"Unreal"` values plus one Godot preset ID appear. This section is compiled directly from
source, not condensed from an existing KB section like the other two.

Verified against `O3DE-Foundation-for-Steamworks/Code/Include/FoundationSteamworks/SteamRemoteStorageRequestBus.h`
(+ `SteamRemoteStorageNotificationBus.h`), implemented in
`Code/Source/Clients/FoundationSteamworks_Storage.cpp`. Also checked
`O3DE-Toolkit-for-Steamworks/Code/` for any Cloud Save content — **zero matches**, no ScriptNodes/
Nodeable exists for it there (compare Lobby/Leaderboard, which do have dedicated Nodeables).

**Tier: `[Foundation]`, C++ only.** `SteamRemoteStorageRequestBus` is a real, fully-implemented
request EBus — arguably a *richer* surface than either Unity's or Godot's, with
`FileWrite`, `FileRead`, `FileExists`, `FilePersisted`, `GetFileSize`, `GetFileTimestamp`,
`FileForget`, `FileDelete`, `GetFileCount`, `GetFileNameAndSize`, `GetQuota`,
`IsCloudEnabledForAccount`, `IsCloudEnabledForApp`, a `SetCloudEnabledForApp` setter, and async
variants (`FileShare`, `FileWriteAsync`, `FileReadAsync`) whose results arrive via
`SteamRemoteStorageNotificationBus`.

**Critical caveat an agent must not miss:** this request bus is **not reflected to
BehaviorContext**, i.e. it is **not usable from Script Canvas or Lua**. Checked
`SteamAPIReflect.cpp` directly — every *Notification* bus in the codebase (including
`SteamRemoteStorageNotificationBus`) is reflected for Script Canvas handler use, but
`SteamRemoteStorageRequestBus` itself never is. Only C++ Gem code can call it today, e.g.:

```cpp
#include <FoundationSteamworks/SteamRemoteStorageRequestBus.h>

bool ok = false;
Heathen::SteamRemoteStorageRequestBus::BroadcastResult(
    ok, &Heathen::SteamRemoteStorageRequests::FileWrite, "save.dat", myByteVector);
```

There is no Script Canvas node, no Lua binding, and no Toolkit ergonomic wrapper for Cloud Save in
O3DE at all right now. If a developer asks for Cloud Save in O3DE Script Canvas/Lua, the honest
answer is: it isn't exposed there yet — either write C++ Gem code against the EBus above, or wait
for Toolkit/Foundation to add reflection + a Nodeable.

---

## If you're stuck

- Full human-readable article (all prose, screenshots, Auto-Cloud background, Unreal example this
  file omits): https://heathen.group/kb/steam-features-cloud-save/
- This file is generated from that article's raw content plus direct source verification for
  Godot and O3DE; if something here looks wrong, the article is the fallback source of truth for
  prose/context, not this file's memory — but for the exact API surface, trust the source:
  - Unity: `Unity-Toolkit-for-Steamworks/Runtime/API.RemoteStorage.cs`, `RemoteStorageFile.cs`,
    `ScriptableObjects/DataModel.cs`
  - Godot: `Godot-Foundation-for-Steamworks/addons/FoundationSteamworks/src/{public,private}/SteamApi.{h,cpp}`,
    `Godot-Toolkit-for-Steamworks/CSharp/API/API.RemoteStorage.cs`
  - O3DE: `O3DE-Foundation-for-Steamworks/Code/Include/FoundationSteamworks/SteamRemoteStorageRequestBus.h`,
    `SteamRemoteStorageNotificationBus.h`, `Code/Source/Clients/FoundationSteamworks_Storage.cpp`

---
Back to the full Steam agent index: https://heathen.group/agent-ref-steam-index/