How to Create a New Extension Class That Inherits From Built-In Godot Classes in gdext-nim
To create a new extension class that inherits from built-in Godot classes, define a Nim type as a ptr object of Node (or any engine class), annotate it with {.gdsync.}, and register it in your bootstrap file to expose it to the engine.
The gdext-nim library (available at godot-nim/gdext-nim) provides a type-safe Nim interface to the GDExtension API. Creating a new extension class that inherits from built-in Godot classes involves using specific pragmas that handle binding registration, property exposure, and lifecycle callbacks automatically.
Project Setup and Class Scaffolding
Start by generating a new extension project using the CLI wizard. The gdextwiz new-extension command creates a skeleton project including a src/bootstrap.nim registration file and template classes.
According to the wizard source in src/gdext/wizard/subcommands/extension.nim, the generated template includes a ready-made example class at src/classes/gdmyclass.nim that demonstrates the inheritance pattern.
Defining the Extension Class
Create a new Nim file in src/classes/ and define your type with the {.gdsync.} pragma. This pragma, implemented in src/gdext/private/userclass/procs.nim, marks the type for engine registration and generates the necessary glue code.
# src/classes/gdmyclass.nim
import gdext
import gdext/classes/gdNode # Import the built-in class you want to inherit from
type MyClass* {.gdsync.} = ptr object of Node
message* {.gdexport.}: string = "This is MyClass."
address: uint64
Key components of this definition:
ptr object of Node— Declares inheritance from the built-inNodeclass (defined insrc/gdext/classes/gdnode.nim). You can substituteNodewith any engine class likeSprite2DorCharacterBody2Dby importing the correspondinggdext/classes/gdSprite2Dmodule.{.gdsync.}— Required pragma that exposes the type and its members to Godot's ClassDB.{.gdexport.}— Makes the field visible as a property in the Godot editor inspector.
Implementing Methods, Signals, and Lifecycle Callbacks
Define methods using Nim's method syntax combined with the {.gdsync.} pragma to implement Godot's virtual callbacks like _ready and _process.
# Lifecycle callbacks
method ready(self: MyClass) {.gdsync.} =
echo "MyClass is ready"
method process(self: MyClass; delta: float) {.gdsync.} =
# Per-frame logic here
discard
For signals, declare a proc with the {.signal.} pragma. The return type must be Error, and you emit the signal by calling the proc:
proc mySignal(self: MyClass): Error {.gdsync, signal.}
method ready(self: MyClass) {.gdsync.} =
assert self.mySignal() == ok # Emit the signal
To expose custom getters and setters for properties, define procs with {.gdsync.} and bind them using gdexport:
proc address_readable(self: MyClass): string {.gdsync, name: "get_address_readable".} =
self.address.toHex.insertSep(' ', 4)
gdexport "address_readable",
address_readable,
proc (self: MyClass; value: string) = (discard) # Setter (optional)
Note that onInit can serve as a constructor-style hook for initialization logic, but it does not require {.gdsync.} because it operates outside the GDExtension API boundary.
Registering the Class with Godot
Every extension class must be registered in the bootstrap file to be recognized by the engine. The generated src/bootstrap.nim contains a register_classes proc executed at the initialize_scene level.
# src/bootstrap.nim
import gdext
import classes/gdmyclass
proc register_classes {.execon: initialize_scene.} =
register MyClass # <-- Add your class here
The register template, defined in the core gdext.nim and utilizing src/gdext/private/classindex.nim, stores the class metadata in Godot's ClassDB at the specified initialization level.
Complete Working Example
Below is a minimal, self-contained class inheriting from Sprite2D that moves right every frame:
# src/classes/gdplayer.nim
import gdext
import gdext/classes/gdSprite2D
import std/math
type Player* {.gdsync.} = ptr object of Sprite2D
speed* {.gdexport.}: float = 300
method ready(self: Player) {.gdsync.} =
echo "Player ready with speed: ", self.speed
method process(self: Player; delta: float) {.gdsync.} =
self.position.x += self.speed * delta
Register it in src/bootstrap.nim:
import gdext
import classes/gdplayer
proc register_classes {.execon: initialize_scene.} =
register Player
Build and run using the CLI:
gdextwiz run
The command compiles the Nim code into a GDExtension library, copies it to your Godot project, and launches the editor. The Player node will appear in the editor with an exposed Speed property.
Summary
- Inheritance — Use
ptr object of Node(or any built-in class fromgdext/classes/) to extend engine functionality. - Exposure — Apply
{.gdsync.}to types and methods, and{.gdexport.}to fields you want visible in the editor. - Lifecycle — Implement
ready,process, and other virtuals as Nim methods with{.gdsync.}. - Registration — Call
register YourClassinside theregister_classesproc inbootstrap.nimmarked with{.execon: initialize_scene.}. - CLI Workflow — Use
gdextwiz new-extensionto scaffold andgdextwiz runto build and test.
Frequently Asked Questions
How do I choose which built-in Godot class to inherit from?
Import the corresponding module from gdext/classes/ (e.g., import gdext/classes/gdCharacterBody2D) and declare your type as ptr object of CharacterBody2D. The inheritance chain follows Godot's native hierarchy exactly, so inheriting from Node gives you scene tree participation, while Node2D or Node3D adds spatial properties.
What is the difference between {.gdsync.} and {.gdexport.}?
{.gdsync.} is a type or method-level pragma that registers the symbol with Godot's extension system (handled in private/userclass/procs.nim), enabling the engine to instantiate and call it. {.gdexport.} is a field-level annotation that generates property bindings for the editor inspector, creating getters and setters automatically unless you provide custom ones.
Why does my class need to be a ptr object?
Godot's GDExtension API manages object lifecycles through pointers. The ptr object allocation model ensures memory compatibility with the engine's reference counting system. The gdsync macro generates the necessary boilerplate to handle reference counting and pointer safety automatically.
How do I expose a property with custom getter and setter logic?
Define a proc with {.gdsync.} and an explicit name pragma (e.g., name: "get_custom"), then bind it using the gdexport macro with the property name, getter proc, and an optional setter proc. This pattern is demonstrated in the template gdmyclass.nim and allows you to transform internal data (like converting a uint64 address to a readable hex string) while maintaining editor visibility.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →