Tag Components
A tag component is a component with no data. Its presence on an entity is the entire signal. Tecs stores tags in a per-archetype bitset rather than allocating a value per entity, so membership costs one bit.
If you need a refresher on the shared component model first, start with Component Construction. For the broader component taxonomy, see the Components overview. If you want the same presence-only idea but scoped to a relationship target, see Relationships (a newRelationship with just a name is the presence-only, target-only form) and FFI Relationships.
Use tags for flags, markers, and classification: "this entity is Selected", "this mob is Stunned", "this node is a SpawnPoint". Anything that reduces to "is this entity part of group X?" is a good fit.
Creating a tag component
Create a tag with tecs.newTagComponent:
local Selected = tecs.newTagComponent({name = "Selected"})
local Stunned = tecs.newTagComponent({name = "Stunned"})tecs.newTagComponent accepts the following properties:
| Property | Description |
|---|---|
name | (required) The component name. |
requires | Array of components to auto-add alongside this tag. See Auto-dependencies. |
container | Optional pre-declared container to register as the tag. Rarely needed. |
transient | If true, omit this tag from snapshots. |
Adding, removing, and testing tags
Tags use the standard component API:
world:set(entityId, Selected) -- add
world:remove(entityId, Disabled) -- remove
world:has(entityId, Selected) -- presence check (boolean)You can also spawn an entity directly with tags:
world:spawn(Position(0, 0), Enemy, Hostile)Using tags in queries
Tags slot into query descriptors like any other component:
-- All selected enemies:
world:query({include = {Enemy, Selected}})
-- All enemies that aren't stunned:
world:query({include = {Enemy}, exclude = {Stunned}})Because tags have no column, there's nothing useful to bind inside the archetype loop. Filter on presence via include / exclude in the query descriptor, then iterate the components that do carry data:
for archetype, len, entities in query:iter() do
local positions = archetype:get(Position)
-- archetype:get(Selected) returns nil: tag has no data column
for row = 1, len do
-- Every row is Selected by construction (query's include list guaranteed it).
end
endPerformance
Tag components are highly compact and fast to query. For workloads like "mark every visible entity this frame" across tens of thousands of entities, the difference vs a zero-field table component matters. If you need a single primitive value instead of pure presence, compare them with scalar components before reaching for a table or FFI payload component.
Two performance properties worth knowing:
Presence checks are free. A query matching on a tag is a bitmask test against the archetype's component set, not a column walk.
Add/remove triggers an archetype transition, just like any component. Adding
Selectedto a million entities shuffles them all into new archetypes. For bulk paths useworld:batchSet/world:batchRemoveagainst a query rather than a per-entity loop:teallocal enemiesInBlast = world:query({ include = {Enemy, InBlastRadius}, temp = true }) world:batchSet(enemiesInBlast, Stunned) -- tag all at once -- ...later... world:batchRemove(stunnedQuery, Stunned) -- untag all at once
Built-in tags
Tecs ships a few tag components you'll interact with directly:
tecs.builtins.Disabled: auto-excluded from queries unless the query explicitly includes it. See Disabled entities.tecs.builtins.Paused: not auto-excluded; used by the state stack's"pause"policy to freeze gameplay entities while a paused state is on top.
The state stack also creates tag components at runtime. world:createState("game") returns a tag component that the stack auto-adds to entities spawned while the state is on top.