--- name: addon-development description: Use when creating Godot editor plugins — EditorPlugin, @tool scripts, custom inspectors, and dock panels --- # Addon Development in Godot 4.3+ Editor plugins extend the Godot editor itself: custom node types, inspector panels, dock widgets, 3D gizmos, and toolbar buttons. All examples target Godot 4.3+ with no deprecated APIs. > **Related skills:** **resource-pattern** for custom Resource editors, **godot-ui** for editor panel UI, **csharp-godot** for C# plugin development. --- ## 1. Plugin Structure Every plugin lives inside `addons/` at the project root. Godot discovers plugins by scanning for `plugin.cfg` files. ``` res:// └── addons/ └── my_plugin/ ├── plugin.cfg # required — plugin metadata ├── plugin.gd # main EditorPlugin script (named in plugin.cfg) ├── my_inspector.gd # optional — EditorInspectorPlugin ├── my_dock.tscn # optional — dock panel scene └── icons/ └── my_node.svg # optional — custom node icons ``` `plugin.cfg` is a plain INI file. Godot reads it when scanning `addons/`. The `script` key must point to the main plugin script relative to the plugin folder. Enable the plugin: **Project → Project Settings → Plugins** → tick the checkbox next to your plugin name. --- ## 2. @tool Annotation `@tool` makes a GDScript (or its C# equivalent) run inside the editor process as well as at runtime. Without it, the script only runs when the game is playing. ### GDScript ```gdscript @tool extends Sprite2D # Engine.is_editor_hint() is true when running inside the editor, # false during a running game. Use it to guard editor-only logic. func _process(delta: float) -> void: if Engine.is_editor_hint(): # This block runs in the editor viewport — safe to call editor APIs. update_configuration_warnings() else: # Normal game logic here. pass # _get_configuration_warnings() returns an array of strings shown as # yellow warning icons on the node in the Scene panel. func _get_configuration_warnings() -> PackedStringArray: var warnings := PackedStringArray() if texture == null: warnings.append("Texture is not set. Assign a Texture2D in the Inspector.") return warnings ``` ### C# ```csharp #if TOOLS using Godot; [Tool] public partial class MyToolSprite : Sprite2D { public override void _Process(double delta) { if (Engine.IsEditorHint()) { // Editor-only logic — safe to call editor APIs here. UpdateConfigurationWarnings(); } else { // Normal game logic. } } public override string[] _GetConfigurationWarnings() { if (Texture == null) return new[] { "Texture is not set. Assign a Texture2D in the Inspector." }; return System.Array.Empty(); } } #endif ``` > Wrap C# tool scripts in `#if TOOLS` / `#endif` to prevent the class from being included in exported builds. GDScript `@tool` scripts are excluded from exports automatically. **Key rules:** - Add `@tool` / `[Tool]` at the top of every script that needs editor access. - Always guard runtime-only code with `Engine.is_editor_hint()` to avoid crashing the editor when processing begins before the scene is fully loaded. - Call `update_configuration_warnings()` whenever a property changes that might affect the warning state. --- ## 3. EditorPlugin Base The main plugin script extends `EditorPlugin`. Godot calls `_enter_tree()` when the plugin is enabled and `_exit_tree()` when it is disabled or the project is closed. **Everything added in `_enter_tree()` must be removed in `_exit_tree()`.** ### GDScript ```gdscript # plugin.gd @tool extends EditorPlugin func _enter_tree() -> void: # Register a custom node type. The editor shows MyNode in the # "Add Node" dialog under the chosen base class, with a custom icon. add_custom_type( "MyNode", # name shown in editor "Node2D", # base class to extend preload("res://addons/my_plugin/my_node.gd"), preload("res://addons/my_plugin/icons/my_node.svg") ) # Add a menu item to the Project menu (top toolbar). add_tool_menu_item("My Plugin Action", _on_tool_menu_item) func _exit_tree() -> void: remove_custom_type("MyNode") remove_tool_menu_item("My Plugin Action") func _on_tool_menu_item() -> void: print("My Plugin Action triggered") ``` ### C# ```csharp // Plugin.cs #if TOOLS using Godot; [Tool] public partial class MyPlugin : EditorPlugin { public override void _EnterTree() { AddCustomType( "MyNode", "Node2D", GD.Load