Plain-text agent reference for the Steam Downloadable Content feature (Foundation for Steamworks + Toolkit for Steamworks), covering Unity and Godot (both source-verified), O3DE (confirmed not documented), Unreal (deliberately deferred). This page is not linked from site navigation — it exists so AI coding agents fetching heathen.group/kb/downloadable-content 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-downloadable-content
description: Steam Downloadable Content (DLC) reference for Heathen's Steamworks Foundation/Toolkit (Unity, Godot, and O3DE — Unreal deferred). Use when a developer is implementing DLC ownership checks, enumerating a game's DLC, or installing/uninstalling optional DLC content.
source: https://heathen.group/kb/downloadable-content/
generated: 2026-07-29
---
# Steam Downloadable Content (DLC) — 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 is deliberately deferred (Blueprint is
image-based, too expensive to verify this way) — Unreal/C++ content in the live article, if any,
has NOT been re-verified this pass and should be treated with the same caution as any unverified
snippet.
**Tier: mixed — mostly [Foundation] (FOSS, free), one [Toolkit] (Pro, paid) exception, and the
exception itself is engine-specific.** DLC identity, ownership checks, enumeration, and
install/uninstall all live in Foundation across every engine covered here — no Toolkit purchase
needed for any of that. Opening the Steam Overlay to a DLC's store page is the one call that
varies by engine tier:
- **Unity**: Overlay-to-store is **[Toolkit]-only** — no Foundation equivalent exists.
- **Godot**: Overlay-to-store (`ActivateGameOverlayToStore`) is **[Foundation]** — confirmed in
both the native GDExtension (`SteamApi.h`/`.cpp`) and the C# facade (`API.App.cs`/
`SteamTools.cs`). No Toolkit purchase needed in Godot for this.
- **O3DE**: no working overlay-to-store call exists yet in either tier for DLC specifically (see
the O3DE section below) — Toolkit declares `DlcData::OpenStorePage()` but it has no
implementation.
In Steam, you don't "own" a DLC, you're "subscribed" to it — `IsSubscribed`/`Subscribed` checks
subscription, not a separate ownership flag. This holds across all three engines below.
## Is Subscribed / get name / open store page
**Unity C#**
```csharp
DlcData dlc = DlcData.Get(someDlcAppId); // [Foundation] wraps an AppId into a DlcData struct
// [Foundation] or [Toolkit] — both work, see the two lines below:
if (API.App.IsSubscribedApp(dlc)) // [Foundation] static call, no Toolkit needed
{
// The DLC is owned and enabled
}
if (dlc.IsSubscribed()) // [Toolkit] same check, instance-call sugar
{
// The DLC is owned and enabled
}
// Name: dlc.Name (Foundation property) only returns a populated value once Foundation's
// name-cache has been warmed (e.g. by enumerating API.App.Dlc once, or via the calls below).
// A fresh DlcData.Get(appId) with no prior enumeration returns an EMPTY STRING from dlc.Name,
// not an error — don't assume it "just works" on the first read.
string displayName = dlc.GetName(); // [Toolkit] auto-warms the cache, always populated
dlc.OpenStore(); // [Toolkit] — opens the Steam Overlay to this
// DLC's store page; no Foundation equivalent exists
```
No Code Free / inspector-component example is published for this article yet (the live KB page's
Code Free slot for Unity says "coming soon").
**Godot GDScript** — `DlcData` is a native GDExtension class (`RefCounted`); everything below is
`[Foundation]` except where noted:
```gdscript
var dlc := DlcData.Get(some_dlc_app_id) # [Foundation] static Get(appId)
if dlc.GetIsSubscribed(): # [Foundation] method call — NOT a property in GDScript
pass
var display_name := dlc.GetDlcName() # [Foundation] method (also exposed as a property,
# DlcName, since it's the one member registered via
# ADD_PROPERTY — either form works)
SteamApi.ActivateGameOverlayToStore(dlc.GetIntId()) # [Foundation] — unlike Unity, opening the
# Overlay to a DLC's store page needs no Toolkit purchase
# in Godot; it's a plain Foundation singleton call
```
Note: `GetIsInstalled()`, `GetIsSubscribed()`, `GetIsAvailable()` are **methods**, not properties,
on the native GDScript-facing class — only `DlcName` is registered as a real Godot property. Don't
guess `dlc.IsSubscribed` (no such member in GDScript) — it's `dlc.GetIsSubscribed()`.
**Godot C#** — the C# facade (`Heathen.SteamworksIntegration.DlcData`) wraps the native object and
exposes plain **properties**, which is a different shape than the GDScript methods above:
```csharp
DlcData dlc = DlcData.Get(someDlcAppId); // [Foundation] static Get(appId)
if (dlc.Subscribed) { } // [Foundation] property — NOT "IsSubscribed"
// (that name doesn't exist on the C# facade;
// a real bug with exactly this shape was found
// and fixed live in the KB article this pass)
string name = dlc.DlcName; // [Foundation] property
API.App.ActivateGameOverlayToStore(dlc.Id); // [Foundation] — same Godot-only exception as
// the GDScript example above: no Toolkit needed
```
**Toolkit adds exactly one thing for Godot DLC**, an extension method, nothing else:
```csharp
System.DateTime purchased = dlc.GetPurchaseTime(); // [Toolkit] — DlcDataExtensions.cs
```
**O3DE C++** — no `DlcData`-style wrapper object exists on the Foundation side; DLC is accessed
via flat static calls on `FoundationSteamworks::SteamAppsAPI` (a thin proxy over the
`SteamAppsRequestBus`):
```cpp
using namespace FoundationSteamworks;
bool owned = SteamAppsAPI::IsSubscribedApp(dlcAppId); // [Foundation]
```
There is no Foundation call to fetch a DLC's display name — Foundation's DLC surface is
ownership/install-state only (see the full list in "Iterate over DLC" below); a game must already
know its own DLC names (e.g. from its own config/catalog), same limitation as the enumeration gap
noted below.
**O3DE — Overlay-to-store is not currently usable for DLC in either tier.** Toolkit declares
`Heathen::SteamTools::DlcData::OpenStorePage()` (header only, in
`ToolkitSteamworks/Data/DlcData.h`) but **there is no `.cpp` implementing it anywhere in the
Toolkit repo** — confirmed by searching the whole source tree. Treat this as WIP, not usable
today; there is no working substitute in Foundation either (Foundation has no overlay-to-store
call scoped to a specific DLC app ID at all — only the general Overlay system, which is out of
scope for this article).
## Iterate over DLC
**Unity C#**
Not present in the live article's Unity-gated content (it's currently only published there for
the C++/Unreal and Godot columns) — filled in here from the same verified Foundation API used
above, for parity with the other engines' equivalent section.
```csharp
foreach (DlcData dlc in API.App.Dlc) // [Foundation] DlcData[] — also warms the name cache
{
string name = dlc.Name; // populated now, since API.App.Dlc just warmed it
bool subscribed = API.App.IsSubscribedApp(dlc);
}
int count = API.App.DlcCount; // [Foundation] — count only, cheaper than enumerating
```
**Godot GDScript**
```gdscript
for dlc in SteamApi.GetDLC(): # [Foundation] returns TypedArray<DlcData>
print(dlc.GetDlcName())
```
**Godot C#**
```csharp
List<DlcData> all = API.App.GetDlc(); // [Foundation]
int count = API.App.DlcCount; // [Foundation]
```
**O3DE C++**
```cpp
int32_t count = SteamAppsAPI::GetDLCCount(); // [Foundation] — count only
```
**Real, confirmed gap**: unlike Unity and Godot, O3DE's Foundation surface has **no per-index
enumeration call** — no equivalent of Unity's `BGetDLCDataByIndex` or Godot's `GetDLC()` that
returns each entry's AppID/name/availability. `GetDLCCount()` is the only enumeration-adjacent
call that exists and is implemented (`SteamAppsRequestBus` → `FoundationSteamworksSystemComponent`
→ real `SteamApps()->GetDLCCount()`). A developer who needs to list DLC by AppID/name in O3DE
today must already know the AppIDs out-of-band (e.g. hardcode them from the Steamworks dashboard)
and check each individually with `IsSubscribedApp`/`IsDlcInstalled` — there is no "give me all of
them" call.
## DLC Installation
**Unity C#**
Also not present in the live article's Unity-gated content — same fill-in-for-parity note as
above.
```csharp
DlcData dlc = DlcData.Get(someDlcAppId);
if (!API.App.IsDlcInstalled(dlc)) // [Foundation]
{
API.App.InstallDlc(dlc); // [Foundation] — a request; Steam may not complete it
// immediately (not owned, files locked by a running
// process, etc.)
}
API.App.GetDlcDownloadProgress(dlc, out ulong downloaded, out ulong total); // [Foundation]
API.App.UninstallDlc(dlc); // [Foundation]
```
Toolkit equivalents exist as instance sugar over the same calls: `dlc.IsInstalled()`,
`dlc.Install()`, `dlc.Uninstall()`, `dlc.GetDownloadProgress()` (returns a single `float` 0-1
instead of separate downloaded/total `ulong`s) — `DlcDataExtensions.cs`.
**Godot GDScript**
```gdscript
if not dlc.GetIsInstalled(): # [Foundation] method, not a property
dlc.Install() # [Foundation]
print(dlc.DownloadProgress()) # [Foundation] — single float 0-1
```
**Godot C#**
```csharp
if (!dlc.Installed) # [Foundation] property — NOT "IsInstalled" (same
# property-vs-method mistake as Subscribed above; also
# a real bug found and fixed live this pass)
dlc.Install(); # [Foundation]
float progress = dlc.DownloadProgress(); # [Foundation]
```
Godot has no separate Toolkit-tier install/uninstall sugar beyond what's shown — Toolkit's only
DLC-specific addition for Godot is `GetPurchaseTime()` (see above).
**O3DE C++**
```cpp
using namespace FoundationSteamworks;
bool installed = SteamAppsAPI::IsDlcInstalled(dlcAppId); // [Foundation]
if (!installed)
{
SteamAppsAPI::InstallDLC(dlcAppId); // [Foundation] — a request, same caveats as other
// engines (ownership, locked files, etc.)
}
SteamAppsAPI::UninstallDLC(dlcAppId); // [Foundation]
```
There is no Foundation or Toolkit call for download-progress percentage in O3DE (unlike Unity's
`GetDlcDownloadProgress` or Godot's `DownloadProgress()`) — not found anywhere in either source
tree for this engine.
**O3DE Toolkit's DLC layer is WIP, not usable today.** `ToolkitSteamworks/Data/DlcData.h` declares
a `DlcData` struct with `IsInstalled()`, `IsSubscribed()`, `OpenStorePage()` intended as ergonomic
sugar over the Foundation bus (matching the pattern used for Unity/Godot Toolkit extensions), but
**none of these methods has an implementation anywhere in the Toolkit repository** — no matching
`.cpp`, and the struct is referenced only inside a large roadmap/design comment in
`ToolkitSteamworksSystemComponent.h` describing a not-yet-built generated `SteamToolsGame` class.
There is also no Script Canvas nodeable for DLC (unlike Lobby/Leaderboard, which have working
nodeables in `Source/ScriptNodes/`). Use the Foundation `SteamAppsAPI` static calls shown above for
O3DE DLC work — they are the only DLC code path confirmed to actually compile and run today.
## If you're stuck
- Full human-readable article (all prose, screenshots, Code-Free inspector field docs this file
compresses out): https://heathen.group/kb/downloadable-content/
- This file is generated from that article's raw content plus direct source verification; if
something here looks wrong, the article is the fallback source of truth, not this file's memory.
---
Back to the full Steam agent index: https://heathen.group/agent-ref-steam-index/