In short
Add a tag to a part in the Properties window (Tags, then +) or with part:AddTag("Lava"). In one Script, loop over CollectionService:GetTagged("Lava") to set up every tagged part that exists now, and connect CollectionService:GetInstanceAddedSignal("Lava") for parts tagged or added later. Use GetInstanceRemovedSignal to disconnect anything you connected when a part is untagged or destroyed. Per-part settings go in attributes.
- Roblox's own CollectionService page suggests tags when you find yourself copying the same script into many objects.
- Tags are saved with the place and replicate, so the server and clients both see them.
- Handle both the existing parts and the added signal; doing only one is the usual bug.
Get the script
Tag binder module with clean up
A ModuleScript that runs a setup function for every instance with a tag, now and later, and runs its clean up when the tag is removed or the instance is destroyed.
-- ModuleScript in ReplicatedStorage named TagBinder.
-- bind(tag, setup) -> stop: setup(instance) runs for every instance with the tag, now and later.
-- If setup returns a function, it runs when the tag is removed or the instance is destroyed.
local CollectionService = game:GetService("CollectionService")
local TagBinder = {}
type Cleanup = () -> ()
function TagBinder.bind(tag: string, setup: (instance: Instance) -> Cleanup?): () -> ()
local cleanups: { [Instance]: Cleanup } = {}
local function added(instance: Instance)
if cleanups[instance] then
return -- already set up
end
local ok, result = pcall(setup, instance)
if not ok then
warn("[TagBinder] " .. tag .. " setup failed for " .. instance:GetFullName() .. ": " .. tostring(result))
elseif typeof(result) == "function" then
cleanups[instance] = result
end
end
local function removed(instance: Instance)
local cleanup = cleanups[instance]
cleanups[instance] = nil
if cleanup then
cleanup()
end
end
local removedConnection = CollectionService:GetInstanceRemovedSignal(tag):Connect(removed)
for _, instance in CollectionService:GetTagged(tag) do
task.spawn(added, instance)
end
local addedConnection = CollectionService:GetInstanceAddedSignal(tag):Connect(added)
-- call the returned function to stop the binding and clean everything up
return function()
addedConnection:Disconnect()
removedConnection:Disconnect()
for instance in cleanups do
removed(instance)
end
end
end
return TagBinder
RoCode builds it in the place you have open in Roblox Studio, on Windows or macOS with a desktop browser; on a phone the button sends the prompt and the link to your computer. Free to start, with free credits every day. More Roblox Studio scripts
Where to put it
ReplicatedStorage
TagBinder ModuleScript new
Before you test
Use it from a Script for gameplay and from a LocalScript for effects only one player sees. The next step shows both.
Type-checked with luau-lsp strict against Roblox's API definitions on 5 November 2026. Not playtested in Studio yet: press Play and watch the Output window before you publish.
Prompt for RoCode
Add a TagBinder ModuleScript to ReplicatedStorage: bind(tag, setup) runs setup for every instance with the tag now and later, and calls the clean up function setup returns when the tag is removed or the instance is destroyed. Then use it to make every part tagged Spinner rotate.
On this page
How tags work
A tag is a short piece of text stored on an instance. An instance can have many tags, and the same tag can be on any number of instances. CollectionService keeps track of which instances carry each tag, so a script can ask for all of them at once and be told when the list changes.
| Call | What it does |
|---|---|
CollectionService:GetTagged(tag) | Every instance with that tag, right now, as a list. |
CollectionService:GetInstanceAddedSignal(tag) | Fires when an instance gets the tag, or a tagged instance is added to the game. |
CollectionService:GetInstanceRemovedSignal(tag) | Fires when the tag is removed, or the instance leaves the game. |
instance:AddTag(tag) / RemoveTag(tag) | Add or remove a tag from code. |
instance:HasTag(tag) / GetTags() | Check one tag, or list them all. |
To add a tag in Studio, select the part, scroll the Properties window to Tags, click + and type the name. Select several parts first to tag them all at once. Tags are matched exactly, so Lava and lava are different tags.
Step 1: One script for every tagged part
Tag a few parts Spinner, anchor them, then add this Script to ServerScriptService. Every tagged part spins, including copies you add while the game runs.
-- Script in ServerScriptService named Spinners.
-- Every part tagged "Spinner" turns slowly.
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local TAG = "Spinner"
local DEGREES_PER_SECOND = 90
local spinning: { [BasePart]: boolean } = {}
local function onAdded(instance: Instance)
if instance:IsA("BasePart") then
instance.Anchored = true
spinning[instance] = true
end
end
local function onRemoved(instance: Instance)
if instance:IsA("BasePart") then
spinning[instance] = nil
end
end
for _, instance in CollectionService:GetTagged(TAG) do
onAdded(instance)
end
CollectionService:GetInstanceAddedSignal(TAG):Connect(onAdded)
CollectionService:GetInstanceRemovedSignal(TAG):Connect(onRemoved)
RunService.Heartbeat:Connect(function(deltaTime: number)
local turn = CFrame.Angles(0, math.rad(DEGREES_PER_SECOND * deltaTime), 0)
for part in spinning do
part.CFrame *= turn
end
end)
The loop over GetTagged handles parts that were there when the script started; the added signal handles everything after. The removed signal takes a part out of the table when it is destroyed or untagged, so the script never holds on to deleted parts. One Heartbeat connection moves all of them, which is cheaper than one loop per part.
Step 2: A reusable tag binder with clean up
Most tag scripts need the same shape: set something up when the tag appears, and undo it when it goes. This module does that once. You give it a tag and a setup function; the setup function returns a clean up function, which the module calls at the right time.
-- ModuleScript in ReplicatedStorage named TagBinder.
-- bind(tag, setup) -> stop: setup(instance) runs for every instance with the tag, now and later.
-- If setup returns a function, it runs when the tag is removed or the instance is destroyed.
local CollectionService = game:GetService("CollectionService")
local TagBinder = {}
type Cleanup = () -> ()
function TagBinder.bind(tag: string, setup: (instance: Instance) -> Cleanup?): () -> ()
local cleanups: { [Instance]: Cleanup } = {}
local function added(instance: Instance)
if cleanups[instance] then
return -- already set up
end
local ok, result = pcall(setup, instance)
if not ok then
warn("[TagBinder] " .. tag .. " setup failed for " .. instance:GetFullName() .. ": " .. tostring(result))
elseif typeof(result) == "function" then
cleanups[instance] = result
end
end
local function removed(instance: Instance)
local cleanup = cleanups[instance]
cleanups[instance] = nil
if cleanup then
cleanup()
end
end
local removedConnection = CollectionService:GetInstanceRemovedSignal(tag):Connect(removed)
for _, instance in CollectionService:GetTagged(tag) do
task.spawn(added, instance)
end
local addedConnection = CollectionService:GetInstanceAddedSignal(tag):Connect(added)
-- call the returned function to stop the binding and clean everything up
return function()
addedConnection:Disconnect()
removedConnection:Disconnect()
for instance in cleanups do
removed(instance)
end
end
end
return TagBinder
Each setup runs in its own thread with task.spawn, so a setup that waits (for example with WaitForChild) does not hold up the others, and pcall keeps one broken part from stopping the rest. bind returns a function: call it to stop the binding and clean up every part, for example when a round ends.
Step 3: Use it for kill bricks and glowing pickups
On the server, gameplay: every part tagged Lava kills on touch, and the connection is cleaned up when the part goes.
-- Script in ServerScriptService named LavaParts.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
type TagBinderModule = { bind: (string, (Instance) -> (() -> ())?) -> () -> () }
local TagBinder = require(ReplicatedStorage:WaitForChild("TagBinder")) :: TagBinderModule
TagBinder.bind("Lava", function(instance: Instance)
if not instance:IsA("BasePart") then
return nil
end
local connection = instance.Touched:Connect(function(hit: BasePart)
local humanoid = hit.Parent and hit.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.Health = 0
end
end)
return function()
connection:Disconnect()
end
end)
On the client, effects: every part tagged Pickup gets a glow that only exists on each player's screen, so the server does not have to send it.
-- LocalScript in StarterPlayerScripts named PickupGlow.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
type TagBinderModule = { bind: (string, (Instance) -> (() -> ())?) -> () -> () }
local TagBinder = require(ReplicatedStorage:WaitForChild("TagBinder")) :: TagBinderModule
TagBinder.bind("Pickup", function(instance: Instance)
if not instance:IsA("BasePart") then
return nil
end
local light = Instance.new("PointLight")
light.Color = Color3.fromRGB(255, 220, 120)
light.Range = 8
light.Parent = instance
return function()
light:Destroy()
end
end)
With StreamingEnabled on (a Workspace property, on by default in new places), parts far from a player are not on their device yet, and parts that stream out are removed. On the client, the added and removed signals fire as parts stream in and out, so the binder sets up and cleans up glows automatically. That is one more reason to use the signals rather than a single GetTagged loop.
Step 4: Settings per part with attributes
A tag says what a part is; an attribute says how it behaves. Give a Spinner a number attribute named Speed and read it in the setup function, falling back to a default when it is missing. The attributes guide covers attributes in full.
-- In a setup function: read a per-part setting, with a default.
local function speedOf(part: BasePart): number
local value = part:GetAttribute("Speed")
return if typeof(value) == "number" then value else 90
end
local part = Instance.new("Part")
part:SetAttribute("Speed", 180)
print(speedOf(part)) -- 180
| You want to | Use |
|---|---|
| Mark what kind of thing a part is | A tag, such as Lava or Door |
| Store a setting or a state on a part | An attribute, such as Speed or Open |
| Find one specific object | Its name and place in the Explorer |
Common errors and fixes
Open the Output window (Window menu, then Output) before you press Play.
GetTagged returns an empty list
Check the spelling and capitals of the tag in the Properties window. On the client, also remember that with streaming on, distant parts are not there yet; use the added signal.
Parts added during the game are ignored
The script only loops over GetTagged and never connects GetInstanceAddedSignal. Connect it too, as in Step 1.
Each part is set up twice
A part can be reported by both the first loop and the added signal if it arrives while the script starts. Keep a table of parts already set up and return early, as TagBinder does.
Things keep running after a part is destroyed
Connections and loops started in setup outlive the part unless you stop them. Return a clean up function from setup, or connect GetInstanceRemovedSignal.
Build it with RoCode
If you would rather not set this up by hand, RoCode can build it in the place you have open. RoCode is an independent AI agent for Roblox Studio, not made by Roblox: you describe the change in a web chat, and its Studio plugin creates the scripts and instances in your Explorer, so there is no code to paste.
My obby has a kill script copied into every lava part. Replace them with one Script in ServerScriptService: tag every part that has the old script as Lava, delete the copies, and make all Lava parts kill on touch.
- Can find every copy of the old script with Search Scripts before it changes anything
- Can add tags to the parts and delete the copied scripts, then write one Script with Create Script
- Is told to compile-check each script it writes with Check Script
- Sets an undo point before and after each batch of changes, so Ctrl+Z steps back one batch at a time
With plugin 3.9 or newer RoCode can run a short playtest and read the errors, but it checks only what it chose to test, so touch a few tagged parts yourself. How RoCode connects to Studio.
Questions
What is CollectionService in Roblox?
The service that keeps track of tags. It tells a script which instances have a tag and fires events when instances gain or lose it.
How do I add a tag in Roblox Studio?
Select the part, find Tags in the Properties window, click + and type the tag. From code, use part:AddTag("Name").
Do tags work on the client?
Yes. Tags added in Studio or by the server replicate to clients. A tag a LocalScript adds stays on that client.
Are tags better than putting a script in every part?
For anything with more than a few copies, yes: one script to fix, no duplicate connections, and parts added later are handled automatically.
Sources and further reading
- Roblox Creator Docs: CollectionService. GetTagged and the added and removed signals
- Roblox Creator Docs: Instance:AddTag
- Roblox Creator Docs: Properties window. adding tags in Studio
- Roblox Creator Docs: Instance streaming
How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 5 November 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.