You managed enemy types with an Enum, and now every new enemy means opening that Enum again. Then you try to build holy water that only affects undead, and your Switch on Enum grows a pin for Skeleton, Zombie, and Ghoul, so every new enemy sends you back into the holy water Blueprint too.
For a small prototype that works fine. But the more types you add, the more "add one enemy" turns into "edit things all over the project." The tool that unwinds this is Gameplay Tags. This article covers classifying enemies with hierarchical labels like Enemy.Undead.Skeleton so that adding types never forces you to rewrite the matching logic, using a Blueprint-first approach.
What You'll Learn
- A Gameplay Tag is a label with a hierarchy built from dots
- The decisive difference from Enums and Names is matching a whole group by its parent tag
- Adding tags in Project Settings, and managing them in
DefaultGameplayTags.ini- When to use Has Tag / Has Any Tags / Has All Tags / Matches Tag
- Hands-on: anti-undead holy water that needs no edits when you add enemies
Where Enums Break Down
Enums are a good fit for "a value from a fixed set of options." While you only have three enemy types, there is nothing wrong with them.
The problems arrive when the categories grow, and when you want to treat a group as one.

Add Skeleton to E_EnemyType and every Blueprint using Switch on Enum grows a new pin. Holy water, holy magic, the necromancer's resurrection: you open all of them and rewire. Adding one enemy means touching assets that have nothing to do with that enemy.
Enums are also flat. There is no way to express an intermediate grouping like "undead in general," so you are stuck enumerating "Skeleton or Zombie or Ghoul" forever.
Managing them with Name (strings) makes additions free, but now typos go unnoticed until runtime. You will not remember whether you wrote "Undead" or "undead".
Gameplay Tags close both gaps. Additions are free, tags are picked from a dictionary so spelling cannot go wrong, and the hierarchy lets you handle groups at once.
A Gameplay Tag Is a Hierarchical Label
A Gameplay Tag is a label whose hierarchy is separated by . (dots).

Enemy
├─ Enemy.Undead
│ ├─ Enemy.Undead.Skeleton
│ ├─ Enemy.Undead.Zombie
│ └─ Enemy.Undead.Ghoul
└─ Enemy.Beast
├─ Enemy.Beast.Wolf
└─ Enemy.Beast.Bear
The decisive property is that a child tag also matches its parent tag. Query an enemy holding Enemy.Undead.Skeleton with Enemy.Undead and you get true. Query it with Enemy and you also get true.
That one-way relationship is what pays off. Logic that "affects undead" only needs to look at Enemy.Undead, and adding Enemy.Undead.Wraith later changes not a single character of that logic.
The reverse does not hold. Query an enemy that only holds Enemy.Undead with Enemy.Undead.Skeleton and you get false. Remember it as ask with the parent and you hit broadly; ask with the child and you hit narrowly.
Tags work for more than enemy types. They suit states and abilities too.
| Use case | Example tag |
|---|---|
| Species / classification | Enemy.Undead.Skeleton |
| State | Status.Debuff.Poison / Status.Stunned |
| Element / resistance | Damage.Type.Holy / Immune.Fire |
| Equipment classification | Item.Weapon.Sword.TwoHanded |
Gameplay Tags are also a core piece of the Gameplay Ability System (GAS), but you can use them on their own without adopting GAS. This article does not assume GAS. If you're weighing whether to bring GAS in at all, Gameplay Ability System: Deciding Whether to Adopt It can help you decide.
Two Ways to Add Tags
Tags have to be registered in the dictionary before you use them. Unregistered tags never appear in Blueprint dropdowns.

Method 1: Add Them from Project Settings
This is the everyday route.
- Open
Edit→Project Settings→GameplayTagsunder theProjectcategory - Click the
+(Add New Gameplay Tag) underGameplay Tags - Enter
Enemy.Undead.SkeletoninName, as a full path - Describe its purpose in
Commentand confirm withAdd New Tag
You do not need to create Enemy and Enemy.Undead first. Entering the full path creates the intermediate levels automatically.
Registered tags can be renamed, deleted, or given child tags from the right-click menu. Rename updates references for you, which is safer than editing the ini directly.
Method 2: Manage Them in the ini File
Tags you add are saved by default to Config/DefaultGameplayTags.ini in your project.
[/Script/GameplayTags.GameplayTagsSettings]
+GameplayTagList=(Tag="Enemy.Undead.Skeleton",DevComment="Bone enemy")
+GameplayTagList=(Tag="Enemy.Undead.Zombie",DevComment="Rotting enemy")
+GameplayTagList=(Tag="Enemy.Beast.Wolf",DevComment="Beast enemy")
Being text means version-control diffs are readable. You can also edit it directly with the editor closed.
Once the project gets big, switching Source when adding a tag in Project Settings lets you split tags into separate ini files per feature, which reduces conflicts from teammates editing the same lines.
You can also define tags in C++. Using
UE_DECLARE_GAMEPLAY_TAG_EXTERN/UE_DEFINE_GAMEPLAY_TAG_COMMENTfromNativeGameplayTags.hlets C++ code reference tags as variables, so misspellings surface at compile time. Doing so requires adding"GameplayTags"toPublicDependencyModuleNamesin yourBuild.cs(see Your First Step from Blueprint to C++).
Choosing the Right Matching Node
There are two types you work with in Blueprint.
| Type | Contents | Use case |
|---|---|---|
| Gameplay Tag | One tag | Things with a single answer, like "this damage's element" |
| Gameplay Tag Container | A set of tags | Things with several answers, like "the properties this enemy has" |

