Godot 4 Plugin
The Forge Logger plugin for Godot 4 integrates bug reporting, player feedback and runtime diagnostics directly into your game. Sessions are created automatically on startup, bug reports can be triggered programmatically or via an in-game popup, feedback forms render natively in the game, and all data is sent to the Forge Logger backend.
New here? The 10-minute quickstart walks you from install to your first bug report. This page is the full settings and API reference.
Features
- Automatic session creation on game start (linked to build version and platform)
- Programmatic bug reports with title, description, severity, tags, and custom data
- User-facing in-game report popup (
show_report_popup()) - In-game feedback form (
show_feedback()) — star rating, price question, custom questions and an opt-in contact, rendered from the form you edit in the dashboard; mouse, keyboard and gamepad - Log file and screenshot upload via pre-signed URLs
- Offline retry queue – bug reports and feedback answers that could not be sent are stored on disk and sent on the next launch (once the backend answers), without duplicates. “Try again later” (offline, rate limited, a server error) never uses up an entry’s attempts — such an entry expires after 30 days; a full queue (100 entries) gives up bug reports before a player’s answer
- Requests run side by side – a report, an event batch and a feedback answer never block each other
- Popups styled through a
Themeresource (popup_theme)
Installation
- Copy the
addons/forge_loggerdirectory from this repository into theaddons/folder of your Godot project. - In the Godot editor open Project → Project Settings → Plugins and enable Forge Logger.
- Configure the plugin under Project Settings → forge_logger/:
| Setting | Description |
|---|---|
base_url | Ingest API base URL, e.g. https://ingest.forgelogger.dev. |
api_key | Your project’s logger token (flg_…) from Project → Tokens. The token carries the project scope — no separate project ID is needed. Required for the hosted backend; leave empty only for self-hosted backends without auth. |
game_name | Name shown in the dashboard next to reports. |
game_version | Semantic version string, e.g. 1.0.0. |
build_hash | Git commit hash or build identifier. |
environment | development, staging, or production. |
enable_logs | Attach the Godot log file to each report. |
enable_screenshot | Capture a screenshot automatically with each report. Off by default. |
auto_start_session | Start a session immediately on launch (recommended). |
collect_device_info | Include the device model and locale in session telemetry. Off by default (opt-in). |
enable_report_hotkey | Register the F8 bug-report popup. On by default; turn off if you only want the web feedback form. |
feedback_base_url | Host of the public feedback form opened by open_feedback(). Empty uses the hosted default, https://forgelogger.dev. |
popup_theme | A Theme resource (res://….tres) for the report popup and the in-game feedback form. Empty uses the built-in look. |
Usage
Automatic session start
When auto_start_session is enabled the plugin calls start_session() as part of its _ready()
lifecycle. No extra code is needed.
To start a session manually (when auto_start_session is off):
await ForgeLogger.start_session()
Programmatic bug report
ForgeLogger.capture_bug({
"title": "Player stuck in wall",
"description": "Dash caused collision lock near checkpoint 3",
"severity": "high",
"tags": ["movement", "collision"],
"custom_data": {
"scene": get_tree().current_scene.name,
"player_position": str(player.global_position),
}
})
User-facing report popup
ForgeLogger.show_report_popup()
This opens a built-in UI where the player can describe the bug. The plugin collects the form data and submits a report automatically.
Feedback form link
ForgeLogger.open_feedback("AbCdEfGh12345678") # link token from the dashboard's Feedback tab
Opens the public feedback form (forgelogger.dev/f/<token>, or feedback_base_url if set) in
the system browser — a new tab in web exports. When a session is active, the URL carries
session, build, playtime, the current scene and the checkpoint from
set_context_provider, so the response comes back linked to what the player did in that
session; with no active session it opens the plain link. A second argument adds further query
parameters, e.g. ForgeLogger.open_feedback(token, {"lang": "de"}) — it never overrides
session, build or playtime (a scene or checkpoint in it replaces the automatic one).
In-game feedback form
func _on_feedback_button_pressed() -> void:
ForgeLogger.show_feedback("AbCdEfGh12345678", {"pause": true}) # link token from the dashboard
var submitted: bool = (await ForgeLogger.feedback_closed)[1]
Renders the same form as the web page, natively in the game — no browser, no leaving
fullscreen, playable on a gamepad or a Steam Deck. The questions come from the server
(GET /v1/ingest/feedback/:linkToken), so editing them in the dashboard needs no new build.
Create a separate link for the game (e.g. “In game”) so the overview can tell its answers apart;
they are marked in game and linked to the player’s session, build, playtime, scene and
checkpoint.
Feedback and telemetry
Every answer sent with a session stores a snapshot of it: playtime, scene, checkpoint, build,
platform, how many bug reports the session had and whether it crashed (at least one
crash event). The dashboard’s Segments (Indie and Studio) split the answers by playtime,
build, platform, crash or link — “players over 20 minutes rate 4.3★, under 5 minutes 2.1★” —
and the numbers stay the same after event retention removes the session’s events. To make
this work for your game:
# Where the player is — sent with reports AND feedback answers
ForgeLogger.set_context_provider(func() -> Dictionary:
return {"checkpoint": "boss_%d" % boss_index})
# The plugin does not catch crashes by itself yet: report one as a `crash` event
ForgeLogger.post_event("crash", {"reason": "save file corrupted"})
Without a checkpoint from the provider, the name of the session’s last checkpoint event
already on the server is used (ForgeLogger.post_event("checkpoint", {"name": "boss_room"});
record_event() only sends its events with the next report or send_events()).
- Offline: the last form fetched is cached in
user://(revalidated with itsETag). CallForgeLogger.prefetch_feedback(token)early — at game start — and the form opens even with no network (a festival booth). An answer is written to the queue on disk before it is sent, so quitting mid-request loses nothing; one given offline is sent on the next launch. If the server cannot store its contact (or the form no longer asks for one), a queued answer is sent without the contact rather than lost. - Raffle: the form offers the raffle only on the terms the server sends (
raffleon the form): after the entry deadline the checkbox gives way to “Raffle entries have closed”, and a “Full raffle rules” button opens the rules page in the player’s language. - Gamepad: the D-pad walks the form, A picks, B closes — also when your project
never mapped the pad to
ui_accept/ui_cancel. Everything except the text fields can be answered with the pad alone; typing on a Steam Deck needs its on-screen keyboard (Steam + X). - Language: buttons, consents and messages are in English — feedback forms are English-only for now. Your questions are shown as you wrote them.
- Options:
pause(pause the game while open),theme(aTheme, overridespopup_theme),ui_scale(0 = scale with the screen height),layer(CanvasLayer order). - Signals:
feedback_submitted(link_token, result)—result.statusis"sent"or"queued";feedback_closed(link_token, submitted).
Styling
Pass a Theme (or set popup_theme). The form uses these theme type variations, so you can
restyle it without touching the rest of your UI: ForgeLoggerPanel (PanelContainer),
ForgeLoggerTitle, ForgeLoggerQuestion, ForgeLoggerHint, ForgeLoggerError,
ForgeLoggerConsentText (Label), ForgeLoggerPrimaryButton, ForgeLoggerChoice (Button — answer
buttons), ForgeLoggerStar (Button, icons star_filled / star_empty) and ForgeLoggerConsent
(CheckBox). Anything your theme leaves out keeps the built-in look.
Utilities
# Retry everything that failed while offline (bug reports and feedback answers)
ForgeLogger.retry_queued()
# Verify connectivity to the backend
await ForgeLogger.check_health()
Plugin Structure
You do not need to understand the internals to use the plugin, but knowing where things live helps if you want to extend it:
| File | Purpose |
|---|---|
forge_logger.gd | Autoload singleton – the public API surface. |
session_manager.gd | Creates and tracks the current session and notices an unclean exit (no crash capture). |
upload_manager.gd | Collects log files and manages pre-signed upload URLs. |
report_builder.gd | Assembles the report payload from all collected data. |
report_queue.gd | Offline queue on disk for bug reports and feedback answers, retried on the next launch. |
feedback_manager.gd | Fetches and caches feedback forms, submits answers. |
ui/feedback_popup.tscn | The in-game feedback form. |
http_client.gd | Low-level HTTP wrapper around the REST API; each request in flight has its own HTTPRequest. |
Custom Integration
ForgeLoggerHttpClient can be used independently of the autoload singleton. This is useful if
you want to send custom events or query data:
var client := ForgeLoggerHttpClient.new()
add_child(client) # requests run on HTTPRequest children of the client
client.configure("https://ingest.forgelogger.dev", "<TOKEN>")
# Available methods:
# client.start_session(payload)
# client.create_upload(payload)
# client.submit_report(payload)
# client.post_events(events_array)
# client.get_feedback_form(link_token, etag)
# client.submit_feedback(payload)
# client.check_health()
Best Practices
- Enrich reports – always provide
title,description,severity, andcustom_data(scene name, checkpoint, player position). Detailed reports are much easier to reproduce. - Do not hard-code
api_key– load it from an environment variable or an encrypted store. Committed API keys can be rotated but the history remains in git. - Use
environmentsetting – tag builds asdevelopment/staging/productionso reports are easy to filter in the dashboard.
For endpoint details and examples in other languages see the API Reference. To poke at the same endpoints by hand, grab the Postman & Bruno collections.