Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when building native extensions for Godot — godot-cpp (C++) or gdext (Rust), binding classes, building, and GDScript/C# interop
.claude/skills/jame581-gdextension/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 60% | 0% |
| case-20 | ✗→✓ | ▲ Improved | 114% | 0% |
| case-22 | ✗→✓ | ▲ Improved | 31% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 301% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 40% | 0% |
Run native C++ (or Rust) in Godot as a shared library without recompiling the engine. Use it for performance-critical code, wrapping existing C/C++ libraries, or language bindings.
> Related skills: csharp-godot for when C# is enough, gdscript-advanced for GDScript performance idioms first, godot-optimization for profiling before going native, addon-development for distributing the result, export-pipeline for shipping the binaries.
Reach for GDScript or C# for almost all game logic. Choose GDExtension only when you genuinely need it:
Contrast with C++ modules, which are compiled into the engine and therefore require shipping a custom engine binary. GDExtension's key advantage is that it runs against a stock Godot — you distribute just a shared library. It is "more complicated to use than GDScript and C#," so don't reach for it by default.
> ⚠️ Changed in Godot 4.7: Custom text servers are no longer a GDExtension use case — TextServer GDExtension build support was removed, so a custom TextServer must be compiled into the engine as a C++ module. See GH-117056.
bashmkdir gdextension_example && cd gdextension_example git init # IMPORTANT: use the godot-cpp branch matching your target engine version (e.g. 4.3), # not the literal "4.x". git submodule add -b 4.3 https://github.com/godotengine/godot-cpp cd godot-cpp && git submodule update --init && cd ..
Directory layout:
gdextension_example/
├── project/ # demo project to test the extension
│ └── bin/example.gdextension
├── godot-cpp/ # C++ bindings (submodule)
└── src/
├── register_types.{h,cpp}
└── gdexample.{h,cpp}Build with scons platform=<platform> (omit the platform to target the current one; default build is debug). The official SConstruct is a downloadable file from the C++ tutorial rather than hand-rolled here — follow godot-cpp's build docs. SCons is the official path; godot-cpp also supports CMake.
> Godot 4.7+: Upstream's reference GDExtension interface files (e.g. gdextension_interface.h) now live in the godot-headers repository instead of godot-cpp (GH-115401). godot-cpp consumes them from there, so the submodule workflow above is unchanged — this only matters if you vendor the raw interface headers directly (e.g. for a custom language binding).
Header (gdexample.h):
cpp#pragma once #include <godot_cpp/classes/sprite2d.hpp> namespace godot { class GDExample : public Sprite2D { GDCLASS(GDExample, Sprite2D) private: double time_passed = 0.0; double amplitude = 10.0; double speed = 1.0; protected: static void _bind_methods(); public: void _process(double delta) override; void set_amplitude(double p_amplitude); double get_amplitude() const; void set_speed(double p_speed); double get_speed() const; }; }
Bindings (gdexample.cpp — _bind_methods):
cppvoid GDExample::_bind_methods() { ClassDB::bind_method(D_METHOD("get_amplitude"), &GDExample::get_amplitude); ClassDB::bind_method(D_METHOD("set_amplitude", "p_amplitude"), &GDExample::set_amplitude); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "amplitude"), "set_amplitude", "get_amplitude"); ClassDB::bind_method(D_METHOD("get_speed"), &GDExample::get_speed); ClassDB::bind_method(D_METHOD("set_speed", "p_speed"), &GDExample::set_speed); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_speed", "get_speed"); ADD_SIGNAL(MethodInfo("position_changed", PropertyInfo(Variant::OBJECT, "node"), PropertyInfo(Variant::VECTOR2, "new_pos"))); }
The patterns:
GDCLASS(Class, Parent) — first line of every native class body; wires up the type into Godot's ClassDB.ClassDB::bind_method(D_METHOD("name", "arg"), &Class::method) — exposes a method (and names its arguments) so GDScript/C#/the editor can call it.ADD_PROPERTY(PropertyInfo(...), setter, getter) — registers an Inspector property; bind the getter and setter first, then reference them here by name.PROPERTY_HINT_RANGE with "0,20,0.01" turns the Inspector field into a slider (min, max, step).ADD_SIGNAL(MethodInfo("name", PropertyInfo(...), ...)) — declares a signal with typed arguments; emit it from code with emit_signal("position_changed", this, new_pos).> ⚠️ Changed in Godot 4.7: The GDExtension interface functions object_cast_to and classdb_get_class_tag are deprecated in favor of is_class-based casts. Binding libraries (godot-cpp, gdext) handle this internally — but native code that calls these interface functions directly should migrate its cast paths. See GH-119254.
Entry point (register_types.cpp):
cppvoid initialize_example_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) return; GDREGISTER_CLASS(GDExample); } void uninitialize_example_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) return; } extern "C" { GDExtensionBool GDE_EXPORT example_library_init( GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); init_obj.register_initializer(initialize_example_module); init_obj.register_terminator(uninitialize_example_module); init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); return init_obj.init(); } }
The .gdextension file (project/bin/example.gdextension):
ini[configuration] entry_symbol = "example_library_init" compatibility_minimum = "4.3" reloadable = true [libraries] macos.debug = "res://bin/libgdexample.macos.template_debug.dylib" macos.release = "res://bin/libgdexample.macos.template_release.dylib" windows.debug.x86_64 = "res://bin/gdexample.windows.template_debug.x86_64.dll" windows.release.x86_64 = "res://bin/gdexample.windows.template_release.x86_64.dll" linux.debug.x86_64 = "res://bin/libgdexample.linux.template_debug.x86_64.so" linux.release.x86_64 = "res://bin/libgdexample.linux.template_release.x86_64.so"
The exported extern "C" symbol name must equal entry_symbol, or the extension won't load. Register classes with GDREGISTER_CLASS, gated on MODULE_INITIALIZATION_LEVEL_SCENE. The [libraries] keys are platform.feature.arch tags; template_debug / template_release distinguish build configs. Optional sections: [icons] (per-node editor icon) and [dependencies] (extra libs copied on export).
> Godot 4.7+: The raw GDExtension interface adds refcount-aware construction and registration entry points — classdb_construct_object3 and classdb_register_extension_class6 (GH-118214). Binding libraries built against 4.7 headers use them automatically; they only concern code that calls the interface directly.
Forward-but-not-backward. An extension targeting 4.2 works in 4.3, but one targeting 4.3 will not load in 4.2. Exception: extensions targeting 4.0 do not work in 4.1+.
reloadable = true hot reload works in debug builds only.compatibility_minimum to the lowest engine version you actually support — too low and the extension fails to load at runtime.template_release binaries present, or the native node type simply won't exist at runtime.> ⚠️ Changed in Godot 4.7: Object.is_class() changed its class parameter type from String to StringName for performance. GDScript is unaffected and C# gained a compatibility method, but GDExtension binaries compiled against the old signature rely on the engine's compatibility mapping — rebuild against bindings matching your target version when updating to 4.7. See the 4.7 migration guide.
After building and placing the .gdextension file, the native class appears as a normal node type: bound properties show up in the Inspector (range hints become sliders) and signals appear in the Node dock.
gdscriptextends Node func _ready(): var node := GDExample.new() node.speed = 2.0 node.position_changed.connect(_on_position_changed) add_child(node) func _on_position_changed(node, new_pos): print("%s is now at %s" % [node.get_class(), new_pos])
csharpusing Godot; public partial class Demo : Node { public override void _Ready() { var node = new GDExample(); // The native class is available like any Godot type. node.Set("speed", 2.0); node.Connect("position_changed", Callable.From<Node, Vector2>(OnPositionChanged)); AddChild(node); } private void OnPositionChanged(Node node, Vector2 newPos) => GD.Print($"{node.GetClass()} is now at {newPos}"); }
To get a strongly-typed C# wrapper you can ship a C# glue class, but the extension is fully usable via the dynamic Set / Connect / Call API shown above.
> Other languages & debugging: Rust (gdext) · Debugging native code
_bind_methods binds every exposed method/property/signalentry_symbol in .gdextension matches the exported extern "C" symbolcompatibility_minimum set to the lowest engine version you support[libraries] has correct paths for every shipped platform/arch (debug + release)template_release binaries| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 12,938 | 9,814 | -24% | 1 | 1 | 0% | 2,657 | 5,088 | +91% | 0 | 0 | — |
case-02 | pass→pass | 4,846 | 3,062 | -37% | 1 | 1 | 0% | 881 | 3,536 | +301% | 0 | 0 | — |
case-03 | fail→pass | 18,253 | 12,009 | -34% | 1 | 1 | 0% | 3,376 | 5,417 | +60% | 0 | 0 | — |
case-04 | pass→pass | 20,016 | 9,472 | -53% | 1 | 1 | 0% | 3,439 | 4,821 | +40% | 0 | 0 | — |
case-05 | pass→pass | 5,033 | 5,049 | +0% | 1 | 1 | 0% | 946 | 4,058 | +329% | 0 | 0 | — |
case-06 | pass→pass | 6,475 | 3,631 | -44% | 1 | 1 | 0% | 1,106 | 3,763 | +240% | 0 | 0 | — |
case-07 | fail→fail | 7,767 | 5,509 | -29% | 1 | 1 | 0% | 1,357 | 4,130 | +204% | 0 | 0 | — |
case-08 | pass→pass | 9,239 | 4,595 | -50% | 1 | 1 | 0% | 1,775 | 3,915 | +121% | 0 | 0 | — |
case-09 | pass→pass | 14,032 | 8,329 | -41% | 1 | 1 | 0% | 2,391 | 4,486 | +88% | 0 | 0 | — |
case-10 | pass→pass | 10,963 | 5,378 | -51% | 1 | 1 | 0% | 1,564 | 3,853 | +146% | 0 | 0 | — |
case-11 | pass→pass | 6,242 | 4,152 | -33% | 1 | 1 | 0% | 1,134 | 3,775 | +233% | 0 | 0 | — |
case-12 | pass→pass | 13,285 | 7,096 | -47% | 1 | 1 | 0% | 2,313 | 4,376 | +89% | 0 | 0 | — |
case-13 | pass→pass | 6,045 | 3,036 | -50% | 1 | 1 | 0% | 999 | 3,602 | +261% | 0 | 0 | — |
case-14 | pass→pass | 8,678 | 3,880 | -55% | 1 | 1 | 0% | 1,386 | 3,741 | +170% | 0 | 0 | — |
case-15 | pass→pass | 6,781 | 3,295 | -51% | 1 | 1 | 0% | 1,209 | 3,647 | +202% | 0 | 0 | — |
case-16 | pass→pass | 13,143 | 8,892 | -32% | 1 | 1 | 0% | 1,980 | 4,527 | +129% | 0 | 0 | — |
case-17 | pass→pass | 12,646 | 9,106 | -28% | 1 | 1 | 0% | 2,033 | 4,503 | +121% | 0 | 0 | — |
case-18 | pass→pass | 5,548 | 2,245 | -60% | 1 | 1 | 0% | 880 | 3,369 | +283% | 0 | 0 | — |
case-19 | pass→pass | 10,317 | 6,113 | -41% | 1 | 1 | 0% | 1,800 | 4,122 | +129% | 0 | 0 | — |
case-20 | fail→pass | 11,525 | 5,292 | -54% | 1 | 1 | 0% | 1,843 | 3,948 | +114% | 0 | 0 | — |
case-21 | pass→pass | 16,302 | 7,571 | -54% | 1 | 1 | 0% | 2,406 | 4,217 | +75% | 0 | 0 | — |
case-22 | fail→pass | 15,970 | 4,125 | -74% | 1 | 1 | 0% | 2,860 | 3,746 | +31% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +14 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.