Skip to content

Rich Presence

Set a player’s current activity (e.g. “Playing Level 5”, “Browsing Shop”) and the SDK syncs it to two places:

  1. Live Activity dashboard — see who’s playing what right now, in real-time over WebSocket
  2. Steam Rich Presence — surface activity strings on the player’s Steam profile and friends list
QuestData.set_activity(activity: String, details: Dictionary = {})
ParameterTypeDefaultDescription
activityStringrequiredActivity identifier (e.g. "in_level", "in_shop", "boss_fight")
detailsDictionary{}Free-form context (level, character, boss, score, …)

The activity is buffered locally and synced to the server every 5 seconds if it has changed. Setting the same activity repeatedly does nothing — only changes are pushed.

QuestData.clear_activity()

Marks the player as no longer in any activity. Call when returning to the title screen, on app pause, or before quit. Clears immediately without waiting for the 5s timer.

QuestData.get_activity() -> Dictionary

Returns the current activity dictionary (copy). Useful for showing the same string in your own in-game UI.

QuestData.get_steam_presence_string() -> String

Returns a human-readable string suitable for Steam.setRichPresence("steam_display", ...) (with the GodotSteam addon). Built-in mappings:

ActivityOutput
in_level (with level/level_name detail)"Playing Level <X>"
in_shop"Browsing Shop"
in_menu"In Menu"
boss_fight (with boss_name detail)"Fighting <Boss>"
otheractivity capitalized, underscores → spaces
empty"In Menu"
QuestData.format_presence(template: String) -> String

Custom templating. {activity} and any {key} from the details dictionary are interpolated.

QuestData.set_activity("in_level", {"level": 5, "deaths": 3})
QuestData.format_presence("Lvl {level} — died {deaths}×")
# → "Lvl 5 — died 3×"
# When a level loads
func _on_level_started(level_id: int):
QuestData.set_activity("in_level", {
"level": level_id,
"character": player.class_name,
"difficulty": GameSettings.difficulty
})
# Boss arena
func _on_boss_intro(boss_id: String, boss_name: String):
QuestData.set_activity("boss_fight", {
"boss_id": boss_id,
"boss_name": boss_name,
"level": current_level
})
# Title screen / menus
func _on_returned_to_menu():
QuestData.clear_activity()
# Quit cleanup
func _notification(what):
if what == NOTIFICATION_WM_CLOSE_REQUEST:
QuestData.clear_activity()
# Sync to Steam every time activity changes
func _on_activity_changed():
if OS.has_feature("steam"):
Steam.setRichPresence("steam_display", QuestData.get_steam_presence_string())
Steam.setRichPresence("status", QuestData.format_presence("{activity}"))

You don’t need GodotSteam to use Rich Presence — set_activity() works standalone for the Live Activity dashboard. Steam is a downstream consumer.

  1. set_activity() writes to a local buffer and marks the state dirty
  2. Every 5 seconds, the SDK checks the dirty flag and POSTs {activity, details} to /v1/players/activity if changed
  3. clear_activity() POSTs immediately with an empty payload
  4. The dashboard’s Live Activity page subscribes via WebSocket and renders changes in real-time
  5. No timer fires when the activity is unchanged — zero overhead at idle
LimitValue
Sync interval5 seconds (only on change)
activity length64 chars
details size1 KB JSON
  1. Keep activity names stable"in_level" not "playing_level_5" (put 5 in details). This keeps the Live Activity dashboard countable per state.
  2. Always clear on quit — otherwise the player looks “in_level” forever in the dashboard until the session timeout sweeps them out.
  3. Don’t hammer it — calling set_activity() every frame is fine (changes are deduped) but it’s still cleaner to call it on actual transitions.
  4. Match Steam’s display rules — Steam displays max ~64 chars and rejects rapid updates. Use the built-in mappings unless you need a specific format.