The matching node's name depends on whether the target is a container or a single tag.
| Node | Target | What it checks |
|---|---|---|
| Has Tag | Container | Whether it holds the given tag or a child of it |
| Has Any Tags | Container | Whether it holds at least one of the given tags |
| Has All Tags | Container | Whether it holds all of the given tags |
| Matches Tag | Single tag | Whether that tag is the given tag or a child of it |
Here it is with concrete values, for an enemy holding both Enemy.Undead.Skeleton and Status.Debuff.Poison.
| Query | Result |
|---|---|
Has Tag(Enemy.Undead) | true (matches as a child tag) |
Has Tag(Enemy.Beast) | false |
Has Any Tags(Enemy.Undead, Enemy.Beast) | true (one match is enough) |
Has All Tags(Enemy.Undead, Status.Debuff) | true (both match) |
Has All Tags(Enemy.Undead, Immune.Fire) | false |
"Affects X" is Has Tag, "affects X or Y" is Has Any Tags, and "only when X and Y" is Has All Tags.
Use the
Exactvariants for exact matching.Has Tag Exactignores the hierarchy, so queryingEnemy.Undead.SkeletonwithEnemy.Undeadreturnsfalse. It is the option when you need "skeletons only, not undead in general."
Empty containers have a trap. When the set of tags you pass in is empty,
Has Any Tagsreturnsfalse, butHas All Tagsinterprets it as "no tags are missing" and returnstrue. If you are feeding conditions in from data, test the empty case once.
Hands-On: Building Anti-Undead Holy Water
An elemental-weakness item in a roguelike, a purification item in a horror game, an anti-air-only tower in a tower defense. "Extra effective against one specific group" shows up regardless of genre. Here we build it so that adding enemy types never requires touching the holy water at all.

Setup for reproducing this: create a new project from the Third Person template. Register these four tags in Project Settings ahead of time.
Enemy.Undead.Skeleton
Enemy.Undead.Zombie
Enemy.Beast.Wolf
Damage.Type.Holy
| Kind | Name | Type / initial value |
|---|---|---|
| Blueprint Class (Actor) | BP_EnemyBase | Parent of the three enemies |
BP_EnemyBase variable | EnemyTags | Gameplay Tag Container / empty. Turn on Instance Editable |
BP_EnemyBase variable | Health | Float / 100.0 |
| Child Blueprint | BP_Skeleton | Enemy.Undead.Skeleton as the default of EnemyTags |
| Child Blueprint | BP_Zombie | Enemy.Undead.Zombie as the default of EnemyTags |
| Child Blueprint | BP_Wolf | Enemy.Beast.Wolf as the default of EnemyTags |
| Blueprint Class (Actor) | BP_HolyWater | Has a Sphere Collision (Radius 200.0) |
BP_HolyWater variable | BaseDamage | Float / 20.0 |
BP_HolyWater variable | HolyMultiplier | Float / 5.0 |
BP_HolyWater variable | TargetTag | Gameplay Tag / default Enemy.Undead. Turn on Instance Editable |
Step 1: create the enemy parent class. Right-click in the Content Browser → Blueprint Class → Actor. Name it BP_EnemyBase.
Add two variables in the My Blueprint panel. For EnemyTags, choose Gameplay Tag Container in the type dropdown. Make Health a Float and set 100.0 as its Default Value in the Details panel. Checking Instance Editable on both lets you add tags per instance placed in a level.
Step 2: create three enemies. Right-click BP_EnemyBase → Create Child Blueprint Class three times. Open each and pick a tag from the + under the Default Value of EnemyTags. Only registered tags appear in the dropdown, so you cannot misspell anything here.
Step 3: write the damage logic in the parent class. Add a custom event called ApplyHolyDamage to BP_EnemyBase with a Damage (Float) input. All it does is subtract Damage from Health and set it.
Step 4: build the holy water Blueprint. Create BP_HolyWater, add a Sphere Collision component, and set its Sphere Radius to 200.0. Set up the three variables as listed in the table above and build the following graph from On Component Begin Overlap.

