How GDExtension Loading and Interface Management Work in Godot Engine
GDExtension loading works through a singleton GDExtensionManager that discovers .gdextension files, loads shared libraries via OS::open_dynamic_library, acquires proc-address getters for interface functions, and initializes extensions across four levels while managing runtime callbacks and editor hot-reloading.
Godot's GDExtension system is the modern replacement for GDNative, enabling native C/C++ code to integrate with the engine at runtime. The loading process involves a sophisticated pipeline of discovery, dynamic linking, and interface function binding that allows extensions to register classes, methods, and properties without recompiling the engine.
The GDExtensionManager Singleton
The engine creates a global GDExtensionManager singleton during early startup to orchestrate all extension operations. This singleton is instantiated in register_core_types() within core/register_core_types.cpp and added to the engine's global singleton table via Engine::add_singleton.
The singleton pattern provides centralized access throughout the engine:
static inline GDExtensionManager *singleton = nullptr;
static GDExtensionManager *GDExtensionManager::get_singleton() { return singleton; }
All extension operations—loading, initialization, callbacks, and reloading—flow through this manager, ensuring consistent state tracking and lifecycle management.
Extension Discovery and Loading Pipeline
Configuration Discovery
The loading process begins with discovery. The manager reads extension_list.cfg (generated by the editor) to obtain paths to .gdextension resource files. Each .gdextension file contains metadata specifying the entry symbol and platform-specific library paths:
[configuration]
entry_symbol = "my_extension_init"
[libraries]
windows.debug = "bin/my_extension.windows.debug.dll"
linux.debug = "bin/my_extension.linux.debug.so"
macos.debug = "bin/my_extension.macos.debug.dylib"
Library Resolution and Loading
When GDExtensionManager::load_extension(const String &p_path) is called, it instantiates a GDExtensionLibraryLoader to handle the heavy lifting. The loader performs three critical tasks:
- Parses the
.gdextensionfile to resolve the best matching library for the current feature set and platform - Locates all dependencies
- Opens the binary using
OS::open_dynamic_library
The loading chain proceeds through load_extension_with_loader, which instantiates a GDExtension resource and calls extension->open_library(p_path, p_loader). Inside GDExtension::open_library, the final platform-specific loading occurs:
OS::get_singleton()->open_dynamic_library(abs_path, library, &data);
Platform implementations (such as platform/windows/os_windows.cpp or platform/linuxbsd/os_linuxbsd.cpp) execute the native dlopen or LoadLibrary calls and store the returned handle for subsequent symbol resolution.
Interface Function Acquisition
Once the library is loaded, Godot establishes the interface function table that extensions use to call engine APIs. The manager obtains a proc-address getter function from the loaded library:
GDExtensionInterfaceFunctionPtr gdextension_get_proc_address(const char *p_name);
Every engine function exposed to extensions is registered in a static hash map via register_interface_function:
static void register_interface_function(const StringName &p_name,
GDExtensionInterfaceFunctionPtr p_ptr) {
gdextension_interface_functions[p_name] = p_ptr;
}
The extension's entry point receives the getter and resolves required symbols:
p_interface->classdb_register_class = (GDExtensionInterfaceClassdbRegisterClass)
p_get_proc_address("classdb_register_class");
This design caches function pointers in gdextension_interface_functions (defined in core/extension/gdextension.h), eliminating the need for repeated OS-level symbol lookups and ensuring high-performance cross-language calls.
Initialization Levels and Lifecycle
Godot defines four distinct initialization levels in GDExtension::InitializationLevel:
CORE– Initial engine core setupSERVERS– Server singletons availableSCENE– Scene types initializedEDITOR– Editor-only classes and tools
During engine startup (Main::setup() in main/main.cpp), the manager iterates through these levels:
GDExtensionManager::get_singleton()->initialize_extensions(
GDExtension::INITIALIZATION_LEVEL_SERVERS);
// ...
GDExtensionManager::get_singleton()->initialize_extensions(
GDExtension::INITIALIZATION_LEVEL_SCENE);
The initialize_extensions(level) method checks each loaded extension's minimum_initialization_level. If the extension's minimum level is less than or equal to the current level, the manager calls extension->initialize_library(level) to trigger the extension's initialization function.
Runtime Callbacks
Extensions may expose three optional lifecycle callbacks in their GDExtensionInitialization struct:
startup– Called immediately after library load viaGDExtensionManager::startupframe– Called every engine frame viaGDExtensionManager::frameshutdown– Called during engine shutdown viaGDExtensionManager::shutdown
The manager tracks invocation state to prevent duplicate calls during reload operations.
Editor Hot-Reloading
When compiled with TOOLS_ENABLED, the manager supports live reloading of extensions without restarting the editor. The reload_extension method implements a careful teardown and restoration sequence:
prepare_reload()– Saves internal state and prepares the extensionclose_library()– Unloads the dynamic library handle- Re-open – Loads the potentially updated binary from the same path
- Re-initialize – Runs initialization up to the current engine level
finish_reload()– Restores instance bindings and completes the transition
This process is implemented in GDExtensionManager::reload_extension, utilizing _unload_extension_internal for the cleanup phase.
Implementation Example
A complete extension requires both configuration and implementation code. The .gdextension file specifies the entry point, while the C/C++ code implements the initialization:
#include <godot/gdextension_interface.h>
static void GDN_EXPORT my_class_register(GDExtensionInterfaceGetProcAddress p_get_proc_address) {
GDExtensionClassCreationInfo create_info = {};
create_info.create_instance_func = my_class_create;
create_info.free_instance_func = my_class_free;
create_info.get_virtual_func = my_class_get_virtual;
GDExtensionInterfaceClassdbRegisterClass register_class =
(GDExtensionInterfaceClassdbRegisterClass)p_get_proc_address("classdb_register_class");
register_class("MyClass", "Node", &create_info);
}
extern "C" GDExtensionBool GDN_EXPORT my_extension_init(
GDExtensionInterfaceGetProcAddress p_get_proc_address,
GDExtensionClassLibraryPtr p_library,
GDExtensionInitialization *r_initialization) {
r_initialization->initialize = my_class_register;
r_initialization->minimum_initialization_level = GDEXTENSION_INITIALIZATION_SCENE;
return GDExtensionBool(true);
}
From GDScript, the registered class behaves like any native engine class:
extends Node
func _ready():
var obj = MyClass.new()
print(obj.some_method())
Summary
GDExtensionManageris the engine-wide singleton that coordinates all extension operations, created duringregister_core_types().- Discovery reads
extension_list.cfgand parses.gdextensionmetadata to locate platform-specific shared libraries. - Loading uses
GDExtensionLibraryLoaderto resolve dependencies andOS::open_dynamic_libraryfor platform-specific dynamic linking. - Interface functions are exposed through a proc-address getter pattern, cached in
gdextension_interface_functionsfor performance. - Initialization levels (CORE, SERVERS, SCENE, EDITOR) allow extensions to register functionality only when dependent engine systems are ready.
- Editor hot-reloading enables iterative development by unloading, updating, and re-initializing extension libraries without restarting Godot.
Frequently Asked Questions
How does Godot find which GDExtensions to load?
Godot reads the extension_list.cfg file (generated by the editor) which contains paths to .gdextension resource files. According to the source code in core/extension/gdextension_manager.cpp, the manager parses these configuration files to determine the entry symbol and select the appropriate platform-specific library binary for the current build profile.
What is the difference between GDExtension and the old GDNative system?
GDExtension is the successor to GDNative, providing a more stable ABI and cleaner interface management. While GDNative required specific version matching and complex binding logic, GDExtension uses a centralized interface function table (gdextension_interface_functions) and standardized initialization levels, making it easier to maintain compatibility across Godot versions.
Can GDExtensions be reloaded without restarting the editor?
Yes, but only in editor builds (TOOLS_ENABLED). The GDExtensionManager::reload_extension method handles the entire process: preparing internal state, closing the library handle, reopening the potentially modified binary, and re-initializing to the current level. This is implemented in core/extension/gdextension_manager.cpp and enables rapid iteration during native extension development.
What happens if an extension fails to load?
The load_extension method returns a LoadStatus enum indicating success or specific failure modes (such as file not found or initialization failure). If open_library fails in core/extension/gdextension.cpp, the manager cleans up the partially loaded state and returns an error code without crashing the engine, allowing Godot to continue running without the problematic extension.
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 →