UE Editor Tools
Target engine: UE 5.8. APIs below are verified against the 5.8 headers; older forms are listed under "Deprecated — do not use".
Editor extensions live in a module with "Type": "Editor" and depend on UnrealEd, Slate, SlateCore, PropertyEditor (detail panels), ToolMenus (menus and toolbars), EditorSubsystem (UEditorSubsystem), Blutility (editor utility widgets and scripted actions), AssetTools and AssetDefinition (Content Browser asset types). None of these may be referenced unconditionally from a Runtime module (engine runtime modules add them only under if (Target.bBuildEditor) plus #if WITH_EDITOR, e.g. AIModule.Build.cs:31-34).
Context
Read .agents/ue-project-context.md if it exists (module names, conventions, enabled plugins, GAS/networking setup). Do not stop if it is missing.
Identify the area from the request and the codebase. Ask only when two plausible readings would produce different code.
Editor Module Setup
.uproject (or .uplugin) module entry — Default is enough for detail customizations and UToolMenus::RegisterStartupCallback menus (CommonUIEditor registers its detail customizations at Default); use PostEngineInit when StartupModule itself needs GEditor or other engine-initialised state:
Declare the module class, define both lifecycle functions, then place IMPLEMENT_MODULE last:
RegisterMenus and OpenMyPanel are defined under Menus and Toolbars. FRegisterCustomClassLayoutParams carries TOptional<int32> OptionalOrder; set it when two modules customize the same class and the order matters, otherwise pass a default-constructed struct.
Directory layout, WITH_EDITOR guards, factories, thumbnails, toolkits and the full mode boilerplate: editor-module-setup.md [blocked].
Detail Customizations
IDetailCustomization replaces the whole details panel layout for a UClass. IPropertyTypeCustomization replaces the row for a USTRUCT or instanced object property. Both are registered from StartupModule as shown above.
The single-argument override does the layout work: EditCategory, GetProperty, AddProperty, AddCustomRow, HideProperty, GetObjectsBeingCustomized — full bodies in the reference below.
The USTRUCT counterpart, FMyRangeCustomization : public IPropertyTypeCustomization, overrides CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) and CustomizeChildren(TSharedRef<IPropertyHandle> PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils).
Key interfaces (all in Engine/Source/Editor/PropertyEditor/Public):
GetValue/SetValue return FPropertyAccess::Result (Success, Fail, MultipleValues). NotifyPostChange takes EPropertyChangeType::Type, e.g. EPropertyChangeType::ValueSet. ECategoryPriority::Type sorts top-down: Variable, Transform, Important, TypeSpecific, Default, Uncommon.
Full row patterns, CustomizeHeader/CustomizeChildren bodies, SObjectPropertyEntryBox, per-object value enumeration and fonts: detail-customization-patterns.md [blocked].
Editor Utility Widgets and Scripted Actions
UEditorUtilityWidget (EditorUtilityWidget.h, module Blutility) is a UUserWidget that runs in the editor. Subclass in C++ to expose helper functions, then build the visuals in a Blueprint child.
UEditorUtilityLibrary (EditorUtilityLibrary.h, Blutility) also provides GetSelectedAssetData(), GetSelectedAssetsOfClass(UClass*), GetSelectionSet() (level actors) and SyncBrowserToFolders(const TArray<FString>&).
Open a widget Blueprint from code through UEditorUtilitySubsystem (EditorUtilitySubsystem.h):
Scripted actions: any UFUNCTION(CallInEditor) on a UAssetActionUtility subclass appears in the Content Browser context menu; UActorActionUtility does the same for level actors. Both derive from UEditorUtilityObject. Filter with the SupportedClasses array (TArray<TSoftClassPtr<UObject>>, editable in Class Defaults); read it back with GetSupportedClasses().
The editor builds these menus without loading the utility asset, from asset-registry tags wrapped by FAssetActionUtilityPrototype (EditorUtilityAssetPrototype.h). Keep the filter in SupportedClasses on the CDO rather than computing it at run time.
Menus and Toolbars
UToolMenus (module ToolMenus) owns every editor menu and toolbar. Register from the startup callback so Slate and the menus you extend exist.
FToolMenuOwnerScoped tags everything created in its scope with the owner so UnregisterOwner removes it all. Useful menu names: LevelEditor.MainMenu.Window, LevelEditor.MainMenu.Tools, LevelEditor.LevelEditorToolBar.PlayToolBar, LevelEditor.LevelEditorToolBar.User, LevelEditor.ActorContextMenu, ContentBrowser.AssetContextMenu. Icons are FSlateIcon(FAppStyle::GetAppStyleSetName(), "StyleName").
Keyboard chords and reusable commands use TCommands with UI_COMMAND; see editor-module-setup.md [blocked].
Editor Subsystems
UEditorSubsystem (module EditorSubsystem, header EditorSubsystem.h) is auto-instantiated once the owning module is loaded — there is no registration call. Reach it with GEditor->GetEditorSubsystem<T>().
UEditorSubsystem derives from UDynamicSubsystem; Initialize(FSubsystemCollectionBase&), Deinitialize() and ShouldCreateSubsystem(UObject* Outer) const come from USubsystem, so always chain to Super::. Engine editor subsystems worth calling before writing your own:
The canonical subsystem type table (game instance, world, local player, engine, editor) lives in ue-cpp-foundations.
Transactions and Editor Delegates
Every editor mutation of a UObject must be wrapped in a transaction and preceded by Modify(), or undo restores stale data:
FScopedTransaction (ScopedTransaction.h, UnrealEd) also offers Cancel() and IsOutstanding(). After a direct property write on an asset call MarkPackageDirty() and PostEditChange().
Editor Modes
UEdMode (Tools/UEdMode.h, UnrealEd) is the mode base class. Every UEdMode subclass is discovered automatically by UAssetEditorSubsystem::RegisterEditorModes once all module loading phases complete — fill Info in the constructor and do not call a registry.
- Set
Info = FEditorModeInfo(EM_MyEditorMode, DisplayName, FSlateIcon(...), /*InVisibility=*/true)in the constructor;Infois a protectedUEdModemember.Enter()andExit()must callSuper::. - Interactive tools: from
Enter(), callRegisterTool(TSharedPtr<FUICommandInfo> UICommand, FString ToolIdentifier, UInteractiveToolBuilder* Builder, EToolsContextScope ToolScope = EToolsContextScope::Default).GetInteractiveToolsContext(EToolsContextScope)returns theUEditorInteractiveToolsContext; the mode's own context object is aUEdModeInteractiveToolsContext. - Viewport input (
MouseMove,InputKey,HandleClick,InputDelta,Tick) comes from implementingILegacyEdModeViewportInterface(Tools/LegacyEdModeInterfaces.h).RenderandDrawHUDcome fromILegacyEdModeWidgetInterface;UBaseLegacyWidgetEdModeimplements both and forwards to anFLegacyEdModeWidgetHelperreturned by yourCreateWidgetHelper(). - Activate with
GLevelEditorModeTools().ActivateMode(UMyEditorMode::EM_MyEditorMode); query withIsModeActive/GetActiveScriptableMode; leave withDeactivateMode. FEdMode(EdMode.h) is the legacy shared-pointer mode; it derives fromFLegacyEdModeWidgetHelperand is registered throughFEditorModeRegistry::Get().RegisterMode<T>(...)/UnregisterMode(...). Write new modes asUEdMode.
Full mode implementation with a toolkit: editor-module-setup.md [blocked].
Asset Types Factories and Thumbnails
UAssetDefinition (module AssetDefinition, header AssetDefinition.h) is the Content Browser integration point in 5.8. Derive from UAssetDefinitionDefault (AssetDefinitionDefault.h, UnrealEd) so OpenAssets already opens a generic FSimpleAssetEditor (AssetDefinitionDefault.cpp:16-25). Definitions are discovered from their CDOs — no registration call.
Override FText GetAssetDisplayName() const, FLinearColor GetAssetColor() const, TSoftClassPtr<UObject> GetAssetClass() const and TConstArrayView<FAssetCategoryPath> GetAssetCategories() const; override EAssetCommandResult OpenAssets(const FAssetOpenArgs& OpenArgs) const only when the default toolkit routing is not what you want.
EAssetCategoryPaths supplies Basic, Animation, Audio, Blueprint, Cinematics, Data, Foliage, FX, Gameplay, AI and more; nest with FAssetCategoryPath(EAssetCategoryPaths::Gameplay, INVTEXT("My Game")). Context-menu entries for the type are added through UToolMenus extensions of ContentBrowser.AssetContextMenu, not through the definition class.
FAssetTypeActions_Base (AssetTypeActions_Base.h, module AssetTools) with IAssetTools::RegisterAssetTypeActions / UnregisterAssetTypeActions still compiles, but IAssetTypeActions members are being deprecated in favour of the definition system. Use UAssetDefinition for new types.
- Factory: a
UFactorysubclass in the editor module setsSupportedClass,bCreateNew,bEditAfterNewin its constructor and overridesFactoryCreateNew(UClass*, UObject*, FName, EObjectFlags, UObject*, FFeedbackContext*). It is auto-discovered; no registration. - Thumbnail: subclass
UThumbnailRenderer, overrideDraw(UObject*, int32 X, int32 Y, uint32 Width, uint32 Height, FRenderTarget*, FCanvas*, bool bAdditionalViewFamily), thenUThumbnailManager::Get().RegisterCustomRenderer(UMyDataAsset::StaticClass(), UMyThumbnailRenderer::StaticClass())inStartupModuleandUnregisterCustomRenderer(...)inShutdownModule. - Queries: build an
FARFilter(AssetRegistry/ARFilter.h) withClassPaths,PackagePaths,bRecursiveClasses,bRecursivePathsand pass it toIAssetRegistry::GetAssets(const FARFilter&, TArray<FAssetData>&, bool bSkipARFilteredAssets = true).
Full definition, factory and thumbnail implementations: editor-module-setup.md [blocked].
Asset Editor Toolkits
FAssetEditorToolkit (Toolkits/AssetEditorToolkit.h) hosts a tabbed editor window. GetToolkitFName, GetBaseToolkitName and GetWorldCentricTabPrefix are pure virtual; your init function builds a layout with FTabManager::NewLayout and calls InitAssetEditor(Mode, InitToolkitHost, AppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectToEdit).
Find or focus an open editor with IAssetEditorInstance* Instance = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(Asset, /*bFocusIfOpen=*/false); then Instance->FocusWindow(Asset) or Instance->CloseWindow(EAssetEditorCloseReason::AssetEditorHostClosed).
Data Validation
UEditorValidatorBase ships in the DataValidation plugin (enabled by default; module DataValidation, LoadingPhase: PreDefault). Validators are discovered from their CDOs; override CanValidateAsset_Implementation(const FAssetData& InAssetData, UObject* InObject, FDataValidationContext& InContext) const and ValidateLoadedAsset_Implementation(const FAssetData& InAssetData, UObject* InAsset, FDataValidationContext& Context). Run headless with UnrealEditor-Cmd <Project>.uproject -run=DataValidation.
Deprecated — do not use
Common Mistakes
Editor headers in a Runtime module: UnrealEd, PropertyEditor, Blutility and ToolMenus are editor-only and are not linked into packaged builds, so a Runtime module that depends on them unconditionally fails to package (UBT throws BuildException, UnrealEd.Build.cs:12-14). Put the code in MyGameEditor; use #if WITH_EDITOR only for runtime-class hooks such as PostEditChangeProperty.
IMPLEMENT_MODULE before the class exists: the macro must follow the FMyGameEditorModule declaration and both function definitions — put it at the end of the .cpp.
Register without unregister: every RegisterCustomClassLayout, RegisterCustomPropertyTypeLayout, RegisterCustomRenderer and RegisterStartupCallback needs its pair in ShutdownModule, guarded by FModuleManager::Get().IsModuleLoaded(...). Missing pairs crash on Live Coding reload.
Editing a UObject without a transaction: always FScopedTransaction plus Object->Modify() before the write.
Calling FEditorModeRegistry::RegisterMode for a UEdMode: UEdMode subclasses are auto-registered from their CDOs; the registry is only for FEdMode.
Raw this captured in a Slate lambda: capture TWeakPtr/TWeakObjectPtr and pin before use — detail-panel widgets outlive the customization object.
ForceRefreshDetails() on every value change: it destroys and rebuilds the layout. Call it only when the row structure changes; drive values with TAttribute and _Lambda bindings.
Touching GEditor in StartupModule at LoadingPhase: Default: project modules at Default load before the editor engine exists; defer through UToolMenus::RegisterStartupCallback / FCoreDelegates::GetOnPostEngineInit() (the OnPostEngineInit member is UE_DEPRECATED(5.8), CoreDelegates.h:240), or load at PostEngineInit.
Related Skills
ue-module-build-system—.Build.cssyntax, module types, public vs private dependencies, plugin descriptorsue-cpp-foundations—UCLASS/UPROPERTY/UFUNCTIONspecifiers, canonical subsystem type table,UObjectlifecycleue-ui-umg-slate— Slate widget fundamentals (SNew,TAttribute,FReply, compound widgets) used by every custom rowue-data-assets-tables—UDataAsset,UPrimaryDataAssetandUDataTabletypes that these editors and definitions wrapue-testing-debugging—UE_LOGcategories and verbosity, automation tests for editor code, Insights profilingue-actor-component-architecture— actor and component classes whose details panels you customizeue-project-context— writing and consuming.agents/ue-project-context.mdue-blueprint-cpp-interop— exposing C++ to Blueprint: UFUNCTION/UPROPERTY meta keys, latent actions and async nodes


