Skip to content

Custom rules

Custom rules run next to the built-in ones: in the panel, in Data Validation, in the commandlet and from Python. They use the same severity settings and appear in reports like any other rule.

Every rule derives from the UTAAssetDoctorRule class. You can write rules in Blueprint (simple checks, no auto-fix) or in C++ (full access to the context, auto-fix support).

Every rule has these properties, set in the Blueprint’s Class Defaults or in the C++ constructor:

Property C++ name Meaning
Rule Id RuleId Unique id shown in the panel and reports, for example Project.NoUntitled. If empty, the class name is used (without the _C suffix for Blueprints).
Display Name DisplayName Name shown in the details panel. If empty, the rule id is shown.
Description Description Description of the rule.
Default Severity DefaultSeverity Info, Warning (default) or Error. Used unless the Rules setting has an entry for this rule id.
Enabled by Default bEnabledByDefault Whether the rule runs (default: enabled). Used unless the Rules setting has an entry for this rule id.

To change a custom rule’s severity or disable it, add an entry with its Rule Id to Project Settings > Plugins > ta Asset Doctor > Rules.

  1. In the Content Browser, create a new Blueprint Class. In the Pick Parent Class dialog, open All Classes, type Doctor in the search box and select TAAssetDoctorRule.

  2. Open the Blueprint. In Class Defaults, set Rule Id (for example Project.NoUntitled) and optionally Display Name, Description, Default Severity and Enabled by Default.

  3. In the My Blueprint panel, override Check Asset. It receives the asset’s Asset Data and returns Out Messages, an array of text.

  4. Add one message to Out Messages for every problem you find. Return an empty array when the asset is fine. Each message becomes one issue.

  5. Compile and save the Blueprint.

  6. Open Project Settings > Plugins > ta Asset Doctor and add the Blueprint class to Rules > Custom Rules.

  7. Scan again. Issues from your rule appear in the panel with your rule id.

Blueprint rules are called for every asset in scope, so keep them fast:

  • Read Asset Registry tags with the Get Tag Value node on Asset Data. This doesn’t load the asset.
  • Call Get Asset only when you really need the loaded object. It loads the asset if it isn’t loaded yet, which makes scans much slower.
  • Return early for asset classes you don’t care about.

Issues from Blueprint rules have no automatic fix. Use a C++ rule if you need one.

Native rules are discovered automatically: every non-abstract C++ subclass of UTAAssetDoctorRule in a loaded module is used. You don’t need to add C++ rules to Custom Rules.

Put your rules in an editor module and add the plugin module to its dependencies:

MyGameEditor.Build.cs
PrivateDependencyModuleNames.AddRange(new string[] { "TAAssetDoctor" });

If the rule lives in a plugin of your own, also list TAAssetDoctor in the Plugins section of that plugin’s .uplugin file.

MyRule_NoUntitled.h
#pragma once
#include "TAAssetDoctorRule.h"
#include "MyRule_NoUntitled.generated.h"
UCLASS()
class UMyRule_NoUntitled : public UTAAssetDoctorRule
{
GENERATED_BODY()
public:
UMyRule_NoUntitled()
{
RuleId = TEXT("Project.NoUntitled");
DefaultSeverity = ETAAssetDoctorSeverity::Error;
}
virtual void CheckAsset(FTAAssetDoctorContext& Context) const override
{
if (Context.AssetData.AssetName.ToString().Contains(TEXT("Untitled")))
{
Context.AddIssue(*this, FText::FromString(TEXT("Give this asset a real name.")));
}
}
};

Report the issue with ETAAssetDoctorFixType::Custom and override ApplyFix. Asset Doctor calls ApplyFix for each of your rule’s issues when the user applies fixes.

MyRule_UITextureGroup.h
#pragma once
#include "TAAssetDoctorRule.h"
#include "Engine/Texture2D.h"
#include "MyRule_UITextureGroup.generated.h"
/** Textures in UI folders must use the UI texture group. */
UCLASS()
class UMyRule_UITextureGroup : public UTAAssetDoctorRule
{
GENERATED_BODY()
public:
UMyRule_UITextureGroup()
{
RuleId = TEXT("Texture.UIGroup");
DisplayName = NSLOCTEXT("MyGameEditor", "UITextureGroupName", "UI texture group");
DefaultSeverity = ETAAssetDoctorSeverity::Warning;
}
virtual void CheckAsset(FTAAssetDoctorContext& Context) const override
{
// Cheap checks first: class and folder come from the Asset Registry.
if (!Context.AssetData.IsInstanceOf(UTexture2D::StaticClass())
|| !Context.AssetData.PackagePath.ToString().Contains(TEXT("/UI/")))
{
return;
}
FString LODGroup;
if (Context.GetTag(TEXT("LODGroup"), LODGroup) && !LODGroup.Contains(TEXT("TEXTUREGROUP_UI")))
{
Context.AddIssue(*this,
FText::FromString(TEXT("Textures in UI folders should use the UI texture group.")),
ETAAssetDoctorFixType::Custom);
}
}
protected:
virtual bool ApplyFix(const FTAAssetDoctorIssue& Issue) const override
{
UTexture* Texture = Cast<UTexture>(Issue.AssetPath.TryLoad());
if (!Texture)
{
return false;
}
Texture->Modify();
Texture->LODGroup = TEXTUREGROUP_UI;
Texture->PostEditChange();
Texture->MarkPackageDirty();
return true; // The asset was changed.
}
};

