Skip to content

Best Practices

These are the do’s and don’ts that save you from headaches down the road. Follow these patterns and your analytics data will be clean, your game will stay fast, and your players’ privacy will be protected.

Consistent event names make your data queryable. Inconsistent names create noise.

# snake_case, verb_noun pattern
QuestData.track("level_complete", {"level": 5})
QuestData.track("item_purchased", {"item": "sword"})
QuestData.track("boss_defeated", {"boss": "dragon"})
QuestData.track("tutorial_skipped", {"step": "combat"})
# Inconsistent naming — don't do this
QuestData.track("LevelComplete", {}) # PascalCase
QuestData.track("level-complete", {}) # kebab-case
QuestData.track("level complete", {}) # spaces
QuestData.track("lvl_comp", {}) # abbreviated
QuestData.track("completed level 5", {}) # natural language
PatternExampleUse case
noun_verblevel_completeMost events
noun_nounshop_openedUI navigation
noun_adjectiveplayer_deadState changes

  • State changes — level_start, level_complete, level_fail
  • Milestones — every 100th click, first purchase, prestige
  • Business events — purchases, subscription changes, ad views
  • Errors — crashes, corrupted data, unexpected states
  • Session lifecycle — game_start, game_end (SDK does this automatically)
  • Every frame — FPS data should be sampled (every 10-30 seconds), not per-frame
  • Every input — Don’t track every tap, click, or keypress
  • PII — Never send real names, email addresses, phone numbers, or IP addresses
  • Passwords or tokens — Never include auth data in event properties
  • Large objects — Don’t serialize your entire game state into properties

Ask yourself: “Will I look at this in the dashboard?” If the answer is no, don’t track it.


The SDK is designed to be lightweight, but you can still cause problems with bad patterns.

  1. track() is instant — it just adds to an in-memory queue
  2. Events are batched and sent every 10 seconds (or when 10+ events queue up)
  3. If the server is unreachable, events are saved to disk and retried later
  4. On game exit, unsent events are automatically persisted
DoDon’t
Call track() freely — it’s non-blockingDon’t call track() in _process() every frame
Use milestones (every 100th action)Don’t track continuous values (mouse position)
Keep properties small (IDs, numbers)Don’t serialize large dictionaries
Let the SDK handle batchingDon’t call flush() after every track()
LimitValue
Max queue size10,000 events
Max event size64 KB
Max properties per event50
Max property value size1 KB

If the queue exceeds 10,000 events, the oldest events are dropped. This only happens if the server is unreachable for a very long time.


Separate your development data from production data using environment-specific API keys.

  1. Create two API keys in the dashboard under Configuration > API Keys:

    • Development key — for local testing
    • Production key — for released builds
  2. The SDK auto-selects based on build type:

    • OS.is_debug_build() → uses quest_data/dev_api_url
    • Release build → uses quest_data/prod_api_url
  • Test events don’t pollute your production analytics
  • You can reset your dev database without affecting real data
  • Retention and funnel data stay clean

Quest Data is designed for privacy-first analytics. But you still need to follow the rules.

  • Player ID — auto-generated UUID (not linked to real identity)
  • Session ID — random, changes every game launch
  • Events — only what you explicitly track()
  • Device info — OS name, not hardware serials or IMEI
RequirementHow
ConsentShow a consent dialog before enabling tracking
Data accessLet players request their data via player ID
Data deletionDelete player data on request (dashboard or API)
No PIINever include real names, emails, addresses in events
DisclosureMention analytics in your privacy policy

Before shipping, grep your codebase for track( calls and verify none of these appear in properties:

  • Email addresses
  • Real names
  • Phone numbers
  • Physical addresses
  • IP addresses (the SDK never sends these)
  • Device serial numbers

Your API key identifies your game to the server. Treat it with basic care:

DoDon’t
Store in Godot ProjectSettingsHardcode in GDScript files
Use different keys for dev/prodUse the same key everywhere
Rotate keys if compromisedShare keys in public repos

The API key is not a secret in the traditional sense — it’s embedded in your game binary which players can decompile. It’s more like a project identifier. The server validates it but doesn’t expose sensitive data through it.

  • Send events (POST /v1/track)
  • Read configs (GET /v1/config)
  • Submit scores, unlock achievements, save games
  • Access other games’ data
  • Read other players’ data
  • Delete data
  • Access the admin dashboard

MistakeProblemFix
Tracking in _process()Thousands of events per secondTrack milestones or sample periodically
Different event names for same thingFragmented dataUse a naming convention
Tracking without consentGDPR violationAdd consent dialog
Using one API key everywhereTest data in productionCreate separate dev/prod keys
Logging stack traces as eventsWrong toolUse track_error() for crashes, log_error() for logs
Huge propertiesWasted bandwidth, potential rejectionKeep properties small, use IDs