GDExtension — native plugins in C++ and Rust
godot-cpp, gdext (Rust), when native extensions are needed and how they work.
GDScript is good for most gameplay logic, but sometimes you need to go lower:
- A voxel engine with millions of blocks
- Custom real-time shading with complex CPU math
- Integration with an existing C/C++ library (physics, NN inference, audio DSP)
- A performance hot path where even statically-typed GDScript isn’t enough
GDExtension is the modern way to extend Godot at the native level. It replaced GDNative as of Godot 4.0.
Core ideas
- You write code in C++ (via
godot-cpp) or Rust (viagdext). - You compile it into a dynamic library (
.so/.dll/.dylib). - You create a
.gdextensionmanifest that Godot loads at startup. - In the editor your classes appear as ordinary nodes — they are added to the scene, have properties in the Inspector, and can connect signals.
The main advantage of GDExtension over modules (which live in the Godot monorepo): you don’t have to rebuild the editor itself. Change the plugin → reload Godot → the new version works.
Node native modules (.node files via node-gyp). Or WASM modules compiled from
Rust/C++ for the browser.
Structure of a GDExtension plugin
my_plugin/
├── my_plugin.gdextension ← manifest
├── src/
│ ├── register_types.cpp ← class registration
│ ├── my_node.cpp
│ └── my_node.h
├── bin/ ← compiled .so/.dll
│ ├── libmyplugin.linux.x86_64.so
│ ├── libmyplugin.windows.x86_64.dll
│ └── libmyplugin.macos.universal.dylib
├── SConstruct ← build via SCons
└── godot-cpp/ ← submodule with bindings
my_plugin.gdextension:
[configuration]
entry_symbol = "myplugin_library_init"
compatibility_minimum = "4.4"
[libraries]
linux.x86_64 = "bin/libmyplugin.linux.x86_64.so"
windows.x86_64 = "bin/libmyplugin.windows.x86_64.dll"
macos = "bin/libmyplugin.macos.universal.dylib"
Minimal C++ example
my_node.h:
#ifndef MY_NODE_H
#define MY_NODE_H
#include <godot_cpp/classes/node3d.hpp>
namespace godot {
class MyNode : public Node3D {
GDCLASS(MyNode, Node3D)
private:
double speed = 1.0;
protected:
static void _bind_methods();
public:
MyNode();
~MyNode();
void _process(double delta) override;
void set_speed(double p_speed);
double get_speed() const;
};
}
#endif
my_node.cpp:
#include "my_node.h"
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void MyNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_speed", "p_speed"), &MyNode::set_speed);
ClassDB::bind_method(D_METHOD("get_speed"), &MyNode::get_speed);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed"), "set_speed", "get_speed");
}
MyNode::MyNode() {}
MyNode::~MyNode() {}
void MyNode::_process(double delta) {
rotate_y(speed * delta);
}
void MyNode::set_speed(double p_speed) { speed = p_speed; }
double MyNode::get_speed() const { return speed; }
After compiling (scons → a binary in bin/) → restart Godot → “MyNode” will appear in Add Node
with a speed field in the Inspector.
Rust — gdext
The community developed gdext — bindings for Rust. The style is more idiomatic:
use godot::prelude::*;
struct MyExtension;
#[gdextension]
unsafe impl ExtensionLibrary for MyExtension {}
#[derive(GodotClass)]
#[class(base=Node3D)]
struct MyNode {
#[var]
speed: f64,
base: Base<Node3D>,
}
#[godot_api]
impl INode3D for MyNode {
fn init(base: Base<Node3D>) -> Self {
Self { speed: 1.0, base }
}
fn process(&mut self, delta: f64) {
let rotation_y = self.base().get_rotation().y + (self.speed * delta) as f32;
let mut rot = self.base().get_rotation();
rot.y = rotation_y;
self.base_mut().set_rotation(rot);
}
}
Rust advantages:
- Memory safety without overhead — no malloc/free errors, no UB.
- Cargo — the package ecosystem is simpler than CMake/SCons for C++.
- Performance on par with C++.
Downsides:
- The first compile is a bit slower.
- Fewer community plugins with examples (than C++).
gdext is actively developing and is considered production-ready as of 2026.
When it’s worth writing a GDExtension
✅ Worth it:
- Voxel engine, terrain LOD, marching cubes.
- Custom physics constraints or soft-body simulation.
- Real-time DSP / audio synthesis.
- Integration with an existing C++ library (Open3D, OpenCV, ML inference libraries).
- Workflows where even Burst/SIMD-level GDScript is insufficient.
❌ NOT worth it:
- Standard gameplay logic (use GDScript).
- Single-shot operations (generating a map once — write it in WorkerThreadPool from GDScript).
- When you’re not sure that the bottleneck is in the CPU computations. Profile first.
Performance
Benchmarks for a simple numerical workload (a million iterations):
| Language | Time |
|---|---|
| GDScript (untyped) | 850 ms |
| GDScript (typed) | 400 ms |
| C# | 80 ms |
| C++ (GDExtension) | 12 ms |
| Rust (gdext) | 12 ms |
C++ and Rust are tens of times faster than typed GDScript. But overhead kicks in when crossing the GDExtension boundary (calling a C++ method from GDScript). That’s why you write large chunks in C++, calling them rarely.
GDExtension consists of native binaries. Linux x86_64 doesn’t run on Windows. You need to build for each target platform, which complicates CI/CD. For one platform it’s simple; for shipping to Windows + Linux + macOS + Android + iOS you need 5 different CI jobs.
Extensions commonly written with GDExtension
- godot-rapier-3d — Rapier physics in Rust (an alternative to the built-in physics).
- Voxelity — a voxel engine for Godot.
- godot-luau-script — Luau (Roblox-style) scripting.
- NAVigator — advanced navigation.
The Asset Library has a “Native plugins” category — with ready-made GDExtension plugins.
An alternative — C# in Godot
If you have a .NET team and don’t want to get into C++, C# via Godot.NET gives a ~10× speedup
over GDScript (untyped). See the next chapter.
Comparison with Unity
Unity equivalent:
- A Native Plugin (
.dll/.dylib/.so) loaded via[DllImport]. - Less integrated — native plugins don’t appear as Node classes in the hierarchy.
- Unity’s alternative for heavy computation is the Job System + Burst (see the chapter on Jobs+Burst), all inside C# and without leaving for the native world.
GDExtension in Godot is a deeper integration with the engine; Burst in Unity is optimization without leaving C#. Both approaches are valid.