Appearance
DataModel and Instances
Written: 2026-10-14
Learning objectives
- You can describe the DataModel tree.
- You can read Instance properties and call its methods.
- You can find children safely with FindFirstChild and WaitForChild.
- You can parent and destroy objects without leaks.
- You can choose Folders and Models with clear names.
- You can store and read attributes on instances.
Prerequisites
The DataModel tree
Every place is one tree. The DataModel sits at the root. You access it with game in your code.
Container services form the top branches. Workspace holds everything you see in the 3D world. Other branches hold lighting, replicated objects, server objects, and client objects.
Every object in the tree inherits from Instance. That base class gives you Name, Parent, and children. Each class then adds its own behavior on top.
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local target = Workspace:FindFirstChild("Baseplate")
if target ~= nil then
print(target.Name)
end
local children = Workspace:GetChildren()
for index, child in children do
print(index, child.Name)
endGet Workspace once. Look for one child by name. Guard the result with if. List direct children with GetChildren. Expected behavior: the Output window prints Baseplate when it exists, then prints each top-level child name with its index.
Properties, methods, and events
A property stores a value. Read it and assign it. A method does work. Call it with arguments. An event signals a change. Connect a function to it.
Name is a non-unique label. You use it to organize the tree. Parent sets the position in the tree. A child is any object parented to another object. Descendants include children of children.
Dot access like Workspace.Baseplate throws when the child is missing. FindFirstChild returns nil instead, so you can guard it. That guard is the pattern you use everywhere.
Finding children safely
Replication has no guaranteed order. Streaming can delay children further. The services guide tells you to treat loading order as unknown.
Use WaitForChild for objects that replicate or arrive late. It yields your thread until the child exists. FindFirstChild with a nil guard suits objects that may or may not exist.
LocalScript in StarterPlayerScripts
luau
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local shared = ReplicatedStorage:WaitForChild("GunConfig")
print(shared.Name)Create a ModuleScript named GunConfig in ReplicatedStorage before you play. Get the replicated container once. Wait for the shared object. Print its name. Expected behavior: the client prints GunConfig once replication delivers it.
Parenting and destroy lifecycle
Parent controls existence. An object with no parent stays in memory only while your variable holds it. An object parented under game lives in the tree.
Set Parent last when you build objects in code. Each parented change can replicate separately. Setting it last sends one update. The Instance reference recommends exactly this order.
Destroy ends an object. It sets Parent to nil, locks Parent, disconnects connections, and destroys children. You cannot reuse a destroyed object. Set your variable to nil after you destroy it.
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local folder = Instance.new("Folder")
folder.Name = "Coins"
local template = Instance.new("Part")
template.Name = "Coin"
template.Size = Vector3.new(1, 1, 1)
template.Anchored = true
template.Parent = folder
folder.Parent = WorkspaceCreate the folder first and set its name. Create the part, set size and anchored state, and parent it to the folder. Parent the folder to Workspace last. Expected behavior: a folder named Coins appears in Workspace with one anchored part named Coin inside it.
Folders versus Models and naming
Use a Folder for organization. It adds no behavior and groups coins, spawn points, or script holders.
Use a Model for a physical set. It groups parts that belong together, like a chair, a table, and a lamp. You can nest models inside models for larger sets.
Give every object a descriptive name. Names keep the tree searchable. Unique sibling names let dot access find the right child. The data model guide asks you to name objects descriptively for this reason.
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local rack = Instance.new("Model")
rack.Name = "CoinRack"
for count = 1, 3 do
local coin = Instance.new("Part")
coin.Name = "Coin" .. tostring(count)
coin.Size = Vector3.new(1, 1, 1)
coin.Anchored = true
coin.Position = Vector3.new(count * 4, 5, 0)
coin.Parent = rack
end
rack.Parent = WorkspaceBuild a small system. Create one Model. Loop three times. Each pass creates a part, names it Coin1 through Coin3, sizes it, anchors it, positions it, and parents it to the model. Parent the model last. Expected behavior: a model named CoinRack appears in Workspace with three spaced coins inside it.
Store data on instances with attributes
Properties are fixed by the class. Attributes are custom key-value pairs you attach to any instance. You use them for tuning numbers, flags, and labels that travel with the object. They accept strings, booleans, numbers, and common value types like Vector3, UDim2, Color3, and BrickColor. The reference lists the exact supported set.
Attributes replicate from the server to every client, so the client can read them but never treat them as proof. Never store secrets in attributes. Store display and tuning data, and re-validate anything that matters on the server. Setting an attribute to nil removes it.
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local gate = Instance.new("Part")
gate.Name = "Gate"
gate.Size = Vector3.new(4, 6, 1)
gate.Anchored = true
gate.Position = Vector3.new(0, 3, 20)
gate:SetAttribute("Cost", 25)
gate:SetAttribute("Open", false)
gate.Parent = Workspace
print(gate:GetAttribute("Cost"))
gate:SetAttribute("Open", true)
print(gate:GetAttribute("Open"))txt
25
trueAttach two attributes before parenting. Read Cost back as 25. Flip Open to true and read true. Expected behavior: Output prints 25 then true, and both values ride along with the part to every client. Prefer attributes over a pile of Value objects when the data belongs to the instance itself.
Common mistakes
Mistake 1: dot access on a missing child.
Wrong example below. It indexes a child that may not exist.
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local plate = Workspace.Baseplate
print(plate.Name)It fails because dot access throws when Baseplate is missing. Replication and renames both cause this. Fix it with FindFirstChild and a nil guard.
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local plate = Workspace:FindFirstChild("Baseplate")
if plate then
print(plate.Name)
endMistake 2: parenting before setting properties.
Wrong example below. It parents first, then changes properties.
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local part = Instance.new("Part")
part.Parent = Workspace
part.Size = Vector3.new(4, 4, 4)
part.Anchored = trueIt works but it replicates extra updates. Each change after parenting can send separately. Fix it by assigning Parent last.
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local part = Instance.new("Part")
part.Size = Vector3.new(4, 4, 4)
part.Anchored = true
part.Parent = WorkspaceMistake 3: reusing a destroyed object.
Wrong example below. It destroys and then reparents.
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local old = Workspace:FindFirstChild("OldCoin")
if old then
old:Destroy()
old.Parent = Workspace
endIt fails because Destroy locks Parent. The final assignment throws. Fix it by clearing the variable and creating a fresh object when you need one again.
Exercises
- List the direct children of Workspace and print each name.
- Build a folder named
Gemswith two anchored parts, with Parent assigned last. - Build a model named
GemShelfwith three parts at different positions, then destroy one part cleanly.
Solution 1
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local children = Workspace:GetChildren()
for index, child in children do
print(index, child.Name)
endExpected behavior: Output lists each Workspace child once.
Solution 2
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local folder = Instance.new("Folder")
folder.Name = "Gems"
for count = 1, 2 do
local gem = Instance.new("Part")
gem.Name = "Gem" .. tostring(count)
gem.Size = Vector3.new(1, 1, 1)
gem.Anchored = true
gem.Position = Vector3.new(count * 3, 5, 0)
gem.Parent = folder
end
folder.Parent = WorkspaceExpected behavior: a Gems folder appears in Workspace with Gem1 and Gem2 inside it.
Solution 3
Script in ServerScriptService
luau
--!strict
local Workspace = game:GetService("Workspace")
local shelf = Instance.new("Model")
shelf.Name = "GemShelf"
for count = 1, 3 do
local gem = Instance.new("Part")
gem.Name = "Gem" .. tostring(count)
gem.Size = Vector3.new(1, 1, 1)
gem.Anchored = true
gem.Position = Vector3.new(count * 3, 6, 0)
gem.Parent = shelf
end
shelf.Parent = Workspace
local extra = shelf:FindFirstChild("Gem3")
if extra then
extra:Destroy()
extra = nil
endExpected behavior: GemShelf appears with two gems left, because Gem3 is destroyed and cleared.
Queries without hard references
CollectionService groups instances by string tags instead of stored paths: AddTag/RemoveTag, GetTagged for the current set, HasTag/GetTags for checks, and GetInstanceAddedSignal/GetInstanceRemovedSignal for live tracking. Combine GetTagged (present) with the added signal (future) so no instance is missed, and always disconnect per-instance connections on the removed signal to avoid leaks. Tags replicate server-to-client as one set, a server add/remove overwrites client-side tags, and streamed-out instances re-sync tags from the server on return, so never treat client tags as authority.
luau
--!strict
local CollectionService = game:GetService("CollectionService")
local connections: { [Instance]: RBXScriptConnection } = {}
local function watch(coin: Instance): ()
if coin:IsA("BasePart") then
connections[coin] = coin.Touched:Connect(function(_hit: BasePart): ()
print(coin.Name)
end)
end
end
CollectionService:GetInstanceAddedSignal("Coin"):Connect(watch)
CollectionService:GetInstanceRemovedSignal("Coin"):Connect(function(coin: Instance): ()
local connection = connections[coin]
if connection then
connection:Disconnect()
connections[coin] = nil
end
end)
for _, coin in CollectionService:GetTagged("Coin") do
watch(coin)
endSummary
The tree is the game. Read it with guards. Build it parent-last. Destroy it cleanly. Organize it with folders and models. Query it with CollectionService tags without hard paths. Next, learn where code runs in Scripts and Execution.