The third argument of AddIssue decides how the issue can be fixed:

Fix type Behavior
ETAAssetDoctorFixType::None (default) The issue has to be fixed by hand.
ETAAssetDoctorFixType::Rename Asset Doctor renames the asset to FixData (the new asset name, in the same folder), updates references and saves the renamed asset.
ETAAssetDoctorFixType::Custom Your rule fixes it. Asset Doctor calls your ApplyFixes with all issues of your rule; the default implementation calls ApplyFix for each one and counts how many returned true. FixData is passed through, so you can store whatever your fix needs.

Custom fixes are applied before renames. Assets changed by a custom fix are not saved automatically; follow the built-in texture fixes and leave them dirty so users can review them.

Passed to CheckAsset for every asset.

struct FTAAssetDoctorContext
{
const FAssetData& AssetData;
/** Set when the asset is already in memory (e.g. during Data Validation). May be null. */
UObject* LoadedAsset = nullptr;
void AddIssue(const UTAAssetDoctorRule& Rule, const FText& Message,
ETAAssetDoctorFixType FixType = ETAAssetDoctorFixType::None, const FString& FixData = FString());
bool GetTag(FName Tag, FString& OutValue) const;
int32 GetIntTag(FName Tag, int32 DefaultValue) const;
bool GetBoolTag(FName Tag, bool bDefaultValue) const;
};
Member Meaning
AssetData Asset Registry data of the asset being checked.
LoadedAsset The loaded asset, if it is already in memory (for example during Data Validation). nullptr during normal scans.
AddIssue Reports one issue for this asset. Pass *this as Rule. The severity comes from the rule’s settings, the asset path and class are filled in for you.
GetTag Reads an Asset Registry tag as a string. Returns false if the asset doesn’t have the tag.
GetIntTag Reads a numeric tag. Returns DefaultValue if the tag is missing or not a number.
GetBoolTag Reads a boolean tag (for example True/False). Returns bDefaultValue if the tag is missing.
Member Meaning
virtual void PrepareForScan() Called once before a batch of assets is checked. Cache anything expensive here, for example settings or class lookups.
virtual void CheckAsset(FTAAssetDoctorContext& Context) const Checks one asset. The default implementation calls the Blueprint Check Asset function.
virtual int32 ApplyFixes(const TArray<FTAAssetDoctorIssue>& Issues) const Applies the custom fixes reported by this rule. Returns the number of assets changed. Override it to fix many assets in one go.
virtual bool ApplyFix(const FTAAssetDoctorIssue& Issue) const Protected. Applies one custom fix. Return true if the asset was changed.
FName GetRuleId() const The rule id (falls back to the class name).
bool IsRuleEnabled() const Enabled state after applying the Rules setting.
ETAAssetDoctorSeverity GetRuleSeverity() const Severity after applying the Rules setting.

Rules run for every asset in scope, often thousands of them. Asset Doctor’s built-in rules never load assets; they only read Asset Registry data. Follow the same approach:

  • Prefer Asset Registry tags (Context.GetTag, GetIntTag, GetBoolTag) over loading assets. For example, the built-in rules read Dimensions, LODGroup, SRGB and CompressionSettings for textures and Triangles, LODs, NaniteEnabled and CollisionPrims for static meshes.
  • Filter first: check the asset class or folder before anything else and return early.
  • Don’t call TryLoad in CheckAsset. Load assets only in ApplyFix, where it’s expected. If you need the object during Data Validation, use Context.LoadedAsset when it isn’t nullptr.
  • Cache in PrepareForScan: work that doesn’t depend on the asset, such as reading settings or resolving classes, belongs there instead of in CheckAsset.

Many tags are shown in an asset’s tooltip in the Content Browser. In C++ you can list all of them with FAssetData::TagsAndValues.