# Find Assets By 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:

<figure><img src="https://368271246-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FStUBkBT2qJGKNkIZ6yc7%2Fuploads%2FLlWUzQ16ULdNcWc8cMcQ%2Fimage.png?alt=media&#x26;token=af847b80-5946-465e-9284-b057a4bd3023" alt="" width="535"><figcaption></figcaption></figure>

### What it Tracks

The Asset Registry collects the header information for all of the assets in the Unreal Project using the [AssetData ](https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/AssetData?application_version=5.3#unreal-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
* <mark style="color:yellow;">The Asset Metadata</mark>

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:

```python
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](https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/AssetRegistry?application_version=5.3#unreal.AssetRegistry.get_assets), I prefer this function because it has a lot of flexibility thanks to the [ARFilter ](https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/ARFilter?application_version=5.3#unreal-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:

```python
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()](https://dev.epicgames.com/documentation/en-us/unreal-engine/python-api/class/AssetRegistryHelpers?application_version=5.3#unreal.AssetRegistryHelpers.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](https://bkortbus.gitbook.io/unreal-python-recipe-book/metadata/getting-metadata "mention") page we can use this to find any assets with `"asset_name"` set to `"eugene"` :

<figure><img src="https://368271246-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FStUBkBT2qJGKNkIZ6yc7%2Fuploads%2FFJQvpkUbYfneQMY9ikTt%2Fimage.png?alt=media&#x26;token=e8cddc2f-3a8c-4c07-8dfe-61ae914037de" alt=""><figcaption></figcaption></figure>

## 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:&#x20;

```python
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:

```python
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:

<figure><img src="https://368271246-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FStUBkBT2qJGKNkIZ6yc7%2Fuploads%2FWptpWLWrIuL3HUzSLFzz%2Fimage.png?alt=media&#x26;token=15a39cdd-396a-46ec-9ad4-3180b8467a1d" alt=""><figcaption></figcaption></figure>

***

## 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:

```python
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:

<figure><img src="https://368271246-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FStUBkBT2qJGKNkIZ6yc7%2Fuploads%2FstybkcdlcAZDTRjvuhkz%2Fimage.png?alt=media&#x26;token=3d28a7b8-99b8-47a2-a897-ab468f506ee4" alt=""><figcaption></figcaption></figure>

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:

```python
    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:

<figure><img src="https://368271246-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FStUBkBT2qJGKNkIZ6yc7%2Fuploads%2F3SfjLHmrzoRMFanOI1Hc%2Fimage.png?alt=media&#x26;token=07f4a60e-9551-4d3e-8c78-2a5fd60b460f" alt=""><figcaption></figcaption></figure>

That's <mark style="color:yellow;">`0.03`</mark> 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 <mark style="color:green;">Parent Path</mark>, 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 <mark style="color:yellow;">parent\_path</mark> parameter:

```python
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:

<figure><img src="https://368271246-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FStUBkBT2qJGKNkIZ6yc7%2Fuploads%2FxJSx2iKOjHwT3NyqOmim%2Fimage.png?alt=media&#x26;token=0317613a-8917-4a20-8457-044ed8eec59a" alt=""><figcaption></figcaption></figure>

With a `parent_path` we only get the Level Sequences found in `/Game/dev` :&#x20;

<figure><img src="https://368271246-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FStUBkBT2qJGKNkIZ6yc7%2Fuploads%2F5fxD7AY8boRidbM9BDIr%2Fimage.png?alt=media&#x26;token=31327960-9836-4df3-9cea-f3abb45f61ff" alt=""><figcaption></figcaption></figure>

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.&#x20;

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&#x20;


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://bkortbus.gitbook.io/unreal-python-recipe-book/metadata/find-assets-by-metadata.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
