Back to Blog

Speedrunning JetBrains Rider Godot Addon Development: Native C++ GDExtensions and Editor Plugins

Published on May 29, 2026
Speedrunning JetBrains Rider Godot Addon Development: Native C++ GDExtensions and Editor Plugins

In a nutshell

Learn how to master jetbrains rider godot addon development with native C++ templates, C# tools, and battle-tested tips for indie game developers.

Building a custom Godot editor plugin or a native C++ GDExtension usually begins with a sudden, painful realization: you've spent three hours configuring SCons compile scripts, fighting headless compiler flags, and parsing ambiguous cross-compilation errors instead of writing your plugin's actual logic. While Godot 4's architecture has unlocked incredible potential for extensions, the developer experience has historically felt like configuring a server rack with a toothpick. With the launch of the new Godot Asset Store, IDE providers are finally treating addon development as a first-class citizen.

JetBrains Rider 2026.2 has officially entered the arena as one of the first major IDE tool providers to offer dedicated templates, CMake automation, and multi-language debugging for the Godot Asset Store. For indie developers and team tools engineers, this means the end of boilerplate configuration hell and the start of rapid tool prototyping. By reducing configuration times down to a single-click wizard, the barrier to extending Godot's core editor interface has never been lower.

The GDExtension Bottleneck: Why Godot Tooling Used to Hurt

Under the hood, Godot 4 uses GDExtension to let developers write high-performance C++ or Rust code that interfaces directly with the engine's core structures without recompiling the entire engine. It does this by loading dynamic libraries—such as .dll on Windows, .so on Linux, and .dylib on macOS—and mapping them to GDScript interfaces. However, setting this up manually requires pulling the godot-cpp bindings repository, matching the exact engine version header, writing custom SCons or CMake scripts, and configuring a .gdextension configuration file to map library paths across five different target platforms.

Worse, debugging these native binaries has been notoriously crash-prone. A typical troubleshooting workflow involves launching the Godot editor under a separate debugger (like GDB or LLDB), setting breakpoints in an external editor, and crossing your fingers that a hot-reload doesn't panic the engine's main thread and force a hard crash. When developers are building custom tooling—especially complex database synchronizers, low-latency netcode interfaces, or asset pipelines—this friction completely destroys productivity.

Rider 2026.2: A Breakdown of the New Addon Toolchain

Out-of-the-Box Project Templates

Rider 2026.2 provides dedicated, wizard-driven templates covering the full gamut of Godot extension formats. You no longer have to clone repository boilerplates or copy-paste folder structures from old projects. Instead, the IDE builds a clean, structured repository for either GDScript editor plugins, C# extensions, or C++ GDExtensions, pre-configured with everything from the plugin.cfg to target build folders. This saves hours of configuration and eliminates the most common cause of early-stage failures: mismatched directory paths in manifest files.

Native CMake Integration for C++

Historically, Godot's C++ bindings strongly favored SCons as a build system. SCons is powerful, but its Python-based configuration files are notoriously opaque, lack IDE autocomplete, and complicate CI/CD integration. Rider 2026.2 introduces robust, native CMake integration for GDExtension projects. When you create a GDExtension addon, Rider automatically generates a clean CMakeLists.txt file that links the core godot-cpp bindings library to your custom source code. This lets you utilize Rider's powerful C++ engine for code navigation, refactoring, and static analysis without any extra configuration.

Dual-Language Debugging in a Single Session

This is the crown jewel of the update. Developers writing high-performance Godot tools rarely stick to a single language. A standard architecture uses high-performance C++ for data heavy-lifting or complex math and a lightweight GDScript file for the GUI dock or editor UI panel. Debugging this hybrid architecture meant using separate tools for C++ and GDScript. Rider 2026.2 generates unified run configurations automatically. You can hit a single "Debug" button, and Rider will launch the Godot editor, attach to its process, and trace executions simultaneously across GDScript and C++. A breakpoint in your UI's GDScript will trigger, and as you step into a native C++ function, Rider will seamlessly transition to the C++ debugger without dropping the session.

Publish-Ready Folder Architecture

The new Godot Asset Store has strict folder structures and packaging requirements to prevent addons from stomping on each other's namespaces. Rider's templates enforce these recommendations from day one. By separating the runtime files from the editor-only GUI components, the IDE ensures that when you output a build, it is immediately ready for upload to the store, reducing packaging errors from a frequent headache to an automated non-event.

Inside the GDExtension Lifecycle: Building the C++ Backend

To understand the value of Rider's automation, we must look at what a GDExtension project actually requires at the code level. In a standard GDExtension, you must define an entry point library initializer, register your custom class types with Godot's ClassDB, and carefully clean up allocations when the module is uninitialized. The following C++ headers and source files represent the minimum boilerplate needed to create a native custom node—in this case, a high-performance telemetry handler.

