How to Debug gdext Extensions Using Godot’s Built-in Debugger and Breakpoints

To debug gdext extensions in Godot, compile your Nim library with the debug target to generate symbol information, optionally enable the Dev.debugCallbacks flag for verbose lifecycle logging, and use the EngineDebugger API to set breakpoints or pause execution programmatically.

The gdext-nim bindings allow you to debug Nim-based GDExtensions directly within the Godot editor. By exposing Godot’s EngineDebugger class and compiling with debug symbols, you can leverage breakpoints, step-through debugging, and detailed logging without leaving the editor environment.

Compile with Debug Symbols and Logging

Before you can hit breakpoints, you must build the shared library with debug information. In src/gdext/private/buildsettings.nim, the Target.debug setting controls symbol generation, while the Dev_debugCallbacks boolean enables optional lifecycle logging.

Configure the Debug Target

Create or modify your config.nims to specify the debug target:


# config.nims

import gdext/buildconf
let setting = BuildSettings(
  target: debug,               # ← enables debug symbols for the shared library

  platform: "linux",
  arch: "x86_64")
configure(setting)

When target is set to debug, the compiler passes -d:debug and includes the necessary symbol tables that Godot uses to map machine code back to Nim source lines. Rebuild the extension with nimble build to apply these settings.

Enable Verbose Lifecycle Logs

For detailed console and file logging of object instantiation, creation, and freeing, enable the Dev.debugCallbacks flag in src/gdext/private/buildsettings.nim:


# src/gdext/private/buildsettings.nim

Dev_debugCallbacks {.booldefine: "Dev.debugCallbacks".} = on

With this flag active, src/gdext/private/debugging.nim compiles a GroupLogger that writes entries such as Instantiate, CREATE, and FREE to both the console and a <project>.callbacks.log file. This helps trace object lifecycles even when the debugger is not paused.

Insert and Manage Breakpoints Programmatically

The EngineDebugger wrapper in src/gdext/classes/gdenginedebugger.nim exposes Godot’s native breakpoint API, allowing you to manipulate breakpoints from Nim code without touching the editor UI.

To insert a breakpoint at a specific line:

import gdext/classes/gdenginedebugger

proc setupBreakpoints() =
  let sourcePath = "res://my_extension/main.nim".toStringName
  EngineDebugger.insertBreakpoint(line = 42, source = sourcePath)

Other available methods include:

  • EngineDebugger.removeBreakpoint(line, source) – Removes a single breakpoint.
  • EngineDebugger.clearBreakpoints() – Clears all registered breakpoints.
  • EngineDebugger.isBreakpoint(line, source) – Returns true if a breakpoint exists at the given location.

These calls forward directly to Godot’s EngineDebugger.insert_breakpoint and behave identically to breakpoints set via the editor gutter.

Trigger the Debugger at Runtime

When your extension reaches a critical state that requires immediate inspection, call the debug() method to force the Godot debugger to pause:

proc heavyCalculation() =
  # Perform work...

  if result < 0:
    EngineDebugger.debug(canContinue = true, isErrorBreakpoint = false)

The parameters control pause behavior:

  • canContinue – When true, Godot resumes execution after the next step/continue command.
  • isErrorBreakpoint – When true, the debugger pauses even if the user has disabled breakpoints globally.

For custom script language integrations, you can also use EngineDebugger.scriptDebug(language, ...) to hook into the script debugging pipeline.

Use the Godot Editor UI

Once your extension is compiled with debug symbols, the Godot Script Editor integrates seamlessly with Nim source files. In src/gdext/classes/gdcodeedit.nim, the setLineAsBreakpoint method handles UI interactions:

  1. Open Script → Editor → Script Editor.
  2. Click the gutter next to any line in your .nim file to toggle a breakpoint.
  3. Run the scene; execution pauses when the line is reached, displaying the call stack and local variables.

The UI maps breakpoints using the exact source path strings (e.g., res://my_extension/main.nim) that you pass to EngineDebugger.insertBreakpoint, ensuring consistency between programmatic and visual breakpoint management.

Inspect Runtime Data and Logs

While execution is paused, the Godot debugger’s Locals, Members, and Inspect panels display Nim object fields because gdext generates GDExtension-compatible class bindings. You can examine ref object properties and step through _ready, _process, and custom methods.

If you enabled Dev.debugCallbacks, check the console or the <project>.callbacks.log file for chronological event streams:


[2026-03-02 14:12:03] DEBUG: Instantiate: MyClass:12345678
[2026-03-02 14:12:03] NOTICE: CREATE: MyClass:12345678

Use the addDebugInfo(self) helper from src/gdext/private/debugging.nim to tag objects with their Nim type names for easier identification in these logs.

Complete Debugging Example

The following skeleton demonstrates a debug-enabled extension with programmatic breakpoints and lifecycle logging:


# src/main.nim

import gdext
import gdext/classes/[gdenginedebugger, gdnode]
import gdext/private/debugging

type MyNode = ref object of Node

proc _ready(self: MyNode) =
  # Log initialization when Dev.debugCallbacks is on

  addDebugInfo(self)
  debug "MyNode._ready executed"
  
  # Set a breakpoint at line 15 of this file

  EngineDebugger.insertBreakpoint(15, "res://src/main.nim".toStringName)

proc _process(self: MyNode, delta: float64) =
  if delta > 0.5:
    # Pause execution when frame time spikes

    EngineDebugger.debug(canContinue = true)

gdextension(MyNode, "MyNode")

Compile with nimble build -d:debug, run the project in the Godot editor, and add a MyNode instance to your scene. The debugger will pause at line 15 when _ready runs, and again in _process whenever the delta exceeds 0.5 seconds.

Summary

  • Compile with target: debug in your config.nims to generate symbols required for breakpoint mapping.
  • Enable Dev.debugCallbacks in src/gdext/private/buildsettings.nim to write detailed lifecycle logs to console and <project>.callbacks.log.
  • Use EngineDebugger.insertBreakpoint and related methods from src/gdext/classes/gdenginedebugger.nim to manage breakpoints programmatically.
  • Call EngineDebugger.debug to trigger immediate pauses at runtime based on application logic.
  • Interact via the Script Editor UI, which uses CodeEdit.setLineAsBreakpoint from src/gdext/classes/gdcodeedit.nim to toggle visual breakpoints that map directly to Nim source lines.

Frequently Asked Questions

How do I enable debug symbols for gdext-nim extensions?

Set target: debug in your config.nims BuildSettings and recompile. This configures the Nim compiler to include debug information in the shared library, allowing Godot to map machine instructions back to your Nim source code for breakpoints and stack traces.

Can I set breakpoints programmatically instead of using the Godot UI?

Yes. Use EngineDebugger.insertBreakpoint(line, source) where source is a StringName containing the absolute res:// path to your Nim file. This method, defined in src/gdext/classes/gdenginedebugger.nim, functions identically to clicking the gutter in the Script Editor.

What is the difference between EngineDebugger.debug() and insertBreakpoint()?

insertBreakpoint() registers a persistent breakpoint at a specific line that triggers whenever execution reaches that location, while debug() immediately pauses execution at the call site regardless of line numbers. Use debug() for conditional pauses based on runtime state, and insertBreakpoint() for static line-based debugging.

Where are lifecycle logs saved when Dev.debugCallbacks is enabled?

When the Dev.debugCallbacks flag is set to on in src/gdext/private/buildsettings.nim, the GroupLogger in src/gdext/private/debugging.nim writes lifecycle events (instantiate, create, free) to both the Godot console and a file named <project>.callbacks.log in your project root.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →