Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when creating Godot editor plugins — EditorPlugin, @tool scripts, custom inspectors, and dock panels
.claude/skills/jame581-addon-development/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -16% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 134% | 0% |
| case-09 | ✗→✓ | ▲ Improved | -29% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 7% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 55% | 0% |
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.
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 iconsplugin.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.
@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@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
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<string>(); } } #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:
@tool / [Tool] at the top of every script that needs editor access.Engine.is_editor_hint() to avoid crashing the editor when processing begins before the scene is fully loaded.update_configuration_warnings() whenever a property changes that might affect the warning state.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# 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")
csharp// Plugin.cs #if TOOLS using Godot; [Tool] public partial class MyPlugin : EditorPlugin { public override void _EnterTree() { AddCustomType( "MyNode", "Node2D", GD.Load<Script>("res://addons/my_plugin/MyNode.cs"), GD.Load<Texture2D>("res://addons/my_plugin/icons/my_node.svg") ); AddToolMenuItem("My Plugin Action", new Callable(this, MethodName.OnToolMenuAction)); } public override void _ExitTree() { RemoveCustomType("MyNode"); RemoveToolMenuItem("My Plugin Action"); } private void OnToolMenuAction() { GD.Print("My Plugin Action triggered"); } } #endif
add_custom_type parameters:
| Parameter | Description | |---|---| | name | The name shown in the Add Node dialog | | base | String name of the Godot base class | | script | The GDScript / C# script resource | | icon | A Texture2D, typically a 16×16 SVG |
add_tool_menu_item adds an entry under Project in the top menu bar. Pass a Callable that takes no arguments.
Godot 4.7 adds file-management APIs useful for build/export tooling — check for unsaved work before running an action, or refresh scripts changed by an external tool.
gdscriptfunc _run_pre_build_check() -> void: var unsaved_scenes := EditorInterface.get_unsaved_scenes() # PackedStringArray of scene paths var script_editor := EditorInterface.get_script_editor() var unsaved_files := script_editor.get_unsaved_files() # PackedStringArray of script paths if not unsaved_scenes.is_empty() or not unsaved_files.is_empty(): push_warning("Unsaved work detected — save before building.") script_editor.save_all_scripts() # saves every open script script_editor.reload_open_files() # re-read files changed outside the editor # Closes the tab, discarding unsaved changes; OK or ERR_FILE_NOT_FOUND. var err := script_editor.close_file("res://addons/my_plugin/generated.gd")
csharp#if TOOLS private void RunPreBuildCheck() { string[] unsavedScenes = EditorInterface.Singleton.GetUnsavedScenes(); var scriptEditor = EditorInterface.Singleton.GetScriptEditor(); string[] unsavedFiles = scriptEditor.GetUnsavedFiles(); if (unsavedScenes.Length > 0 || unsavedFiles.Length > 0) GD.PushWarning("Unsaved work detected — save before building."); scriptEditor.SaveAllScripts(); scriptEditor.ReloadOpenFiles(); Error err = scriptEditor.CloseFile("res://addons/my_plugin/Generated.cs"); } #endif
When you want a custom widget for an exported property of a specific type, register an EditorInspectorPlugin from your main EditorPlugin. The inspector plugin overrides _can_handle to opt in and _parse_property (or _parse_begin) to inject custom widgets. Pair with an EditorProperty subclass for the actual UI.
> See references/inspector-plugins.md for the full GDScript and C# scaffold (custom inspector + EditorProperty + registration boilerplate).
> Godot 4.7+: the static EditorInspector.create_default_inspector(filter_line_edit: LineEdit = null) returns an inspector with the same configuration as the editor's Inspector dock, ready to embed in plugin UIs — pass a LineEdit for live property filtering (see references/inspector-plugins.md). EditorContextMenuPlugin also gains CONTEXT_SLOT_INSPECTOR_PROPERTY in ContextMenuSlot, so context-menu plugins can extend the inspector property right-click menu: _popup_menu() receives [object ID, property name] and the option callback receives the EditorProperty directly.
Add a custom dock to the editor by calling add_control_to_dock(slot, control) from your EditorPlugin._enter_tree. Free the control on _exit_tree. Useful for project-wide tooling UIs (level browser, asset summary, build dashboard).
> See references/dock-panels.md for the full GDScript and C# dock scaffold.
EditorResourcePicker lets you constrain a property to a specific Resource subclass with a tooltip and base-type filter. EditorResourcePreviewGenerator provides custom thumbnails for resources in the FileSystem dock and Inspector.
> See references/inspector-plugins.md for the full GDScript and C# EditorResourcePicker and EditorResourcePreviewGenerator scaffolds.
EditorNode3DGizmoPlugin adds visual handles for 3D nodes in the editor — wireframe shapes, draggable handles, rotation rings. Implement _init (materials), _get_gizmo_name, _has_gizmo, _redraw (draw lines/handles), and _get_handle_value / _set_handle / _commit_handle for interactive editing.
> See references/gizmos-deep-dive.md for the full GDScript and C# gizmo plugin (with undo/redo wiring for handle commits).
> Godot 4.7+: override _can_commit_handle_on_click() -> bool (returns false if not overridden) to commit a handle action even when the final handle position is the same as the initial one — i.e. on a plain click.
Toggle the plugin off and on in Project Settings → Plugins to reload it; saving a @tool script hot-reloads automatically, but new class registrations and dock changes need the full cycle. print() / push_warning() / push_error() go to the Output panel. C# plugins must recompile first — Could not find type "Plugin" means the assembly failed to build, so check the MSBuild panel before anything else.
Reload recipes (GDScript + C# PluginReloader), console launch flags, and the lifecycle-gotcha table (orphaned docks, stale custom types, double-registered inspector plugins): references/testing-plugins.md
plugin.cfg is a plain INI file placed at the root of the plugin folder. All fields in the [plugin] section are required except dependencies and installs.
ini[plugin] name="My Plugin" description="Adds MyNode, a custom inspector, and a dock panel to the editor." author="Your Name" version="1.0.0" script="plugin.gd"
Field reference:
| Key | Type | Description | |---|---|---| | name | String | Display name shown in Project Settings → Plugins | | description | String | Short summary shown in the Plugins panel | | author | String | Author name or organisation | | version | String | Semantic version string (e.g. "1.2.0") | | script | String | Path to the main EditorPlugin script, relative to the plugin folder |
Complete example with all optional fields:
ini[plugin] name="My Plugin" description="Adds MyNode, a custom inspector, and a dock panel to the editor." author="Your Name" version="1.0.0" script="plugin.gd"
> There are no other standard keys in Godot 4.x plugin.cfg. Dependency management is handled externally (e.g., by the Asset Library or manual installation instructions).
addons/<plugin_name>/plugin.cfg exists with name, description, author, version, scriptEditorPlugin and is decorated with @tool (GDScript) or [Tool] inside #if TOOLS (C#)_enter_tree() is unregistered in _exit_tree()add_custom_type / remove_custom_type with a matching icon SVG@tool scripts guard editor-only code with Engine.is_editor_hint()_get_configuration_warnings() returns non-empty array when node is misconfigured_can_handle to avoid handling unintended types_parse_property returns true only for properties that need a custom editorCustom Minimum Size set so the panel is usable at default dock widthsControl is freed with queue_free() in _exit_tree()EditorResourcePreviewGenerator is both added and removed via EditorInterface.get_resource_previewer()_commit_handle with get_undo_redo() so handle drags are undoablepush_error() used instead of silent failures in all _enter_tree setup paths#if TOOLS / #endif| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 31,293 | 6,985 | -78% | 1 | 1 | 0% | 5,975 | 5,031 | -16% | 0 | 0 | — |
case-02 | pass→pass | 6,063 | 6,010 | -1% | 1 | 1 | 0% | 1,381 | 4,787 | +247% | 0 | 0 | — |
case-03 | pass→pass | 7,010 | 5,398 | -23% | 1 | 1 | 0% | 1,329 | 4,515 | +240% | 0 | 0 | — |
case-12 | fail→pass | 10,697 | 5,291 | -51% | 1 | 1 | 0% | 1,872 | 4,383 | +134% | 0 | 0 | — |
case-04 | pass→pass | 4,274 | 3,002 | -30% | 1 | 1 | 0% | 898 | 4,037 | +350% | 0 | 0 | — |
case-05 | pass→pass | 7,354 | 4,870 | -34% | 1 | 1 | 0% | 1,280 | 4,349 | +240% | 0 | 0 | — |
case-06 | pass→pass | 5,406 | 4,569 | -15% | 1 | 1 | 0% | 1,006 | 4,181 | +316% | 0 | 0 | — |
case-07 | pass→pass | 5,024 | 4,374 | -13% | 1 | 1 | 0% | 875 | 4,203 | +380% | 0 | 0 | — |
case-08 | pass→pass | 17,821 | 12,265 | -31% | 1 | 1 | 0% | 3,381 | 5,943 | +76% | 0 | 0 | — |
case-09 | fail→pass | 31,765 | 3,754 | -88% | 1 | 1 | 0% | 5,846 | 4,166 | -29% | 0 | 0 | — |
case-10 | fail→pass | 22,191 | 3,492 | -84% | 1 | 1 | 0% | 3,788 | 4,065 | +7% | 0 | 0 | — |
case-11 | fail→pass | 17,044 | 8,175 | -52% | 1 | 1 | 0% | 3,239 | 5,016 | +55% | 0 | 0 | — |
case-13 | pass→pass | 7,812 | 2,917 | -63% | 1 | 1 | 0% | 1,427 | 3,987 | +179% | 0 | 0 | — |
case-14 | pass→pass | 8,863 | 6,041 | -32% | 1 | 1 | 0% | 1,446 | 4,363 | +202% | 0 | 0 | — |
case-15 | pass→pass | 8,054 | 5,183 | -36% | 1 | 1 | 0% | 1,486 | 4,289 | +189% | 0 | 0 | — |
case-16 | pass→pass | 7,157 | 6,188 | -14% | 1 | 1 | 0% | 1,190 | 4,478 | +276% | 0 | 0 | — |
case-17 | pass→pass | 3,460 | 2,731 | -21% | 1 | 1 | 0% | 609 | 3,884 | +538% | 0 | 0 | — |
case-18 | pass→pass | 8,580 | 6,043 | -30% | 1 | 1 | 0% | 1,627 | 4,500 | +177% | 0 | 0 | — |
case-19 | pass→pass | 7,523 | 9,954 | +32% | 1 | 1 | 0% | 1,475 | 5,207 | +253% | 0 | 0 | — |
case-20 | pass→pass | 9,029 | 6,843 | -24% | 1 | 1 | 0% | 1,604 | 4,619 | +188% | 0 | 0 | — |
case-21 | pass→pass | 8,688 | 8,999 | +4% | 1 | 1 | 0% | 1,578 | 5,055 | +220% | 0 | 0 | — |
case-22 | pass→pass | 15,650 | 10,898 | -30% | 1 | 1 | 0% | 2,886 | 5,608 | +94% | 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 +23 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.