Content/Skills/asset-management/SKILL.md
Search, find, open, move, duplicate, save, delete, import, and export assets
npx skillsauth add kevinpbuckley/vibeue asset-managementInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
search_assets Does NOT Search Engine Contentsearch_assets(term, type) only searches /Game/ content. There is NO third parameter.
# WRONG - will error (only 2 params exist)
assets = unreal.AssetDiscoveryService.search_assets("Cube", "", True)
# CORRECT - use list_assets_in_path for engine content
engine_assets = unreal.AssetDiscoveryService.list_assets_in_path("/Engine")
cubes = [a for a in engine_assets if "Cube" in str(a.asset_name)]
| WRONG (old) | CORRECT (UE 5.7) |
|-------------|------------------|
| asset.name | asset.asset_name |
| asset.path | asset.package_path |
| asset.asset_class | asset.asset_class_path |
To bring an image file from disk into the Content Browser, use the manage_asset import action
(or AssetDiscoveryService.import_asset). Do NOT call unreal.AssetToolsHelpers...import_asset_tasks
or ImportAssets from execute_python_code — those pump the game-thread task graph and trip a
RecursionGuard assertion that crashes the editor when run from inside a tool call.
# PREFERRED — MCP tool (robust, no crash)
# manage_asset(action="import",
# source_file_path="C:/Images/rocks.jpg",
# destination_path="/Game/UI/Textures",
# asset_name="T_Rocks")
# Python equivalent (same safe C++ path)
import unreal
path, err = unreal.AssetDiscoveryService.import_asset(
"C:/Images/rocks.jpg", "/Game/UI/Textures", "T_Rocks")
print(path or err)
# WRONG — crashes the editor (TaskGraph RecursionGuard assertion)
# tasks = [unreal.AssetImportTask()]; unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks(tasks)
Supported image formats: png, jpg, jpeg, bmp, tga, dds, exr, hdr, tiff, tif, psd, pcx.
Use /Game/... paths, not file system paths.
# WRONG - file system path
asset = unreal.AssetDiscoveryService.find_asset_by_path("C:/Projects/Content/BP_Player.uasset")
# CORRECT - content browser path
asset = unreal.AssetDiscoveryService.find_asset_by_path("/Game/Blueprints/BP_Player")
Duplicating creates a new asset identity. References stay pointed at the original asset, so deleting the original after a duplicate can break those references.
Use a real move instead:
import unreal
unreal.AssetDiscoveryService.move_asset(
"/Game/StateTree/STT_Rotate",
"/Game/StateTree/Tasks/STT_Rotate"
)
# MCP equivalent
# manage_asset(action="move", source_path="/Game/StateTree/STT_Rotate", destination_path="/Game/StateTree/Tasks/STT_Rotate")
| Does NOT Exist | Alternative |
|----------------|-------------|
| asset_exists() | find_asset_by_path() → check for None |
| is_asset_in_use() | get_asset_referencers() → check if empty |
| rename_asset() | move_asset(old_path, new_path) |
| export_asset() | export_texture() for textures only |
import unreal
# By name (partial match) - ONLY searches /Game/ content
results = unreal.AssetDiscoveryService.search_assets("Player", "Blueprint")
for asset in results:
print(f"{asset.asset_name}: {asset.package_path}")
# By folder
assets = unreal.AssetDiscoveryService.list_assets_in_path("/Game/Blueprints")
# By type only
all_bps = unreal.AssetDiscoveryService.get_assets_by_type("Blueprint")
import unreal
# Search engine content (e.g., for built-in meshes like Cube)
engine_assets = unreal.AssetDiscoveryService.list_assets_in_path("/Engine")
cubes = [a for a in engine_assets if "Cube" in str(a.asset_name)]
# Filter by type
meshes = unreal.AssetDiscoveryService.list_assets_in_path("/Engine", "StaticMesh")
import unreal
asset = unreal.AssetDiscoveryService.find_asset_by_path("/Game/BP_Player")
if asset:
print(f"Found: {asset.asset_name}")
else:
print("Not found - create it")
import unreal
success = unreal.AssetDiscoveryService.duplicate_asset("/Game/BP_Enemy", "/Game/BP_EnemyBoss")
if success:
print("Duplicated")
import unreal
success = unreal.AssetDiscoveryService.move_asset(
"/Game/StateTree/STT_Rotate",
"/Game/StateTree/Tasks/STT_Rotate"
)
if success:
print("Moved without breaking references")
import unreal
# Save specific asset
unreal.AssetDiscoveryService.save_asset("/Game/BP_Player")
# Save all dirty assets
count = unreal.AssetDiscoveryService.save_all_assets()
print(f"Saved {count} assets")
import unreal
refs = unreal.AssetDiscoveryService.get_asset_referencers("/Game/SM_Weapon")
if len(refs) == 0:
unreal.AssetDiscoveryService.delete_asset("/Game/SM_Weapon")
else:
print(f"In use by {len(refs)} assets")
import unreal
# Import (disk → Content Browser). Returns (asset_path, error); asset_path is "" on failure.
# Pass a destination FOLDER + optional asset name.
path, err = unreal.AssetDiscoveryService.import_asset(
"C:/Textures/logo.png", "/Game/Textures", "T_Logo")
print(path or err)
# import_texture(src, dest_asset_path) still works (takes a full asset path) and now uses the
# same crash-safe importer under the hood:
unreal.AssetDiscoveryService.import_texture("C:/Textures/logo.png", "/Game/Textures/T_Logo")
# Export (project → file system)
unreal.AssetDiscoveryService.export_texture("/Game/Textures/T_Logo", "C:/Exports/logo.png")
Prefer the
manage_assetimport action in tool flows:manage_asset(action="import", source_file_path="C:/Textures/logo.png", destination_path="/Game/Textures", asset_name="T_Logo")
import unreal
# Get all open assets
open_assets = unreal.AssetDiscoveryService.get_open_assets()
print(f"Editing {len(open_assets)} assets")
# Check if specific asset is open
if unreal.AssetDiscoveryService.is_asset_open("/Game/BP_Player"):
print("BP_Player is open")
# Get selected assets in Content Browser
selected = unreal.AssetDiscoveryService.get_content_browser_selections()
for asset in selected:
print(asset.asset_name)
# Get primary selection
asset = unreal.AssetData()
if unreal.AssetDiscoveryService.get_primary_content_browser_selection(asset):
print(f"Selected: {asset.asset_name}")
Assets not covered by VibeUE services (e.g., LandscapeGrassType) require AssetToolsHelpers + a factory:
import unreal
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
# LandscapeGrassType
factory = unreal.LandscapeGrassTypeFactory()
lgt = asset_tools.create_asset("LGT_MyGrass", "/Game/Landscape", unreal.LandscapeGrassType, factory)
# After creation, set properties via set_editor_property, then save
unreal.EditorAssetLibrary.save_asset("/Game/Landscape/LGT_MyGrass")
Tip: Use
discover_python_module("unreal", name_filter="Factory")to find available factories for other asset types.
search_assets(term, type) searches by name/type but may return empty for niche types like LandscapeGrassType. If search returns nothing:
get_assets_by_type("LandscapeGrassType") for type-specific querieslist_assets_in_path("/Game/SomeFolder") and filter in PythonLandscapeGrassType, not GrassType)Blueprint - Blueprint classesWidgetBlueprint - UMG Widget BlueprintsTexture2D - Texture assetsStaticMesh - Static mesh assetsSkeletalMesh - Skeletal mesh assetsMaterial - MaterialsMaterialInstanceConstant - Material instancesDataTable - Data TablesPrimaryDataAsset - Primary Data AssetsLandscapeGrassType - Landscape grass/vegetation scatter typesLandscapeLayerInfoObject - Landscape paint layer infotesting
Procedurally design an AAA-style open-world FPS map blockout (roads, POIs, fields, forests/treelines, railway/bridges) from a VibeUE-generated landscape, validate it through gated checks, and materialize the plan into real engine geometry (splines, paint layers, foliage, actors).
tools
Create and manage Niagara particle systems - system lifecycle, adding/copying emitters, user parameters, system script settings, scratch-pad authoring
development
Configure Niagara emitter internals - modules, renderers, rapid iteration parameters, graph positioning, and scratch-pad authoring (Custom HLSL + node graph)
tools
Create and modify Blueprint assets, variables, functions, and components