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 Theme resource (popup_theme)

Installation

  1. Copy the addons/forge_logger directory from this repository into the addons/ folder of your Godot project.
  2. In the Godot editor open Project → Project Settings → Plugins and enable Forge Logger.
  3. Configure the plugin under Project Settings → forge_logger/:
SettingDescription
base_urlIngest API base URL, e.g. https://ingest.forgelogger.dev.
api_keyYour 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_nameName shown in the dashboard next to reports.
game_versionSemantic version string, e.g. 1.0.0.
build_hashGit commit hash or build identifier.
environmentdevelopment, staging, or production.
enable_logsAttach the Godot log file to each report.
enable_screenshotCapture a screenshot automatically with each report. Off by default.
auto_start_sessionStart a session immediately on launch (recommended).
collect_device_infoInclude the device model and locale in session telemetry. Off by default (opt-in).
enable_report_hotkeyRegister the F8 bug-report popup. On by default; turn off if you only want the web feedback form.
feedback_base_urlHost of the public feedback form opened by open_feedback(). Empty uses the hosted default, https://forgelogger.dev.
popup_themeA 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.

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 its ETag). Call ForgeLogger.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 (raffle on 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 (a Theme, overrides popup_theme), ui_scale (0 = scale with the screen height), layer (CanvasLayer order).
  • Signals: feedback_submitted(link_token, result) — result.status is "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:

FilePurpose
forge_logger.gdAutoload singleton – the public API surface.
session_manager.gdCreates and tracks the current session and notices an unclean exit (no crash capture).
upload_manager.gdCollects log files and manages pre-signed upload URLs.
report_builder.gdAssembles the report payload from all collected data.
report_queue.gdOffline queue on disk for bug reports and feedback answers, retried on the next launch.
feedback_manager.gdFetches and caches feedback forms, submits answers.
ui/feedback_popup.tscnThe in-game feedback form.
http_client.gdLow-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, and custom_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 environment setting – tag builds as development / staging / production so 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.