Find Assets By Metadata

How to find Content browser assets using Metadata

This is a more complicated topic, but one of the most powerful asset management tools available in Unreal is the ability to search for assets using Metadata and the Asset Registry. This allows us to use more than just asset paths to manage our assets, making tools more flexible and robust in production.

Understanding the Asset Registry

One of the events that runs during the Unreal Editor startup process is a scan of the project for all of the available assets. This information is collected in the Asset Registry and is relied upon by a number of systems, such as the dropdown menu when changing a Material property:

What it Tracks

The Asset Registry collects the header information for all of the assets in the Unreal Project using the AssetData class. It doesn't load the entire asset, it just tracks some of the essentials:

  • The Asset Class

  • The Asset Name

  • The Asset Path

  • The Asset Metadata

That last bullet point is what we care about here: if an asset has metadata on it, and that metadata key is registered in the Asset Registry, we can search for assets using that metadata.


Getting Assets by Class

Because this is a bit of a complicated topic, we're going to first start with an asset search function that just returns all of the assets of the given class type:

asset_registry_helper = unreal.AssetRegistryHelpers()
asset_registry        = asset_registry_helper.get_asset_registry()


def find_assets_by_class(class_names=["object"]):
    """List all of the available assets matching the provided class names"""

    # create a basic Asset Registry filter based on the class_names
    base_filter = unreal.ARFilter(
        class_names=class_names,
        recursive_classes=True
    )

    # Get the class-based search results
    results = asset_registry.get_assets(base_filter) or []
    
    return results

This is our baseline setup, asking the Asset Registry for a list of assets of the given class.

While there are a few similar functions available in the Asset Registry, I prefer this function because it has a lot of flexibility thanks to the ARFilter it uses, which will be important as we expand our function.


How to Filter by Metadata

Once we have a list of assets from the Asset Registry as AssetData objects we can use the following operation to only get those matching the desired metadata values:

query = [unreal.TagAndValue("METADATA_KEY", "VALUE_TO_LOOK_FOR")]
base_filter = unreal.ARFilter(class_names=["object"], recursive_classes=True)
meta_filter = asset_registry_helper.set_filter_tags_and_values(base_filter, query)

# reduce the results to only those matching the given metadata
results = asset_registry.run_assets_through_filter(results, meta_filter) or []

The set_filter_tags_and_values() function is responsible for most of the magic here - it will create an ARFilter based on the provided metadata key:value pair.

Using an example asset from the Getting Metadata page we can use this to find any assets with "asset_name" set to "eugene" :

Search by Metadata

In a production setting we can often find ourselves wanting to find a file based on multiple metadata values, such as getting version 10 of an particular asset. Let's set up our function to expect our metadata query as a dictionary:

metadata_search_data = {
    "asset_name": "Eugene",
    "asset_version": 10
}

Expanding the earlier function, once we get the list of results from the base_filter we want to loop over each metadata pair, further reducing the results to only those that match:

asset_registry_helper = unreal.AssetRegistryHelpers()
asset_registry        = asset_registry_helper.get_asset_registry()

def find_assets_by_metadata(meta_dict, class_names=["object"]):
    """
    Find assets in the Content Browser based on 
    a given dict of metadata {key:value} pairs
    """

    # create a basic Asset Registry filter based on the class_names
    base_filter = unreal.ARFilter(
        class_names=class_names,
        recursive_classes=True
    )

    # Get the initial class-based search results
    results = asset_registry.get_assets(base_filter) or []

    for key, value in meta_dict.items():
        # Create a new ARFilter based on the {key:value} pair
        query = [unreal.TagAndValue(key, str(value))]
        meta_filter = asset_registry_helper.set_filter_tags_and_values(base_filter, query)

        # reduce the results to only those matching the given metadata
        results = asset_registry.run_assets_through_filter(results, meta_filter) or []

    # return the results as a Python list as it might currently be an unreal.Array
    return list(results)

