Spring Sale: 30% off bundles with SPRINGBUNDLE or 15% off individual products with SPRING15 — ends Apr 15

StraySparkStraySpark
ProductsFree AssetsDocsBlogGamesAbout
StraySparkStraySpark

Game Studio & UE5 Tool Developers. Building professional-grade tools for the Unreal Engine community.

Products

  • Complete Toolkit (Bundle)
  • Procedural Placement Tool
  • Cinematic Spline Tool
  • Blueprint Template Library
  • DetailForge
  • UltraWire
  • Unreal MCP Server
  • Blender MCP Server
  • Godot MCP Server

Resources

  • Free Assets
  • Documentation
  • Blog
  • Changelog
  • Roadmap
  • FAQ
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 StraySpark. All rights reserved.

Back to Blog
tutorial
StraySparkMarch 24, 20265 min read
Building UE5 Editor Tools with Python and Editor Utility Widgets 
Unreal EnginePythonToolsProductivityAutomation

Why Build Custom Editor Tools?

Every UE5 project has repetitive tasks that eat development time: renaming assets to match conventions, batch-updating material properties, validating level setups, generating reports. These tasks are too specific for marketplace plugins but too tedious for manual execution.

Custom editor tools solve this. UE5 provides two approaches:

  • Python scripting: Fast to write, great for automation and batch operations
  • Editor Utility Widgets (EUW): Blueprint-based UI tools that live in the editor

Both run only in the editor (zero runtime overhead), and together they cover most custom tooling needs.

Python Scripting in UE5

Enabling Python

  1. Enable the Python Editor Script Plugin in Edit → Plugins
  2. Restart the editor
  3. Open the Python console: Window → Developer Tools → Output Log → switch to Python

Running Scripts

Three ways to execute Python:

Output Log Console:

import unreal
unreal.log("Hello from Python!")

File Execution:

# Save as Content/Python/my_script.py
# Execute via console: py "Content/Python/my_script.py"

Startup Scripts: Place scripts in Content/Python/init_unreal.py to run on editor startup.

Common Python Operations

Asset Operations

import unreal

# Get the asset registry
asset_registry = unreal.AssetRegistryHelpers.get_asset_registry()

# Find all static meshes in a folder
assets = asset_registry.get_assets_by_path("/Game/Environment/Rocks", recursive=True)

for asset in assets:
    if asset.asset_class_path.asset_name == "StaticMesh":
        unreal.log(f"Found mesh: {asset.asset_name}")

Batch Rename Assets

import unreal

editor_util = unreal.EditorUtilityLibrary()
selected_assets = editor_util.get_selected_assets()

for asset in selected_assets:
    old_name = asset.get_name()
    # Add SM_ prefix to static meshes
    if isinstance(asset, unreal.StaticMesh) and not old_name.startswith("SM_"):
        new_name = f"SM_{old_name}"
        unreal.EditorAssetLibrary.rename_asset(
            asset.get_path_name(),
            f"{asset.get_path_name().rsplit('/', 1)[0]}/{new_name}"
        )
        unreal.log(f"Renamed: {old_name} → {new_name}")

Material Property Batch Update

import unreal

# Find all material instances in a folder
assets = unreal.EditorAssetLibrary.list_assets("/Game/Materials/Terrain", recursive=True)

for asset_path in assets:
    asset = unreal.EditorAssetLibrary.load_asset(asset_path)
    if isinstance(asset, unreal.MaterialInstanceConstant):
        # Set roughness scalar parameter
        unreal.MaterialEditingLibrary.set_material_instance_scalar_parameter_value(
            asset, "Roughness", 0.7
        )
        unreal.log(f"Updated roughness on: {asset.get_name()}")

# Save all modified assets
unreal.EditorAssetLibrary.save_directory("/Game/Materials/Terrain")

Level Validation

import unreal

def validate_level():
    """Check for common level issues."""
    issues = []
    editor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
    actors = editor_subsystem.get_all_level_actors()

    for actor in actors:
        # Check for actors at world origin (likely misplaced)
        location = actor.get_actor_location()
        if location.x == 0 and location.y == 0 and location.z == 0:
            issues.append(f"Actor at origin: {actor.get_name()} ({actor.get_class().get_name()})")

        # Check for missing mesh references
        if isinstance(actor, unreal.StaticMeshActor):
            comp = actor.static_mesh_component
            if comp and not comp.static_mesh:
                issues.append(f"Missing mesh: {actor.get_name()}")

        # Check for negative scale (flipped normals)
        scale = actor.get_actor_scale3d()
        if scale.x < 0 or scale.y < 0 or scale.z < 0:
            issues.append(f"Negative scale: {actor.get_name()} ({scale})")

    if issues:
        for issue in issues:
            unreal.log_warning(issue)
        unreal.log_warning(f"Found {len(issues)} issues!")
    else:
        unreal.log("Level validation passed!")

validate_level()

Texture Audit

import unreal

