Godot SDK for Aptabase — privacy-first, open source analytics for games and apps.
- 🎮 Made for games — ships as a drop-in autoload singleton
- 🚀 Fully async — events are sent in the background with
HTTPRequest; tracking never blocks your game loop - 🛡️ Crash-proof — GDScript has no exceptions, so every network failure is caught via result codes, logged, and retried. A failed HTTP call can never affect your game
- 🔒 Privacy-first — no personal data collection
- 🔄 Auto-batching — efficient batching (up to 25 events) and periodic flushing
- Godot 4.2+
- Copy the
addons/aptabasefolder into your project'saddons/directory. - Enable the plugin in Project → Project Settings → Plugins.
That registers an autoload singleton named Aptabase, available globally from any script.
You can also open this repository directly in Godot — it's a minimal project with the plugin already enabled.
func _ready() -> void:
# Initialize once, as early as possible (e.g. your main scene).
Aptabase.init("A-EU-0000000000", {"app_version": "1.2.3"})
# Track a simple event.
Aptabase.track("app_started")
# Track an event with properties.
Aptabase.track("level_completed", {
"level": 3,
"score": 1200,
"character": "knight",
})Get your app key from the Aptabase dashboard.
init() takes the app key and an optional options dictionary:
Aptabase.init("A-EU-0000000000", {
"app_version": "1.2.3", # Your app version (default "1.0.0")
"is_debug": false, # Debug flag (default: OS.is_debug_build())
"max_batch_size": 25, # Max events per request (max & default 25)
"flush_interval": 10.0, # Auto-flush interval in seconds (default 10)
"timeout": 30.0, # HTTP timeout in seconds (default 30)
"base_url": "", # Required for self-hosted A-SH-* keys
})If the configuration is invalid (missing/malformed app key, unresolved base URL),
analytics are disabled silently — an error is logged via push_error, and
track()/flush() become no-ops. Your game keeps running either way.
Your app key determines the server region:
A-EU-*— European serversA-US-*— US serversA-SH-*— Self-hosted (pass your instance URL via thebase_urloption)
| Method | Description |
|---|---|
Aptabase.init(app_key: String, options := {}) |
Configure and start the client. Call once. |
Aptabase.track(event_name: String, props := {}) |
Queue an event (non-blocking). |
Aptabase.flush() |
Send queued events now. |
track()only appends to an in-memory queue and returns immediately.- Events are flushed on a timer (
flush_interval), as soon as a batch fills up (max_batch_size), and once more on quit (best-effort — the async round-trip may not finish during shutdown, so the last unsent batch can be lost). - Each batch is sent through a short-lived
HTTPRequestnode that is freed as soon as the request finishes, so requests never block the main thread. - Only one request is in flight at a time. When it succeeds, any backlog is drained immediately; on failure it waits for the next tick, so the client never hammers the network or stacks up requests.
- On a network error or a
5xxresponse, the batch is re-queued for the next flush. A4xxresponse (bad key/payload) is dropped, since retrying can't help.
The queue is hard-capped at 1000 events. While offline, track() keeps the
most recent 1000 events and drops the oldest — memory stays bounded no matter how
long the game runs without a connection, and only a single HTTPRequest node is
ever alive. No leaks, no unbounded growth, no OOM.
In short: a failed HTTP call is caught, logged, and never propagates to your game.