BP_HolyWater
On Component Begin Overlap (Sphere)
→ Cast To BP_EnemyBase (Object: Other Actor)
success → Get EnemyTags (from As BP Enemy Base)
→ Has Tag (Container: EnemyTags / Tag: TargetTag)
→ Branch
True → Multiply (BaseDamage × HolyMultiplier) → ApplyHolyDamage (Target: As BP Enemy Base)
False → ApplyHolyDamage (Damage: BaseDamage, Target: As BP Enemy Base)
You find the Has Tag node by dragging off the EnemyTags pin and searching. Wire the TargetTag variable into the Tag input. You could hardcode a tag literal there, but making it a variable lets you change the target per holy water instance placed in the level (an anti-beast version is just swapping in Enemy.Beast).
Step 5: place them and check. Line up BP_Skeleton / BP_Zombie / BP_Wolf and drop BP_HolyWater in the middle. Adding a Print String inside ApplyHolyDamage to show the remaining HP makes verification easier (see checking values with Print String).
Press Play and let the enemies enter the holy water's radius. You should see these values.
| Enemy | Damage taken | Remaining Health |
|---|---|---|
BP_Skeleton | 100.0 (20 × 5) | 0.0 |
BP_Zombie | 100.0 | 0.0 |
BP_Wolf | 20.0 | 80.0 |
If only the wolf is left at 80, it works. If all three take 100 damage, suspect the tag wired into Has Tag; if all three take 20, suspect the EnemyTags settings.
Now for the real point. Create BP_Wraith as a child of BP_EnemyBase, add Enemy.Undead.Wraith in Project Settings, and set it on EnemyTags. Drop it in the level and press Play without opening BP_HolyWater, and the wraith takes 100 damage and dies. That is the decisive difference from an Enum.
Two things to take away.
- Write your checks against the parent tag: what you pass to
Has TagisEnemy.Undead, notEnemy.Undead.Skeleton. Picking the tag at the level of "how broadly do I want this to apply" is what makes the design resilient to additions - Tags are data, checks are logic: the tags an enemy carries live in the level or the Blueprint defaults (data), while the tag the holy water looks at is a variable (configuration). Keeping type names out of your logic means expansion becomes a data-side job. Combine it with a Data Table and the enemy parameters move out too (see managing game data with Data Tables)
Bonus: Good to Know for Later
Gameplay Tag Containers can be modified at runtime. For state management, add Status.Debuff.Poison with Add Gameplay Tag when poisoned and Remove Gameplay Tag when it wears off. That keeps state in one place instead of growing a pile of bool variables like bIsPoisoned.
Order tag names from broad to specific. Putting the specific part first, as in Skeleton.Enemy.Undead, throws away the benefit of grouping by hierarchy. The rule is broad category on the left, more specific as you move right.
Do not go too deep, either. Build out Item.Weapon.Melee.Sword.OneHanded.Iron.Rusty and even you will not know which level to check against. Creating only the levels that correspond to actual "I want to handle these together" groupings is enough.
Before adding a tag, ask whether you really need one. If there are only two values and no prospect of more, a bool is fine. Gameplay Tags pay off only when the categories are likely to grow and you want to handle groups together.
Lyra is a good reference for large-scale usage. Epic's official Lyra Sample Game organizes tags into per-category headers defined in C++. It is a big project though, so there is no need to copy it wholesale for solo development.
Summary
| Enum | Name (string) | Gameplay Tag | |
|---|---|---|---|
| Ease of adding | Definition changes ripple everywhere | Free | Free |
| Misspelling | Impossible | Unnoticed until runtime | Impossible, chosen from a dropdown |
| Group matching | Only by enumerating | Not possible | One parent tag does it |
- A Gameplay Tag is a label with a hierarchy built from dots, usable from Blueprint alone with no C++
- Child tags match parent tags. That one-way relationship is what makes it survive additions
- Add them from Project Settings. The actual storage is text in
Config/DefaultGameplayTags.ini - Containers use Has Tag / Has Any Tags / Has All Tags; single tags use Matches Tag
- Write your checks against the tag at the level you want the effect to reach
Once you have organized your types with tags, the next step is moving the per-type numbers out into data. Managing per-enemy HP and rewards together is covered in managing game data with Data Tables, and swapping behavior per type is covered in the Blueprint Interface article.
Which Blueprint in your project currently has the longest Switch on Enum?