---
name: roblox-attributes
description: Use when state belongs to a specific Instance - a door, an NPC, a tool, an ore, a player's character or a tagged part - or when replacing IntValue/BoolValue/StringValue objects. Covers the Attributes API, what replicates, allowed types, change listeners, pairing with CollectionService tags, and mirroring Attributes into Charm.
---

# State with Attributes

Attributes are named values stored on an Instance. They replicate from the server to clients automatically, show up
in the Properties window, can be set by builders without touching code, and cost far less than ValueObjects. They're
the best home for **state that belongs to one Instance**.

## API

```luau
door:SetAttribute("Open", true)              -- set (nil removes it)
local open = door:GetAttribute("Open")       -- read (nil if unset)
local all = door:GetAttributes()             -- { [name]: value }

door:GetAttributeChangedSignal("Open"):Connect(function()
	animateDoor(door:GetAttribute("Open"))
end)

door.AttributeChanged:Connect(function(name) -- any attribute on this Instance
	print(name, door:GetAttribute(name))
end)
```

Always read the current value right away as well as connecting, so you handle the state it's already in.

## Rules

- **Types:** string, boolean, number, UDim, UDim2, BrickColor, Color3, Vector2, Vector3, CFrame, NumberSequence,
  ColorSequence, NumberRange, Rect, Font and EnumItem. **No tables and no Instances.** Store an id or a name instead,
  or serialize small tables to a JSON string only if you must.
- **Names:** letters, numbers and underscores, up to 100 characters, and they can't start with `RBX`.
- **Replication:** server changes replicate to every client that can see the Instance. Client changes stay on that
  client. Treat client-set attributes as local visuals only.
- **Authority:** only the server sets attributes that affect gameplay (health, ownership, open, price). Never read an
  attribute the client could have set as proof of anything on the server.
- **Not for secrets:** every client can read every replicated attribute. Keep hidden values (loot tables, answers,
  other players' private data) on the server.
- **Not for hot data:** setting an attribute every frame floods replication. For fast-changing values, replicate at
  a lower rate or compute on the client.

## Patterns

**Tags + attributes = data-driven systems.** Tag the Instances with CollectionService and configure each one with
attributes, so builders tune them in Studio:

```luau
for _, ore in CollectionService:GetTagged("Ore") do
	local hp = ore:GetAttribute("MaxHealth") or 100
	ore:SetAttribute("Health", hp)
end
```

**Per-player state on the Player or character** (server sets, everyone reads): `player:SetAttribute("Team", "Red")`,
`character:SetAttribute("Stunned", true)`. Good for state other systems react to. Private or large per-player data
belongs in Charm synced to that player only.

**Mirror an attribute into Charm** so it composes with the rest of your reactive state:

```luau
local function attributeSignal(instance: Instance, name: string)
	local get, set = Charm.signal(instance:GetAttribute(name))
	local connection = instance:GetAttributeChangedSignal(name):Connect(function()
		set(instance:GetAttribute(name))
	end)
	return get, function()
		connection:Disconnect()
	end
end

local getOpen, stop = attributeSignal(door, "Open")
Charm.effect(function()
	prompt.Enabled = not getOpen()
end)
```

**Clean up.** Connections on an Instance end when it's destroyed, but connections you made in other scripts to
long-lived Instances (Players, ReplicatedStorage) must be disconnected when the thing that owns them goes away.

## Attributes vs Charm vs ValueObjects

- One Instance's state, visible in Studio, replicated to everyone: **Attributes**.
- Game-wide or per-player state, derived values, or anything only some clients should see: **Charm** (with Charm
  Sync). See the `roblox-charm` skill.
- ValueObjects (IntValue and friends): only for old code or APIs that need an Instance. Prefer attributes.