// horizon_telemetry_node.h
#ifndef HORIZON_TELEMETRY_NODE_H
#define HORIZON_TELEMETRY_NODE_H

#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/core/class_db.hpp>

namespace godot {

class HorizonTelemetryNode : public Node {
    GDCLASS(HorizonTelemetryNode, Node);

private:
    String session_id;
    int event_count;

protected:
    static void _bind_methods();

public:
    HorizonTelemetryNode();
    ~HorizonTelemetryNode();

    void initialize_telemetry(const String &p_session_id);
    void track_event(const String &p_event_name);
    String get_session_id() const;
};

}

#endif // HORIZON_TELEMETRY_NODE_H

Next, we implement the core behavior. In the implementation file, we register our methods inside _bind_methods() to ensure Godot's runtime reflection engine can access them.

// horizon_telemetry_node.cpp
#include "horizon_telemetry_node.h"
#include <godot_cpp/variant/utility_functions.hpp>

using namespace godot;

void HorizonTelemetryNode::_bind_methods() {
    ClassDB::bind_method(D_METHOD("initialize_telemetry", "session_id"), &HorizonTelemetryNode::initialize_telemetry);
    ClassDB::bind_method(D_METHOD("track_event", "event_name"), &HorizonTelemetryNode::track_event);
    ClassDB::bind_method(D_METHOD("get_session_id"), &HorizonTelemetryNode::get_session_id);

    ADD_PROPERTY(PropertyInfo(Variant::STRING, "session_id"), "", "get_session_id");
}

HorizonTelemetryNode::HorizonTelemetryNode() {
    event_count = 0;
    session_id = "";
}

HorizonTelemetryNode::~HorizonTelemetryNode() {
    // Clean up memory safely here
}

void HorizonTelemetryNode::initialize_telemetry(const String &p_session_id) {
    session_id = p_session_id;
    UtilityFunctions::print("Telemetry initialized for session: ", session_id);
}

void HorizonTelemetryNode::track_event(const String &p_event_name) {
    event_count++;
    UtilityFunctions::print("Event tracked: ", p_event_name, " (Total: ", event_count, ")");
}

String HorizonTelemetryNode::get_session_id() const {
    return session_id;
}

Finally, we must tell Godot how to load our module using an initialization function. This register_types file acts as the library's main entry point, hooked up via GDExtension's loading system.

// register_types.cpp
#include "register_types.h"
#include "horizon_telemetry_node.h"
#include <gdextension_interface.h>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>

using namespace godot;

void initialize_horizon_plugin_module(ModuleInitializationLevel p_level) {
    if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
        return;
    }
    ClassDB::register_class<HorizonTelemetryNode>();
}

void uninitialize_horizon_plugin_module(ModuleInitializationLevel p_level) {
    if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
        return;
    }
}

extern "C" {
// Initialization entry point called by Godot.
GDExtensionBool GDE_EXPORT horizon_plugin_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_horizon_plugin_module);
    init_obj.register_terminator(uninitialize_horizon_plugin_module);
    init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);

    return init_obj.init();
}
}

Developing Editor Plugins: The GDScript Pipeline

While GDExtension handles the high-performance C++ backend, the UI of your Godot addon—such as adding a custom panel in the bottom dock or creating custom inspector nodes—is typically written in GDScript using the @tool annotation. The @tool directive tells Godot that this script should execute directly inside the running editor instance, not just when the game is playing.

Writing tool scripts requires clean lifecycle management. The _enter_tree() and _exit_tree() functions act as the constructors and destructors of your editor integrations. Failing to clean up custom UI nodes during editor unloading will result in stale, orphaned GUI nodes cluttering the editor memory space, which eventually triggers crash-on-exit issues.

# horizon_editor_plugin.gd
@tool
extends EditorPlugin

const PLUGIN_NAME = "HorizonBackendHelper"
var dock: Control