def audit_textures(path="/Game"):
    """Find textures that are too large or have wrong settings."""
    assets = unreal.EditorAssetLibrary.list_assets(path, recursive=True)
    oversized = []
    non_power_of_two = []

    for asset_path in assets:
        asset_data = asset_registry.get_asset_by_object_path(asset_path)
        if asset_data.asset_class_path.asset_name != "Texture2D":
            continue

        texture = unreal.EditorAssetLibrary.load_asset(asset_path)
        if not texture:
            continue

        width = texture.blueprint_get_size_x()
        height = texture.blueprint_get_size_y()

        # Flag textures larger than 4K
        if width > 4096 or height > 4096:
            oversized.append(f"{asset_path}: {width}x{height}")

        # Flag non-power-of-two textures
        if (width & (width - 1)) != 0 or (height & (height - 1)) != 0:
            non_power_of_two.append(f"{asset_path}: {width}x{height}")

    unreal.log(f"Oversized textures (>4K): {len(oversized)}")
    for t in oversized:
        unreal.log_warning(t)

    unreal.log(f"Non-power-of-two textures: {len(non_power_of_two)}")
    for t in non_power_of_two:
        unreal.log_warning(t)

audit_textures()

Editor Utility Widgets

For tools that need a visual interface, Editor Utility Widgets provide Blueprint-based UI that runs in the editor.

Creating an Editor Utility Widget

  1. Right-click Content Browser → Editor Utilities → Editor Utility Widget
  2. Name it (e.g., EUW_AssetManager)
  3. Double-click to open the Widget Blueprint editor
  4. Design your UI using UMG widgets
  5. Run it via right-click → Run Editor Utility Widget

Example: Batch Asset Processor

Create an EUW with:

  • A text field for the source folder path
  • Checkboxes for operations (rename, move, update properties)
  • A "Process" button
  • A scrollbox for output log

The Button click event calls your Python scripts or Blueprint logic:

// In the EUW Blueprint Graph:
On Button Clicked →
    Get Selected Assets →
    For Each Loop →
        Process Asset (your custom logic) →
        Add to Output Log scrollbox

Example: Level Setup Tool

A tool that sets up standard level elements:

  • Lighting preset buttons: Interior, Exterior Day, Exterior Night, Underground
  • Post-processing presets: Cinematic, Stylized, Realistic, Horror
  • Standard actor spawning: Player start, nav mesh bounds, kill volume, blocking volumes
  • Validation button: Run the validation script and display results

Registering as a Menu Item

Make your EUW accessible from the editor toolbar:

import unreal

# Register a menu entry that opens the widget
menus = unreal.ToolMenus.get()
menu = menus.find_menu("LevelEditor.MainMenu.Tools")
section = menu.add_section("CustomTools", "Custom Tools")

entry = unreal.ToolMenuEntry(
    name="OpenAssetManager",
    type=unreal.MultiBlockType.MENU_ENTRY
)
entry.set_label("Asset Manager")
entry.set_tool_tip("Open the custom asset management tool")
# Set command to open EUW
menu.add_menu_entry("CustomTools", entry)
menus.refresh_all_widgets()

Advanced: Combining Python and C++

For tools that need both UI (EUW) and performance (C++ batch operations):

  1. Write the heavy processing in a C++ Editor Module
  2. Expose functions with UFUNCTION(BlueprintCallable, Category = "EditorTools")
  3. Call those functions from your EUW Blueprint
  4. Use Python for quick scripting and prototyping before moving to C++

Editor Module Setup

// MyEditorTools.Build.cs
PublicDependencyModuleNames.AddRange(new string[] {
    "Core", "CoreUObject", "Engine", "UnrealEd",
    "EditorScriptingUtilities", "Blutility"
});

// MyEditorToolsBPLibrary.h
UCLASS()
class UMyEditorToolsBPLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()

    UFUNCTION(BlueprintCallable, Category = "Editor Tools")
    static int32 BatchUpdateMaterials(const FString& FolderPath, float NewRoughness);

    UFUNCTION(BlueprintCallable, Category = "Editor Tools")
    static TArray<FString> ValidateLevel();
};

Practical Tool Ideas

Asset Naming Convention Enforcer

Scan the project for assets that don't match naming conventions:

  • SM_ prefix for Static Meshes
  • MI_ prefix for Material Instances
  • T_ prefix for Textures
  • BP_ prefix for Blueprints
  • Offer automatic rename

Build Report Generator

Generate a report showing:

  • Total asset count by type
  • Texture memory usage
  • Mesh polygon counts
  • Blueprint complexity scores
  • Unused assets (no references)
  • Missing references

Data Table Editor

A custom editor for gameplay data that's friendlier than UE5's default data table editor:

  • Spreadsheet-like grid editing
  • Validation rules per column
  • Import/Export to CSV
  • Diff view for changes

Level Audit Dashboard

Real-time metrics for the open level:

  • Actor count by class
  • Light count and overlap visualization
  • Draw call estimation
  • Navigation mesh coverage
  • Collision complexity warnings

Building custom editor tools is an investment that pays dividends throughout your project. Start with the task that wastes the most time, automate it, and build from there. Your future self will thank you.

For editor customization without writing code, check out DetailForge — it lets you reorganize UE5's Details Panel with metadata attributes instead of Slate C++.

Tags

Unreal EnginePythonToolsProductivityAutomation

Continue Reading

tutorial

Getting Started with UE5 PCG Framework: Build Your First Procedural World

Read more
tutorial

Nanite Foliage in UE5: The Complete Guide to High-Performance Vegetation

Read more
tutorial

UE5 Lumen Optimization Guide: Achieving 60fps with Dynamic Global Illumination

Read more
All posts