To simplify the function, here's essentially what it's doing:

  1. Get a list of assets in the project of the given class(es)

  2. Create an Asset Registry Filter for each metadata key:value pair

  3. Pass the list of assets through each Metadata Filter, removing any assets that don't match

With this function we now have a simplified method of searching for assets based on metadata:


Making Metadata Searches Faster

To test the performance of this function I created an Unreal Project with nearly 40,000 tagged assets in it (37,880 to be precise!).

Using the following logic, I tested from zero metadata queries up to two metadata queries timing the function:

start = time.time()
query = {"name": "b"}
results = find_assets_by_metadata(query)
end = time.time()
print(f"took {end-start:.2f} seconds to find {len(results)} matches for {query}!")

This was the result:

An interesting observation here is that the first Asset Registry interaction collecting the list of assets isn't the most expensive check — it's the second interaction, which performs the first Metadata filter.

Something we can try in our function is to start with the first metadata query:

    first_filter = base_filter
    if meta_dict:
        first_key = list(meta_dict.keys())[0]
        first_value = meta_dict.pop(first_key)
        query = [unreal.TagAndValue(first_key, str(first_value))]
        first_filter = asset_registry_helper.set_filter_tags_and_values(base_filter, query)

    results = asset_registry.get_assets(first_filter) or []

Incredibly, this actually made it faster to search by metadata:

That's 0.03 seconds to find a specific tagged asset in the Unreal Project of 37,800 assets.


Expanding the Function

In a production environment we might want more than just the metadata in our asset search function. One example of note is to also filter results based on a Parent Path, only returning assets that live under a specific folder path.

Thankfully this isn't too hard to add, the ARFilter class has a property called package_paths that can help us here. This property along with recursive_paths allow us to filter assets based on the folder they live in.

And here is an updated function with a new parent_path parameter:

asset_registry_helper = unreal.AssetRegistryHelpers()
asset_registry        = asset_registry_helper.get_asset_registry()

def find_assets_by_metadata(meta_dict, class_names=["object"], parent_path = ""):
    """
    Find assets in the Content Browser based on 
    a given dict of metadata {key:value} pairs
    
    use 'parent_path' to only return results that live under that asset path
    """

    # create a basic Asset Registry filter based on the class_names
    # include the parent path if provided
    base_filter = unreal.ARFilter(
        class_names=class_names,
        recursive_classes=True,
        package_paths=[parent_path] if parent_path else [],
        recursive_paths=True,
    )
    
    # Start with the first Metadata Query if provided
    first_filter = base_filter
    if meta_dict:
        first_key = list(meta_dict.keys())[0]
        first_value = meta_dict.pop(first_key)
        query = [unreal.TagAndValue(first_key, str(first_value))]
        first_filter = asset_registry_helper.set_filter_tags_and_values(base_filter, query)

    # Get the initial search results
    results = asset_registry.get_assets(first_filter) or []

    for key, value in meta_dict.items():
        # Create a new ARFilter based on the {key:value} pair
        query = [unreal.TagAndValue(key, str(value))]
        meta_filter = asset_registry_helper.set_filter_tags_and_values(base_filter, query)

        # reduce the results to only those matching the given metadata
        results = asset_registry.run_assets_through_filter(results, meta_filter) or []

    # return the results as a Python list as it might currently be an unreal.Array
    return list(results)

Without a parent_path we get every Level Sequence in the project:

With a parent_path we only get the Level Sequences found in /Game/dev :

This function is now capable of looking for assets based on:

  • Metadata

  • Asset Class

  • Asset Path


Summary

The Asset Registry is quite powerful in Unreal. With this approach our tools can be a lot more robust and no longer need to rely on strict folder pathing or naming conventions, we can treat and manage our assets like a database service.

There's also a lot more functionality that can be added to the function examples above, I definitely encourage giving metadata a try in Unreal

Last updated