func _enter_tree() -> void {
    # Initialize the dock UI from our pre-packed scene
    dock = preload("res://addons/horizon_plugin/dock.tscn").instantiate()
    
    # Add the main panel to the editor dock on the upper-right slot
    add_control_to_dock(DOCK_SLOT_LEFT_UR, dock)
    
    # Register our custom backend node so developers can add it in the scene tree
    add_custom_type(
        "HorizonClientNode",
        "Node",
        preload("res://addons/horizon_plugin/horizon_client_node.gd"),
        preload("res://addons/horizon_plugin/icon.png")
    )
    print("Plugin ", PLUGIN_NAME, " successfully initialized in editor.")

func _exit_tree() -> void {
    # Clean up dock and custom types to prevent editor memory leaks
    if dock:
        remove_control_from_docks(dock)
        dock.queue_free()
    
    remove_custom_type("HorizonClientNode")
    print("Plugin ", PLUGIN_NAME, " cleaned up.")

The Connectivity Challenge: Integrating Remote Backends

When building editor plugins or GDExtensions, your tool is often only as powerful as the backend services it interfaces with. For instance, if you are building an admin panel for an indie game, a remote level editor, or an in-engine telemetry tracker, your plugin needs to communicate with databases, manage developer or player identities, and sync remote states. Implementing this yourself means building a customized, secure web service. You must spin up a virtual private server, set up an API gateway, deploy a database, write custom user authentication models, and implement SSL/TLS certificate rotation. This is an enormous engineering overhead that can easily consume 4 to 6 weeks of dedicated development time before your plugin can even talk to a database.

Instead of routing your team's limited energy into managing raw backend infrastructure, you can integrate horizOn as your core game engine backend. horizOn provides a native, high-performance C# and GDScript SDK that plugs directly into your custom editor addons. Rather than spending weeks provisioning databases and writing custom WebSockets handlers, you can drop horizOn's client into your project and instantly gain secure authentication, real-time database access, and player management. By leaving the infrastructure heavy lifting to horizOn, you can dedicate your time to refining your addon's UX and gameplay tools, knowing that your backend will scale seamlessly when you publish on the store.

5 Battle-Tested Best Practices for Godot Addon Development

1. Namespace Isolation via Folder Structure

Always prefix your addon folders and scripts with a unique namespace under res://addons/your_unique_addon_name/. Godot shares a single, flat global path namespace for all custom classes registered via the @icon or class_name directives. If you use a generic class name like NetworkManager or ConfigHelper, your addon will conflict with the developer's core project or other third-party extensions. Keep all your utility scripts scoped strictly under your unique folder directory.

2. Automate Binary Compilation and Exclude from VCS

Keep heavy, compiled GDExtension binaries (.dll, .so, .dylib) out of your main Git repository history. The repository size will quickly bloat as you recompile libraries during development. Instead, use a .gitignore to ignore build directories and release folders, and set up a CI/CD pipeline (such as GitHub Actions or GitLab CI) using automated CMake scripts to build target binaries for multiple platforms, packaging them only inside release zip files.

3. Manage the GDScript-to-C++ Boundary with Care

Be mindful of how memory is handled when passing variables across the language boundary. GDScript automatically manages the lifecycle of classes derived from RefCounted (like Resource), but uses manual memory management for objects inheriting from Object (like raw Node objects). In your C++ GDExtension code, always use Godot's Ref<T> smart wrapper for reference-counted classes to avoid double-free errors or memory leaks. For standard classes, perform defensive casting using Object::cast_to<T>() and check for null pointers before invoking native methods.

4. Prefer WebSockets and Persistent Connections for Real-Time State

Avoid using traditional HTTP polling for plugins that require real-time synchronization, such as shared editor systems or backend matchmaking tools. Repeatedly firing HTTP requests introduces high CPU overhead and triggers massive rate-limiting penalties on backend services. Instead, you should ditch HTTP polling in favor of WebSockets to establish a persistent, bidirectional connection. This reduces latency from a sluggish 500ms down to sub-10ms intervals and minimizes data overhead.

5. Design Graceful Fallback Systems for Remote Cloud Pipelines

If your addon communicates with remote cloud servers, never let a network interruption freeze the Godot editor thread. Synchronous web requests can block Godot's main process, causing the editor to hang. Always use asynchronous callbacks or thread pools to keep UI responses fluid. Furthermore, if you are designing a live-ops integration, study how to design robust pipelines by evaluating the stop killing games campaign vs live ops server fallbacks. This ensures your tool gracefully downgrades to offline mode and keeps the editor functional even when cloud endpoints are completely unreachable.

Conclusion: Streamlining Your Godot Tooling Pipeline

JetBrains Rider 2026.2 transforms Godot addon development from a complex exercise in system configuration into a streamlined, productive developer workflow. By automating GDExtension scaffolding, providing robust CMake integration, and offering simultaneous GDScript and C++ debugging, Rider eliminates configuration fatigue and lets you focus on coding beautiful tools. Combining Rider's development templates with a scalable, fully managed backend architecture allows you to create high-performance, connected plugins without the overhead of manual server engineering.

Ready to scale your multiplayer backend? Try horizOn for free or check out the API docs.


Source: JetBrains Rider brings support for Godot Asset Store addons

This dashboard is made with love by Projectmakers

© 2026 projectmakers.de

unknown-v1.87.6 / unknown-v--