Skip to content

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:

teal
local Selected = tecs.newTagComponent({name = "Selected"})
local Stunned = tecs.newTagComponent({name = "Stunned"})

tecs.newTagComponent accepts the following properties:

PropertyDescription
name(required) The component name.
requiresArray of components to auto-add alongside this tag. See Auto-dependencies.
containerOptional pre-declared container to register as the tag. Rarely needed.
transientIf true, omit this tag from snapshots.

Adding, removing, and testing tags

Tags use the standard component API:

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

teal
world:spawn(Position(0, 0), Enemy, Hostile)

Using tags in queries

Tags slot into query descriptors like any other component:

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

teal
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
end

Performance

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 Selected to a million entities shuffles them all into new archetypes. For bulk paths use world:batchSet / world:batchRemove against a query rather than a per-entity loop:

    teal
    local 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:

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.