
Why Learn Luau Scripting
You already know how to place parts and build things in Roblox Studio. That gets you a static world – walls, floors, decorations. But the moment you want a door that opens, a leaderboard that tracks points, or enemies that chase players, you need a Roblox Luau scripting tutorial to get started. Scripts in Roblox are written in Luau, and this beginner guide walks you through everything step by step.
Luau is Roblox’s programming language. It is based on Lua 5.1 but Roblox added type checking, performance optimizations, and a ton of built-in APIs that talk directly to the game engine. If you have already gone through our Roblox Studio beginner tutorial, this is your next step. We are going from building to programming.
The best part? Luau was literally designed for people who have never coded before. The syntax is clean, the error messages are readable, and you can see the results of your code in real time by pressing F5. No compiling, no terminal, no complicated setup. Just write code and watch your game react.
Let’s get into it.
Setting Up Your First Script in Roblox Studio
Before writing any code, you need to understand where scripts live in Roblox Studio. This matters more than you think – putting a script in the wrong location is the number one reason beginners get confused when their code does nothing.
Script Types and Where They Go
| Script Type | Location | Purpose |
|---|---|---|
| Script | ServerScriptService | Game logic that runs on the server (damage, scoring, data saves) |
| LocalScript | StarterPlayerScripts or StarterGui | Player-side code (camera, UI, input) |
| ModuleScript | ReplicatedStorage or ServerStorage | Reusable code that other scripts can require |
For this tutorial, we are starting with regular Scripts in ServerScriptService. These run on the server and affect all players equally.
Creating Your First Script
- Open Roblox Studio and load any project (a Baseplate works fine)
- In the Explorer panel, find ServerScriptService
- Right-click it and select Insert Object > Script
- The script editor opens with a single line:
print("Hello world!") - Press F5 to playtest
- Check the Output window (View > Output if you do not see it)
You should see “Hello world!” printed in the Output. That one line just ran on the Roblox server. Everything from here is building on this exact workflow: write code, press F5, check Output.
If you do not see the Output window, go to View in the top menu and click Output. You will use this window constantly, so keep it open.
Variables and Data Types in Luau
Variables store information that your scripts use. Think of them as labeled boxes – you put a value inside and reference the label whenever you need that value.
Declaring Variables
In Luau, you create variables with the local keyword:
local playerName = "RobloxDrop"
local maxHealth = 100
local isGameOver = false
local speedMultiplier = 1.5
Always use local. Technically Luau lets you skip it, but that creates global variables which cause bugs in larger projects and run slower. Make local a habit from day one.
Data Types You Will Use
| Type | Example | Use Case |
|---|---|---|
| string | "Hello" | Player names, messages, UI text |
| number | 42, 3.14 | Health, speed, scores, positions |
| boolean | true, false | Toggles, conditions, game state |
| nil | nil | Nothing, empty, not set yet |
| table | {1, 2, 3} | Lists, inventories, leaderboard data |
String Operations
Strings are text values and you will use them everywhere:
local firstName = "Roblox"
local lastName = "Drop"
-- Concatenation (joining strings)
local fullName = firstName.. lastName -- "RobloxDrop"
local spaced = firstName.. " ".. lastName -- "Roblox Drop"
-- String length
local nameLength = #fullName -- 10
-- Useful string functions
local upper = string.upper(fullName) -- "ROBLOXDROP"
local lower = string.lower(fullName) -- "robloxdrop"
local sub = string.sub(fullName, 1, 6) -- "Roblox"
Tables (Arrays and Dictionaries)
Tables are Luau’s multi-purpose data structure. They work as both ordered lists and key-value pairs:
-- Array (ordered list)
local fruits = {"Apple", "Banana", "Cherry"}
print(fruits[1]) -- "Apple" (Luau arrays start at 1, not 0)
-- Dictionary (key-value pairs)
local playerStats = {
health = 100,
speed = 16,
level = 1
}
print(playerStats.health) -- 100
-- Adding to a table
table.insert(fruits, "Mango")
playerStats.coins = 50
Tables will become essential when you build inventories, save player data, or store game configuration.
Functions: Reusable Blocks of Logic
Functions let you package code into reusable blocks. Instead of writing the same logic over and over, you define it once and call it whenever you need it.
Defining and Calling Functions
local function healPlayer(humanoid, amount)
local newHealth = humanoid.Health + amount
if newHealth > humanoid.MaxHealth then
newHealth = humanoid.MaxHealth
end
humanoid.Health = newHealth
print("Healed to ".. newHealth)
end
That function takes a Humanoid object and a heal amount, caps the health at the maximum, and applies the healing. You call it like this:
-- Somewhere else in your code
healPlayer(someHumanoid, 25)
Functions That Return Values
Functions can also calculate something and send the result back:
local function calculateDamage(baseDamage, level)
local multiplier = 1 + (level * 0.1)
return baseDamage * multiplier
end
local damage = calculateDamage(50, 10)
print(damage) -- 100
Anonymous Functions
You will see functions without names all the time in Roblox scripting, especially with events:
part.Touched:Connect(function(hit)
print(hit.Name.. " touched the part")
end)
That function(hit) ... end block is an anonymous function – it does not have a name, it just runs when the event fires. This pattern is everywhere in Luau.
Events and Connections: Making Things Happen
Events are the heartbeat of any Roblox game. They fire when something happens – a player touches a part, a character spawns, a value changes. Your scripts listen for events and respond to them.
How Events Work
Every Roblox object has events you can connect to. The pattern is always the same:
object.EventName:Connect(function(parameters)
-- Your code runs here when the event fires
end)
Common Events You Will Use
| Event | Fires When | Found On |
|---|---|---|
Touched | Something physically touches the part | BasePart |
TouchEnded | Something stops touching the part | BasePart |
PlayerAdded | A new player joins the server | Players service |
PlayerRemoving | A player leaves the server | Players service |
CharacterAdded | A player’s character spawns or respawns | Player object |
Changed | A property value changes | Any instance |
ChildAdded | A new child is added to an object | Any instance |
Connecting to PlayerAdded
One of the most important events. This fires every time a player joins your game:
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
print(player.Name.. " joined the game!")
end)
The game:GetService("Players") line is how you access Roblox services. Services are top-level objects that manage different systems – Players handles player connections, Lighting handles the environment, DataStoreService handles saving data, and so on.
Disconnecting Events
Sometimes you need an event to fire only once or stop listening:
local connection
connection = part.Touched:Connect(function(hit)
print("Touched once!")
connection:Disconnect -- Stop listening after first touch
end)
This is useful for one-time triggers like picking up a collectible or opening a chest.
Making a Working Door Script
Let’s build something practical. A door that opens when a player touches it and closes after a few seconds. This combines variables, functions, events, and timing – everything we have covered so far.
Step 1: Set Up the Door
- Create a Part in your workspace
- Resize it to look like a door (try Size 1, 7, 5)
- Color it brown and set Material to Wood
- Anchor it (this is critical)
- Name it “Door” in the Properties panel
Step 2: Write the Script
Right-click the Door part in Explorer, insert a Script, and replace the default code:
local door = script.Parent
local debounce = false
local openTime = 3
local closedPosition = door.Position
local openOffset = Vector3.new(0, door.Size.Y, 0)
local function openDoor
if debounce then return end
debounce = true
-- Move door up (open)
door.Position = closedPosition + openOffset
door.Transparency = 0.5
door.CanCollide = false
print("Door opened")
task.wait(openTime)
-- Move door back down (close)
door.Position = closedPosition
door.Transparency = 0
door.CanCollide = true
print("Door closed")
debounce = false
end
door.Touched:Connect(function(hit)
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if humanoid then
openDoor
end
end)
How It Works
- debounce prevents the door from being triggered repeatedly while it is already open. This is a pattern you will use in almost every interactive script.
- task.wait(3) pauses the script for 3 seconds before closing the door. The
tasklibrary is the modern way to handle timing in Luau. - We check for a Humanoid in the touching object to make sure only players (not random parts) trigger the door.
- The door moves up by its own height, becomes semi-transparent, and disables collision so players can walk through.
Press F5 and walk into the door. It should slide up, stay open for 3 seconds, and come back down. If you are building obby stages, RPG dungeons, or tycoon gates, this door pattern is your foundation.
Building a Points Leaderboard
Leaderboards let players see their stats on the right side of the screen. Roblox has a built-in leaderboard system tied to a folder called “leaderstats.” Once you set it up, the leaderboard shows automatically – no UI scripting needed.
The Leaderboard Script
Create a new Script in ServerScriptService and add this code:
local Players = game:GetService("Players")
local function onPlayerAdded(player)
-- Create the leaderstats folder
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
-- Create a Points value
local points = Instance.new("IntValue")
points.Name = "Points"
points.Value = 0
points.Parent = leaderstats
-- Create a Level value
local level = Instance.new("IntValue")
level.Name = "Level"
level.Value = 1
level.Parent = leaderstats
print(player.Name.. " leaderstats created")
end
Players.PlayerAdded:Connect(onPlayerAdded)
Press F5 and you will see a leaderboard appear in the top-right corner with your username, 0 Points, and Level 1.
Adding a Points Pickup
Now let’s create parts that give points when a player touches them. Add this to a new Script inside ServerScriptService:
local function setupCoin(coin)
coin.Touched:Connect(function(hit)
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if not humanoid then return end
local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent)
if not player then return end
local points = player:FindFirstChild("leaderstats")
and player.leaderstats:FindFirstChild("Points")
if not points then return end
points.Value = points.Value + 10
coin:Destroy
print(player.Name.. " collected a coin! Total: " .. points.Value)
end)
end
-- Set up all parts in a folder called "Coins"
local coinsFolder = workspace:FindFirstChild("Coins")
if coinsFolder then
for _, coin in coinsFolder:GetChildren do
setupCoin(coin)
end
end
To make this work, create a Folder in Workspace named “Coins” and add some small yellow Parts inside it. Each one becomes a collectible that adds 10 points to the leaderboard. This is the same concept behind coin collection in tycoons, obbies, and adventure games.
If you want to learn how to turn simple game mechanics like this into a full published experience, our guide on how to make a popular Roblox game covers the design and launch process.
Debugging Tips That Will Save You Hours
Every programmer spends time debugging. The difference between a frustrated beginner and a productive scripter is knowing how to find and fix errors quickly.
Reading Error Messages
When a script breaks, Roblox tells you exactly what went wrong in the Output window. A typical error looks like this:
ServerScriptService.Script:15: attempt to index nil with 'Health'
This tells you:
- ServerScriptService.Script – Which script has the error
- :15 – The line number (line 15)
- attempt to index nil with ‘Health’ – You tried to access
.Healthon something that does not exist (nil)
Using Print for Debugging
When you are not sure what your code is doing, add print statements to trace the flow:
local function onTouched(hit)
print("1. Something touched the part:", hit.Name)
local humanoid = hit.Parent:FindFirstChild("Humanoid")
print("2. Found humanoid:", humanoid)
if humanoid then
print("3. Player health:", humanoid.Health)
humanoid.Health = humanoid.Health - 25
print("4. New health:", humanoid.Health)
end
end
Run the game and check Output. If you see step 1 print but not step 2, you know the problem is between those lines. This “print debugging” technique works at every skill level.
Common Error Types
| Error | What It Means | Fix |
|---|---|---|
attempt to index nil | You are accessing a property on something that does not exist | Check if the object exists before using it with FindFirstChild |
Expected 'end' | You forgot to close a function, if block, or loop | Count your function/if/for openings and make sure each has an end |
Expected identifier | Typo in a keyword or variable name | Check spelling carefully, Luau is case-sensitive |
Infinite yield possible | WaitForChild is waiting for something that may never appear | Make sure the object you are waiting for actually exists or gets created |
The Output Window Is Your Best Friend
Keep the Output window open at all times. Go to View > Output if it is not visible. Every error, every print statement, every warning shows up here. Ignoring it is like driving with your eyes closed.
You can also filter messages – right-click in the Output window to show only errors, warnings, or messages. When your game gets complex and the Output is noisy, filtering helps you focus.
Common Beginner Mistakes in Luau
After helping hundreds of new scripters, these are the mistakes that come up again and again. Knowing them ahead of time will save you real frustration.
1. Forgetting the local Keyword
-- Bad: creates a global variable
playerScore = 100
-- Good: creates a local variable
local playerScore = 100
Global variables leak between scripts and cause unpredictable bugs. Always use local.
2. Putting Scripts in the Wrong Service
A Script in StarterPlayerScripts will not work. A LocalScript in ServerScriptService will not work. Match the script type to the correct location every time.
3. Not Checking for Nil
-- This crashes if there is no Humanoid
hit.Parent.Humanoid.Health = 0
-- This is safe
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if humanoid then
humanoid.Health = 0
end
Always use FindFirstChild before accessing child objects. Not everything that touches a part is a player character.
4. Forgetting That Touched Fires for Every Body Part
When a player walks into a part, the Touched event fires for the head, torso, and each limb. Without a debounce, your code runs 5+ times:
local debounce = false
part.Touched:Connect(function(hit)
if debounce then return end
debounce = true
-- Your code here (runs once)
task.wait(1)
debounce = false
end)
5. Using wait Instead of task.wait
The old wait function is deprecated. Always use task.wait instead:
-- Old (deprecated, less accurate)
wait(2)
-- New (recommended, more reliable)
task.wait(2)
The task library also gives you task.spawn, task.delay, and task.defer for more control over timing.
6. Infinite Loops Without Yields
If you write a while loop that never pauses, it freezes the entire game:
-- This freezes the game
while true do
-- doing stuff forever with no pause
end
-- This works fine
while true do
-- doing stuff
task.wait(1) -- Pause 1 second each loop
end
Every loop needs a task.wait or some other yield point. No exceptions.
If you run into performance issues while testing your scripts, check out our Roblox lag fix and performance guide for optimization techniques.
Luau Type Checking (2026 Feature)
One of the biggest advantages Luau has over standard Lua is its type system. Type annotations help catch bugs before you even run your game.
Basic Type Annotations
local playerName: string = "RobloxDrop"
local health: number = 100
local isAlive: boolean = true
local function calculateDamage(base: number, multiplier: number): number
return base * multiplier
end
You do not have to use types – they are optional. But adding them to function parameters and return values helps Studio catch mistakes in the editor before you press F5. As your scripts get longer, type checking becomes incredibly valuable.
Enabling Strict Mode
Add this comment at the top of your script to enable strict type checking:
--!strict
local function greet(name: string): string
return "Hello, " .. name
end
greet(42) -- Studio warns you: number is not a string
Strict mode catches type mismatches, missing return values, and other issues that would otherwise only show up at runtime. It is not required for beginners, but it is worth learning early.
Next Steps After This Tutorial
You now have the fundamentals of Luau scripting – variables, functions, events, practical examples, and debugging skills. Here is where to go next to keep leveling up your game development:
Intermediate Topics:
- RemoteEvents and RemoteFunctions – Communication between server and client scripts. Required for any multiplayer feature like trading, chat systems, or team mechanics.
- DataStoreService – Saving player data (coins, levels, inventory) so it persists between sessions. Without this, players lose everything when they leave.
- ModuleScripts – Organize your code into reusable modules instead of dumping everything into one massive script.
- TweenService – Smooth animations for moving parts, fading UI, and creating polished visual effects without frame-by-frame scripting.
Advanced Topics:
- Metatables and OOP – Object-oriented patterns in Luau for building complex systems like weapon classes or enemy AI.
- Raycasting – Detecting what a player is looking at or shooting at. Essential for FPS games and interaction systems.
- CollectionService – Tagging objects and managing groups of similar items efficiently.
The official Roblox Creator Documentation covers every API in detail and includes interactive examples. The DevForum at devforum.roblox.com is where experienced developers share tutorials and answer questions.
For inspiration on what you can build, browse our best Roblox games roundup and reverse-engineer what makes popular games work. Check out Roblox’s 4D generation experiences to see where the platform is heading and what kinds of games are getting attention from Roblox themselves.
Explore all of our development and strategy content in our guides hub – from monetization to UGC creation, there is a guide for every step of your Roblox creator journey.
You have the tools. You have the knowledge. Now go build something and make it yours.
FAQ
Is Luau the same as Lua?
Luau is based on Lua 5.1 but includes Roblox-specific additions like strict typing, type annotations, and performance improvements. Most standard Lua code works in Luau, but Luau has extra features that standard Lua does not.
Do I need coding experience before learning Luau?
No. Luau is designed to be beginner-friendly and works as a first programming language. If you can follow step-by-step instructions and are willing to experiment, you can learn Luau.
Where do I put my scripts in Roblox Studio?
Server scripts go in ServerScriptService. LocalScripts go in StarterPlayerScripts or StarterGui. ModuleScripts go in ReplicatedStorage or ServerStorage depending on who needs to access them.
What is the difference between Script and LocalScript?
A Script runs on the server and controls shared game logic. A LocalScript runs on a single player’s device and handles things like camera movement, UI, and input detection.
How do I debug Luau scripts?
Open the Output window from the View menu. It shows print statements and error messages with line numbers. Add print calls throughout your code to trace execution flow and identify where things break.
Can I build a full multiplayer game with Luau?
Yes. Roblox games are multiplayer by default. Server-side Scripts manage shared state while LocalScripts handle each player’s experience. RemoteEvents connect server and client.
How long does it take to get good at Luau?
Basic scripts like doors and kill bricks take a day to learn. Events and game loops take about a week. Data saving, remote events, and polished game systems take 2 to 4 weeks of consistent practice.
What are the most common beginner mistakes?
Forgetting the local keyword, putting scripts in the wrong service, not checking for nil before accessing properties, and skipping the Output window when errors happen.
Is Luau fast enough for complex games?
Yes. Luau was specifically optimized by Roblox for game performance. It handles physics, AI, large player counts, and complex systems without issues when you follow good coding practices.
What resources should I use to keep learning?
Start with the official Roblox Creator Docs at create.roblox.com/docs. Join the DevForum for community help. Follow YouTube creators who specialize in Roblox development tutorials.
