Quick answer: Verify the tag is registered in Project Settings > Gameplay Tags or in DefaultGameplayTags.ini. Check IsValid() on the FGameplayTag — unregistered tags are invalid and always fail matching. Use HasTag() for hierarchical matching and HasTagExact() for exact matching.

Here is how to fix Unreal Gameplay Tag queries always returning false. You create an FGameplayTag from a string, add it to a container, then query with HasMatchingGameplayTag — false. You print the container contents and the tag is right there. Or you apply a GAS effect that should grant a tag, check for it on the ability system component, and get nothing. Gameplay Tags are strict about registration and hierarchy, and several matching functions have subtly different semantics.

The Symptom

HasMatchingGameplayTag(), HasTag(), or HasAny() returns false even though you believe the tag is present. The container appears to contain the correct tag when printed to the log. Or the tag is never added in the first place despite the code appearing correct.

Variant: parent tag queries return false when only child tags are present (or vice versa). Or tags work in Blueprint but not in C++. Or tags work in PIE but not in packaged builds.

What Causes This

Tag not registered. Gameplay Tags must be registered before use. Registration happens via Project Settings > Gameplay Tags, a GameplayTagTableRow DataTable, or DefaultGameplayTags.ini. Creating a tag from an unregistered string with FGameplayTag::RequestGameplayTag(FName("Unregistered.Tag")) returns an invalid tag. Invalid tags always fail matching.

Typo in tag string. Tag names are case-sensitive strings with dot-separated hierarchy. Status.Debuff.Burning and Status.Debuff.burning are different tags. A typo in either the registration or the query produces a mismatch.

Exact vs hierarchical matching confusion. HasTag() does hierarchical matching: if the container has Status.Debuff.Burning, querying for Status.Debuff returns true. HasTagExact() requires an exact match: querying for Status.Debuff returns false because only the child tag is present. Using the wrong function produces unexpected results.

Tag added to wrong container. In GAS, abilities, effects, and the AbilitySystemComponent all have their own tag containers. Adding a tag to the ASC’s owned tags does not make it appear on an individual ability’s activation tags, and vice versa.

Tag added after the check. If you grant a tag via a GameplayEffect and immediately check for it in the same frame, the effect may not have been applied yet. GAS effects apply during the next ability system tick.

The Fix

Step 1: Register tags properly. The simplest method is Project Settings > Gameplay Tags > Add New Tag. For C++ projects, use DefaultGameplayTags.ini:

; Config/DefaultGameplayTags.ini
[/Script/GameplayTags.GameplayTagsSettings]
+GameplayTagList=(Tag="Status.Debuff.Burning",DevComment="On fire")
+GameplayTagList=(Tag="Status.Debuff.Frozen",DevComment="Frozen solid")
+GameplayTagList=(Tag="Status.Buff.Haste",DevComment="Move speed up")
+GameplayTagList=(Tag="Ability.Attack.Melee",DevComment="Melee attack")
+GameplayTagList=(Tag="Ability.Attack.Ranged",DevComment="Ranged attack")

Parent tags (Status, Status.Debuff) are auto-created when you register a child. You do not need to register every level explicitly.

Step 2: Request tags safely and verify validity.

// Safe: bErrorIfNotFound = true (default)
FGameplayTag BurnTag = FGameplayTag::RequestGameplayTag(
    FName("Status.Debuff.Burning"));

if (!BurnTag.IsValid())
{
    UE_LOG(LogTemp, Error, TEXT("BurnTag is invalid — not registered!"));
    return;
}

// Add to container
FGameplayTagContainer MyTags;
MyTags.AddTag(BurnTag);

Always check IsValid() after requesting a tag. Invalid tags silently fail every query.

Step 3: Use the correct matching function.

// Container has: Status.Debuff.Burning

// Hierarchical match — returns TRUE
bool bHasDebuff = MyTags.HasTag(
    FGameplayTag::RequestGameplayTag(FName("Status.Debuff")));

// Exact match — returns FALSE (only child is present)
bool bHasDebuffExact = MyTags.HasTagExact(
    FGameplayTag::RequestGameplayTag(FName("Status.Debuff")));

// Exact match — returns TRUE
bool bHasBurning = MyTags.HasTagExact(
    FGameplayTag::RequestGameplayTag(FName("Status.Debuff.Burning")));

Step 4: Query multiple tags with HasAny and HasAll.

FGameplayTagContainer RequiredTags;
RequiredTags.AddTag(FGameplayTag::RequestGameplayTag(
    FName("Status.Debuff.Burning")));
RequiredTags.AddTag(FGameplayTag::RequestGameplayTag(
    FName("Status.Debuff.Frozen")));

// True if ANY of the required tags is present
bool bAnyDebuff = MyTags.HasAny(RequiredTags);

// True only if ALL required tags are present
bool bAllDebuffs = MyTags.HasAll(RequiredTags);

Tags in the Gameplay Ability System

In GAS, tags are granted and checked at multiple levels:

// Check tags on the ASC (includes granted tags from effects)
bool bIsBurning = ASC->HasMatchingGameplayTag(BurnTag);

// Get all active tags
FGameplayTagContainer ActiveTags;
ASC->GetOwnedGameplayTags(ActiveTags);

If a GameplayEffect grants a tag, the tag appears on the ASC only while the effect is active. When the effect expires, the tag is automatically removed.

Debugging Tags at Runtime

Use the console command to dump all active tags:

showdebug abilitysystem

This shows all active GameplayEffects and their granted tags on the selected pawn. If your expected tag is not listed, the effect was not applied or has already expired.

“Register the tag, check IsValid, use HasTag for hierarchy. Three steps from tag query always-false to working.”

Related Issues

For ability activation failures, see GameplayAbility Not Activating. For tag-based AI behavior, AI Perception Not Detecting Actors covers related AI configuration patterns.

Tags must be registered. IsValid() catches unregistered tags. HasTag for hierarchy, HasTagExact for exact. Know which you need.