Skip to content

Achievements

Track achievement unlocks for your players. Define achievements in the dashboard, unlock them from GDScript, and the SDK handles caching and offline access.

QuestData.unlock_achievement(achievement_key: String, callback: Callable = Callable())
ParameterTypeDefaultDescription
achievement_keyStringrequiredUnique key matching the achievement defined in the dashboard
callbackCallableCallable()Called with a response Dictionary

Callback response:

KeyTypeDescription
successboolWhether the unlock succeeded
already_unlockedbooltrue if achievement was already unlocked
achievementDictionaryAchievement details (name, description, points)
total_pointsintPlayer’s total achievement points after unlock

If the achievement was already unlocked, success is still true but already_unlocked is true.

QuestData.get_achievements(callback: Callable = Callable())

Fetches all achievements with the current player’s unlock status.

Callback signature: func(achievements: Array, total_points: int, unlocked_points: int)

Each achievement in the Array is a Dictionary with: key, name, description, points, hidden, unlocked, unlocked_at.

QuestData.get_achievements_cached() -> Dictionary

Returns cached achievement data synchronously. The Dictionary contains:

  • achievements: Array of achievement Dictionaries
  • total_points: Total available points
  • unlocked_points: Player’s earned points

Returns an empty Dictionary if no data has been fetched yet.

QuestData.clear_achievement_cache()

Clears the local achievement cache, forcing a re-fetch on next get_achievements() call.

# Unlock on boss defeat
func _on_boss_defeated(boss_id: String):
if boss_id == "final_boss":
QuestData.unlock_achievement("beat_final_boss", func(response: Dictionary):
if response.get("success") and not response.get("already_unlocked"):
show_achievement_popup(response["achievement"])
)
# Display achievement list
func show_achievements():
QuestData.get_achievements(func(achievements: Array, total: int, unlocked: int):
progress_label.text = "%d / %d points" % [unlocked, total]
for ach in achievements:
if ach["hidden"] and not ach["unlocked"]:
add_hidden_entry()
else:
add_achievement_entry(ach)
)
# Quick check from cache (no network)
func has_achievement(key: String) -> bool:
var cached = QuestData.get_achievements_cached()
for ach in cached.get("achievements", []):
if ach["key"] == key and ach["unlocked"]:
return true
return false
  1. Define achievements in the dashboard under Players > Achievements (key, name, description, points, hidden)
  2. unlock_achievement() sends a POST to /v1/achievements/unlock
  3. The server records the unlock and returns the achievement details
  4. The local cache is updated immediately (no re-fetch needed)
  5. get_achievements() fetches the full list with unlock status from the server
  6. Achievement data is cached to disk and available offline

Manage achievements under Players > Achievements:

  • Create/Edit — Define achievements with key, name, description, points
  • Hidden achievements — Mark achievements as hidden (shown as ”???” until unlocked)
  • Global unlock rates — See what percentage of players have each achievement
  • Player lookup — Check which achievements a specific player has unlocked
  1. Use stable keys — Achievement keys can’t be changed after creation; use descriptive ones like "beat_level_10" not "ach_1"
  2. Check already_unlocked — Don’t show the popup again if the player already has it
  3. Cache for UI — Use get_achievements_cached() in menus to avoid network calls on every open
  4. Hidden achievements — Great for spoiler-sensitive content; players see ”???” until they unlock it
  5. Fetch on game start — Call get_achievements() early so the cache is warm