In short
Put a LocalScript in ReplicatedFirst. It runs before anything else reaches the player. In it, create a ScreenGui with IgnoreGuiInset on, parent it to the player's PlayerGui, and call ReplicatedFirst:RemoveDefaultLoadingScreen(). Then wait for game.Loaded, preload what matters with ContentProvider:PreloadAsync(), and destroy the ScreenGui when you are done.
- Only a LocalScript in ReplicatedFirst runs early enough. In StarterGui or StarterPlayerScripts it starts after most of the game has loaded, so the screen appears too late.
- Build the screen in code, or keep your designed ScreenGui inside the script. Anything in StarterGui has not arrived yet when the script starts.
- Do not preload the whole game one object at a time. Preload in batches, or only the images and sounds the player sees first.
Get the script
Custom loading screen with a progress bar
One LocalScript in ReplicatedFirst replaces the default loading screen with your title, a progress bar, a Skip button and a fade out.
-- LocalScript in ReplicatedFirst named LoadingScreen.
-- Replaces Roblox's default loading screen with your own: a title, a progress bar,
-- a Skip button for slow connections, and a fade out when the game is ready.
local ContentProvider = game:GetService("ContentProvider")
local Players = game:GetService("Players")
local ReplicatedFirst = game:GetService("ReplicatedFirst")
local TweenService = game:GetService("TweenService")
local TITLE = "My Game"
local MIN_SECONDS = 2 -- show the screen at least this long, so it never just flashes
local SKIP_AFTER_SECONDS = 8 -- offer a Skip button when loading takes longer than this
local STEPS = 20 -- how many times the bar moves while assets preload
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui") :: PlayerGui
-- The screen is built in code, so it exists before anything else in the game has loaded
local gui = Instance.new("ScreenGui")
gui.Name = "LoadingScreen"
gui.IgnoreGuiInset = true -- also cover the strip at the top of the screen
gui.DisplayOrder = 100 -- in front of every other ScreenGui
gui.ResetOnSpawn = false
local background = Instance.new("Frame")
background.Size = UDim2.fromScale(1, 1)
background.BackgroundColor3 = Color3.fromRGB(20, 16, 32)
background.BorderSizePixel = 0
background.Parent = gui
local title = Instance.new("TextLabel")
title.Text = TITLE
title.Font = Enum.Font.GothamBold
title.TextSize = 42
title.TextColor3 = Color3.new(1, 1, 1)
title.BackgroundTransparency = 1
title.AnchorPoint = Vector2.new(0.5, 0.5)
title.Position = UDim2.fromScale(0.5, 0.42)
title.Size = UDim2.fromOffset(600, 60)
title.Parent = background
local barBack = Instance.new("Frame")
barBack.AnchorPoint = Vector2.new(0.5, 0.5)
barBack.Position = UDim2.fromScale(0.5, 0.54)
barBack.Size = UDim2.fromOffset(320, 10)
barBack.BackgroundColor3 = Color3.fromRGB(60, 52, 84)
barBack.BorderSizePixel = 0
barBack.Parent = background
local barFill = Instance.new("Frame")
barFill.Size = UDim2.fromScale(0, 1)
barFill.BackgroundColor3 = Color3.fromRGB(160, 120, 255)
barFill.BorderSizePixel = 0
barFill.Parent = barBack
local status = Instance.new("TextLabel")
status.Text = "Loading..."
status.Font = Enum.Font.Gotham
status.TextSize = 18
status.TextColor3 = Color3.fromRGB(200, 190, 230)
status.BackgroundTransparency = 1
status.AnchorPoint = Vector2.new(0.5, 0)
status.Position = UDim2.fromScale(0.5, 0.57)
status.Size = UDim2.fromOffset(320, 24)
status.Parent = background
local skip = Instance.new("TextButton")
skip.Text = "Skip"
skip.Font = Enum.Font.GothamBold
skip.TextSize = 18
skip.TextColor3 = Color3.new(1, 1, 1)
skip.BackgroundColor3 = Color3.fromRGB(60, 52, 84)
skip.AnchorPoint = Vector2.new(0.5, 0)
skip.Position = UDim2.fromScale(0.5, 0.64)
skip.Size = UDim2.fromOffset(120, 36)
skip.Visible = false
skip.Parent = background
gui.Parent = playerGui
ReplicatedFirst:RemoveDefaultLoadingScreen()
local started = os.clock()
local skipped = false
skip.Activated:Connect(function()
skipped = true
end)
task.delay(SKIP_AFTER_SECONDS, function()
skip.Visible = true
end)
-- Wait until the first copy of the game has arrived from the server
if not game:IsLoaded() then
game.Loaded:Wait()
end
-- Preload the images, meshes and sounds in Workspace in STEPS batches, moving the bar after each
local items = workspace:GetDescendants()
local perStep = math.max(1, math.ceil(#items / STEPS))
for first = 1, #items, perStep do
if skipped then
break
end
ContentProvider:PreloadAsync(table.move(items, first, math.min(first + perStep - 1, #items), 1, {}))
local done = math.min(first + perStep - 1, #items) / #items
barFill.Size = UDim2.fromScale(done, 1)
status.Text = string.format("Loading %d%%", math.floor(done * 100))
end
local shownFor = os.clock() - started
if shownFor < MIN_SECONDS and not skipped then
task.wait(MIN_SECONDS - shownFor)
end
-- Fade everything out, then remove the screen
local fade = TweenInfo.new(0.5)
for _, object in gui:GetDescendants() do
if object:IsA("TextLabel") or object:IsA("TextButton") then
TweenService:Create(object, fade, { TextTransparency = 1, BackgroundTransparency = 1 }):Play()
elseif object:IsA("Frame") then
TweenService:Create(object, fade, { BackgroundTransparency = 1 }):Play()
end
end
task.wait(0.5)
gui:Destroy()
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
ReplicatedFirst
LoadingScreen LocalScript new
Settings you can change
TITLEIn the script- The text in the middle of the screen. Put your game's name here. Default:
"My Game". MIN_SECONDSIn the script- The shortest time the screen stays up, so it never just flashes on a fast connection. Default:
2. SKIP_AFTER_SECONDSIn the script- When loading takes longer than this, a Skip button appears. Default:
8.
In a Studio playtest everything is already on your computer, so the screen shows for about MIN_SECONDS and the bar fills almost at once. To see a slow load, raise MIN_SECONDS while you test.
Type-checked with luau-lsp strict against Roblox's API definitions on 21 October 2026. Not playtested in Studio yet: press Play and watch the Output window before you publish.
Prompt for RoCode
Add a custom loading screen to my game: a LocalScript in ReplicatedFirst that removes the default loading screen, shows a full-screen ScreenGui with my game name and a progress bar while Workspace assets preload in batches, offers a Skip button after 8 seconds, stays up at least 2 seconds and fades out when the game is loaded.
On this page
How loading works in Roblox
When a player joins, Roblox shows its own loading screen while the server sends a copy of your game to their device. Your game's scripts mostly start once that copy has arrived, which is too late to show anything during the wait. ReplicatedFirst is the exception: Roblox sends its contents to the player before anything else, and a LocalScript inside it starts straight away.
| Piece | What it does |
|---|---|
ReplicatedFirst | Its LocalScripts reach the player first and run before the rest of the game has loaded. |
ReplicatedFirst:RemoveDefaultLoadingScreen() | Removes Roblox's default loading screen, so yours is the one players see. |
game:IsLoaded() and game.Loaded | Tell you when the first copy of the game has arrived. |
ContentProvider:PreloadAsync() | Waits until the images, meshes and sounds of the objects you pass have downloaded, so the game does not appear with grey textures. |
A ScreenGui with IgnoreGuiInset | The screen itself, covering the whole window including the strip at the top. |
A loading screen does not make the game load faster. It hides the loading and gives players something to look at, which matters most on phones and slow connections, where the default screen can sit for a while.
Step 1: Add the loading screen script
- In the Explorer, hover over ReplicatedFirst, click + and insert a LocalScript.
- Rename it
LoadingScreenand paste the code below. - Change
TITLEto your game's name, then press Play.
-- LocalScript in ReplicatedFirst named LoadingScreen.
-- Replaces Roblox's default loading screen with your own: a title, a progress bar,
-- a Skip button for slow connections, and a fade out when the game is ready.
local ContentProvider = game:GetService("ContentProvider")
local Players = game:GetService("Players")
local ReplicatedFirst = game:GetService("ReplicatedFirst")
local TweenService = game:GetService("TweenService")
local TITLE = "My Game"
local MIN_SECONDS = 2 -- show the screen at least this long, so it never just flashes
local SKIP_AFTER_SECONDS = 8 -- offer a Skip button when loading takes longer than this
local STEPS = 20 -- how many times the bar moves while assets preload
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui") :: PlayerGui
-- The screen is built in code, so it exists before anything else in the game has loaded
local gui = Instance.new("ScreenGui")
gui.Name = "LoadingScreen"
gui.IgnoreGuiInset = true -- also cover the strip at the top of the screen
gui.DisplayOrder = 100 -- in front of every other ScreenGui
gui.ResetOnSpawn = false
local background = Instance.new("Frame")
background.Size = UDim2.fromScale(1, 1)
background.BackgroundColor3 = Color3.fromRGB(20, 16, 32)
background.BorderSizePixel = 0
background.Parent = gui
local title = Instance.new("TextLabel")
title.Text = TITLE
title.Font = Enum.Font.GothamBold
title.TextSize = 42
title.TextColor3 = Color3.new(1, 1, 1)
title.BackgroundTransparency = 1
title.AnchorPoint = Vector2.new(0.5, 0.5)
title.Position = UDim2.fromScale(0.5, 0.42)
title.Size = UDim2.fromOffset(600, 60)
title.Parent = background
local barBack = Instance.new("Frame")
barBack.AnchorPoint = Vector2.new(0.5, 0.5)
barBack.Position = UDim2.fromScale(0.5, 0.54)
barBack.Size = UDim2.fromOffset(320, 10)
barBack.BackgroundColor3 = Color3.fromRGB(60, 52, 84)
barBack.BorderSizePixel = 0
barBack.Parent = background
local barFill = Instance.new("Frame")
barFill.Size = UDim2.fromScale(0, 1)
barFill.BackgroundColor3 = Color3.fromRGB(160, 120, 255)
barFill.BorderSizePixel = 0
barFill.Parent = barBack
local status = Instance.new("TextLabel")
status.Text = "Loading..."
status.Font = Enum.Font.Gotham
status.TextSize = 18
status.TextColor3 = Color3.fromRGB(200, 190, 230)
status.BackgroundTransparency = 1
status.AnchorPoint = Vector2.new(0.5, 0)
status.Position = UDim2.fromScale(0.5, 0.57)
status.Size = UDim2.fromOffset(320, 24)
status.Parent = background
local skip = Instance.new("TextButton")
skip.Text = "Skip"
skip.Font = Enum.Font.GothamBold
skip.TextSize = 18
skip.TextColor3 = Color3.new(1, 1, 1)
skip.BackgroundColor3 = Color3.fromRGB(60, 52, 84)
skip.AnchorPoint = Vector2.new(0.5, 0)
skip.Position = UDim2.fromScale(0.5, 0.64)
skip.Size = UDim2.fromOffset(120, 36)
skip.Visible = false
skip.Parent = background
gui.Parent = playerGui
ReplicatedFirst:RemoveDefaultLoadingScreen()
local started = os.clock()
local skipped = false
skip.Activated:Connect(function()
skipped = true
end)
task.delay(SKIP_AFTER_SECONDS, function()
skip.Visible = true
end)
-- Wait until the first copy of the game has arrived from the server
if not game:IsLoaded() then
game.Loaded:Wait()
end
-- Preload the images, meshes and sounds in Workspace in STEPS batches, moving the bar after each
local items = workspace:GetDescendants()
local perStep = math.max(1, math.ceil(#items / STEPS))
for first = 1, #items, perStep do
if skipped then
break
end
ContentProvider:PreloadAsync(table.move(items, first, math.min(first + perStep - 1, #items), 1, {}))
local done = math.min(first + perStep - 1, #items) / #items
barFill.Size = UDim2.fromScale(done, 1)
status.Text = string.format("Loading %d%%", math.floor(done * 100))
end
local shownFor = os.clock() - started
if shownFor < MIN_SECONDS and not skipped then
task.wait(MIN_SECONDS - shownFor)
end
-- Fade everything out, then remove the screen
local fade = TweenInfo.new(0.5)
for _, object in gui:GetDescendants() do
if object:IsA("TextLabel") or object:IsA("TextButton") then
TweenService:Create(object, fade, { TextTransparency = 1, BackgroundTransparency = 1 }):Play()
elseif object:IsA("Frame") then
TweenService:Create(object, fade, { BackgroundTransparency = 1 }):Play()
end
end
task.wait(0.5)
gui:Destroy()
PreloadAsync waits for every asset in the objects it is given. Passing the whole Workspace in one call would leave the bar stuck at 0% until the end, so the script splits it into STEPS batches and moves the bar after each one.
The screen goes into PlayerGui directly because StarterGui has not been copied to the player yet when this script runs. ResetOnSpawn is off so the screen survives the character spawning underneath it.
Step 2: Use a screen you designed in Studio
Building a screen in code is quick, but you may want to design it in Studio with images and your own layout. Design it as a ScreenGui in StarterGui, name it LoadingScreen, then drag it onto the LoadingScreen LocalScript in ReplicatedFirst, so it becomes the script's child and arrives with it. Use this shorter script instead of Step 1's:
-- LocalScript in ReplicatedFirst named LoadingScreen, with your own ScreenGui named
-- LoadingScreen inside it (design it in StarterGui, then drag it onto this script).
local Players = game:GetService("Players")
local ReplicatedFirst = game:GetService("ReplicatedFirst")
local MIN_SECONDS = 2
local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui") :: PlayerGui
local gui = (script:WaitForChild("LoadingScreen") :: ScreenGui):Clone()
gui.IgnoreGuiInset = true
gui.DisplayOrder = 100
gui.ResetOnSpawn = false
gui.Parent = playerGui
ReplicatedFirst:RemoveDefaultLoadingScreen()
local started = os.clock()
if not game:IsLoaded() then
game.Loaded:Wait()
end
local shownFor = os.clock() - started
if shownFor < MIN_SECONDS then
task.wait(MIN_SECONDS - shownFor)
end
gui:Destroy()
Step 3: Test it
- Open the Output window (Window menu, then Output) and press Play. Your screen should appear, the bar fill, and the screen fade after about
MIN_SECONDS. - Studio already has every asset on your computer, so the bar fills almost at once. Set
MIN_SECONDSto 8 and play again to check the Skip button appears and works. - Publish and join from the Roblox app on a phone to see the screen as players will.
Common errors and fixes
If the default loading screen still shows, or yours never goes away, check these first.
Roblox's default loading screen still shows
The script is not a LocalScript in ReplicatedFirst, or it errors before it reaches RemoveDefaultLoadingScreen(). Check the Output window for a red line naming LoadingScreen.
My screen appears late, after the game is visible
The script is in StarterGui or StarterPlayerScripts. Move it to ReplicatedFirst.
Infinite yield possible on 'ReplicatedFirst.LoadingScreen:WaitForChild("LoadingScreen")'
Step 2's script cannot find your ScreenGui. It must be a child of the script and named exactly LoadingScreen.
The screen never goes away
Something after game.Loaded errored or never finished. Check Output; the Skip button in Step 1 is also there so a player is never stuck.
A gap shows at the top of the screen
Set IgnoreGuiInset to true on the ScreenGui, as both scripts do.
Build it with RoCode
RoCode is an AI agent that works inside Roblox Studio through a plugin. You describe the screen you want, and it builds it in the place you have open.
Add a loading screen with my game's name, a purple progress bar and a Skip button, that fades into the game when it is loaded.
- Writes the LocalScript in ReplicatedFirst and builds the ScreenGui
- Matches the colours and font you ask for, and can reuse a ScreenGui you already designed
- Checks the scripts it wrote inside Studio before it finishes
You still press Play and check the screen looks right on a phone and a computer. How RoCode connects to Studio.
Questions
Where do I put a loading screen script in Roblox?
In ReplicatedFirst, as a LocalScript. It is the only place a script runs before the rest of the game has loaded.
How do I remove the default Roblox loading screen?
Call game:GetService("ReplicatedFirst"):RemoveDefaultLoadingScreen() from a LocalScript in ReplicatedFirst, once your own screen is showing.
Does a loading screen make my game load faster?
No. It covers the wait. To shorten it, use fewer and smaller images, meshes and sounds near the spawn.
Why does my loading screen only show for a moment in Studio?
In a Studio playtest everything is already on your computer. Raise MIN_SECONDS while testing, or test the published game on a phone.
Sources and further reading
- Roblox Creator Docs: ReplicatedFirst. what runs first and RemoveDefaultLoadingScreen
- Roblox Creator Docs: ContentProvider. PreloadAsync
- Roblox Creator Docs: DataModel. IsLoaded and the Loaded event
- Roblox Creator Docs: ScreenGui. IgnoreGuiInset, DisplayOrder and ResetOnSpawn
How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 21 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.