$npx -y skills add quodsoler/unreal-engine-skills --skill ue-editor-toolsUse when extending the Unreal Editor with editor tool, editor utility widget, Blutility, detail customization, property customization, editor mode, asset editor, editor subsystem, editor extension, UToolMenus, or scripted asset operations. For Slate fundamentals, see ue-ui-umg-sl
| 1 | # UE Editor Tools |
| 2 | |
| 3 | You are an expert in extending the Unreal Editor with custom tools and workflows. |
| 4 | |
| 5 | ## Context |
| 6 | |
| 7 | Read `.agents/ue-project-context.md` for editor module structure, engine version, team workflows, and project-specific conventions before providing guidance. |
| 8 | |
| 9 | ## Before You Start |
| 10 | |
| 11 | Ask which area the user needs if not clear: |
| 12 | - **Editor Utility Widget** — UMG panel run from editor right-click |
| 13 | - **Blutility** — UAssetActionUtility or UActorActionUtility scripted actions |
| 14 | - **Detail Customization** — Custom property panel (IDetailCustomization / IPropertyTypeCustomization) |
| 15 | - **Custom Editor Mode** — Viewport mode with specialized interaction (FEdMode) |
| 16 | - **Asset Type Actions** — Content Browser integration for a custom asset type |
| 17 | - **Editor Subsystem** — Editor-lifetime singleton (UEditorSubsystem) |
| 18 | - **Menu / Toolbar Extension** — UToolMenus additions to main menu, toolbars, context menus |
| 19 | |
| 20 | --- |
| 21 | |
| 22 | ## Editor Module Setup |
| 23 | |
| 24 | All editor-extending code must live in a module with `"Type": "Editor"`. Never put `UnrealEd` / `PropertyEditor` includes in a Runtime module without `#if WITH_EDITOR` guards. |
| 25 | |
| 26 | ```json |
| 27 | { "Name": "MyGameEditor", "Type": "Editor", "LoadingPhase": "PostEngineInit" } |
| 28 | ``` |
| 29 | |
| 30 | ```csharp |
| 31 | // MyGameEditor.Build.cs — key dependencies |
| 32 | PrivateDependencyModuleNames.AddRange(new string[] { |
| 33 | "Core", "CoreUObject", "Engine", "UnrealEd", |
| 34 | "Slate", "SlateCore", "EditorStyle", |
| 35 | "PropertyEditor", // IDetailCustomization, IPropertyTypeCustomization |
| 36 | "EditorSubsystem", // UEditorSubsystem |
| 37 | "Blutility", // UEditorUtilityWidget, UAssetActionUtility |
| 38 | "ToolMenus", // UToolMenus |
| 39 | "AssetTools", // FAssetTypeActions_Base |
| 40 | "MyGame" |
| 41 | }); |
| 42 | ``` |
| 43 | Module skeleton — every registration in `StartupModule` must be mirrored in `ShutdownModule`: |
| 44 | ```cpp |
| 45 | IMPLEMENT_MODULE(FMyGameEditorModule, MyGameEditor) |
| 46 | |
| 47 | void FMyGameEditorModule::StartupModule() |
| 48 | { |
| 49 | FPropertyEditorModule& PropMod = |
| 50 | FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor"); |
| 51 | PropMod.RegisterCustomClassLayout( |
| 52 | UMyDataAsset::StaticClass()->GetFName(), |
| 53 | FOnGetDetailCustomizationInstance::CreateStatic( |
| 54 | &FMyDataAssetCustomization::MakeInstance)); |
| 55 | PropMod.NotifyCustomizationModuleChanged(); |
| 56 | |
| 57 | UToolMenus::RegisterStartupCallback( |
| 58 | FSimpleMulticastDelegate::FDelegate::CreateRaw( |
| 59 | this, &FMyGameEditorModule::RegisterMenus)); |
| 60 | } |
| 61 | |
| 62 | void FMyGameEditorModule::ShutdownModule() |
| 63 | { |
| 64 | if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) |
| 65 | { |
| 66 | FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor") |
| 67 | .UnregisterCustomClassLayout(UMyDataAsset::StaticClass()->GetFName()); |
| 68 | } |
| 69 | UToolMenus::UnRegisterStartupCallback(this); |
| 70 | if (UToolMenus* TM = UToolMenus::TryGet()) { TM->UnregisterOwner(this); } |
| 71 | } |
| 72 | ``` |
| 73 | |
| 74 | > Full boilerplate with asset type actions, editor modes, and factory: `references/editor-module-setup.md` |
| 75 | |
| 76 | --- |
| 77 | |
| 78 | ## Editor Utility Widgets |
| 79 | |
| 80 | `UEditorUtilityWidget` (from `EditorUtilityWidget.h`) extends `UUserWidget` for editor-only UMG panels. Create as a Blueprint subclass (right-click Content Browser > Editor Utilities > Editor Utility Widget). **Run**: right-click the widget asset > Run Editor Utility Widget. Or subclass in C++: |
| 81 | |
| 82 | ```cpp |
| 83 | #pragma once |
| 84 | #include "EditorUtilityWidget.h" |
| 85 | #include "MyEditorUtilityWidget.generated.h" |
| 86 | |
| 87 | UCLASS() |
| 88 | class MYGAMEEDITOR_API UMyEditorUtilityWidget : public UEditorUtilityWidget |
| 89 | { |
| 90 | GENERATED_BODY() |
| 91 | public: |
| 92 | UFUNCTION(BlueprintCallable, Category = "My Tools") |
| 93 | void BatchRenameSelectedAssets(const FString& Prefix); |
| 94 | }; |
| 95 | |
| 96 | // .cpp |
| 97 | void UMyEditorUtilityWidget::BatchRenameSelectedAssets(const FString& Prefix) |
| 98 | { |
| 99 | for (UObject* Asset : UEditorUtilityLibrary::GetSelectedAssets()) |
| 100 | { |
| 101 | if (Asset) |
| 102 | { |
| 103 | const FString Src = Asset->GetOutermost()->GetName(); // e.g. /Game/Folder/OldName |
| 104 | UEditorAssetLibrary::RenameAsset(Src, FPaths::GetPath(Src) / Prefix + TEXT("_") + Asset->GetName()); |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | ``` |
| 109 | |
| 110 | Key `UEditorUtilityLibrary` functions (from `EditorUtilityLibrary.h`): |
| 111 | |
| 112 | | Function | Purpose | |
| 113 | |---|---| |
| 114 | | `GetSelectedAssets()` | Currently selected Content Browser assets | |
| 115 | | `GetSelectedAssetsOfClass(UClass*)` | Filter selection by class | |
| 116 | | `UEditorAssetLibrary::DeleteAsset(Path)` | Delete asset; `RenameAsset` for move/rename | |
| 117 | | `GetSelectionSet()` | Selected level actors | |
| 118 | | `SyncBrowserToFolders(TArray<FString>)` | Sync content browser view | |
| 119 | |
| 120 | **Content browser filters**: Use `FARFilter` with `IAssetRegistry::GetAssets` for programma |