In short
Create the badge in Creator Hub (your experience, Engagement, Badges, or the experience's menu, Create Badge) and copy its ID with Copy Asset ID. In a Script in ServerScriptService, call BadgeService:AwardBadgeAsync(player.UserId, BADGE_ID) inside pcall, after checking with BadgeService:UserHasBadgeAsync that they do not have it yet. Award only from the server: AwardBadgeAsync does not work in a LocalScript.
- You can create 5 badges free per experience in each 24 hours (GMT); more cost 100 Robux each.
- Badge calls go to Roblox's servers and can fail, so wrap them in
pcall. - Use
AwardBadgeAsync. The olderAwardBadgeis deprecated.
Get the script
Badge award script with safe checks
A server ModuleScript with Badges.award(player, id): checks the badge is enabled and not owned yet, awards it inside pcall and never awards twice in one session.
-- ModuleScript in ServerScriptService named Badges.
-- Badges.award(player, badgeId): gives the badge once, safely. Server only.
local BadgeService = game:GetService("BadgeService")
local Players = game:GetService("Players")
local Badges = {}
local enabledCache: { [number]: boolean } = {}
local awardedThisSession: { [Player]: { [number]: boolean } } = {}
local function isEnabled(badgeId: number): boolean
local cached = enabledCache[badgeId]
if cached ~= nil then
return cached
end
local ok, info = pcall(function()
return BadgeService:GetBadgeInfoAsync(badgeId)
end)
if not ok then
warn("[Badges] could not read badge", badgeId, info)
return false
end
enabledCache[badgeId] = info.IsEnabled == true
return enabledCache[badgeId]
end
function Badges.award(player: Player, badgeId: number)
local given = awardedThisSession[player]
if not given then
given = {}
awardedThisSession[player] = given
end
if given[badgeId] or not isEnabled(badgeId) then
return
end
given[badgeId] = true -- do not try again this session, even if a call below fails
local okHas, hasBadge = pcall(function()
return BadgeService:UserHasBadgeAsync(player.UserId, badgeId)
end)
if okHas and hasBadge then
return -- already earned in an earlier visit
end
local okAward, result = pcall(function()
return BadgeService:AwardBadgeAsync(player.UserId, badgeId)
end)
if not okAward then
warn("[Badges] could not award", badgeId, "to", player.Name, result)
given[badgeId] = nil -- allow a later retry
end
end
Players.PlayerRemoving:Connect(function(player: Player)
awardedThisSession[player] = nil
end)
return Badges
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
ServerScriptService
Badges ModuleScript new
WelcomeBadge Script new
Before you test
Replace the 0 IDs with your own badge IDs from Creator Hub (Copy Asset ID).
What a cheater can and cannot doBadges are awarded only from the server, so a player cannot give themselves one. Decide when they have earned it on the server too.
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 badges to my game: a server ModuleScript with award(player, badgeId) that checks the badge is enabled and not owned, awards it in pcall and caches the result, then award my Welcome badge on join and my Summit badge when a player touches the part named Summit.
On this page
Step 1: Create the badge and copy its ID
Your place must be published first: badges belong to an experience. Then:
- Open the Creator Dashboard on create.roblox.com, hover over your experience, click the three dots and choose Create Badge.
- Upload an image. Use a 512 by 512 pixel picture; Roblox trims it to a circle, so keep anything important away from the corners.
- Give it a name and a description that says how to earn it, leave Badge is Enabled on, and click Create Badge.
- In your experience's Engagement section, open Badges, hover over the new badge, click the three dots and choose Copy Asset ID. That number is the badge ID your script needs.
You can create up to 5 badges free in each 24-hour period (GMT) for each experience; each extra badge in that period costs 100 Robux. Disabled badges cannot be earned and are not shown on the experience page.
Step 2: A safe award function
Every badge in your game should go through one function that does the checks once. Insert a ModuleScript in ServerScriptService named Badges:
-- ModuleScript in ServerScriptService named Badges.
-- Badges.award(player, badgeId): gives the badge once, safely. Server only.
local BadgeService = game:GetService("BadgeService")
local Players = game:GetService("Players")
local Badges = {}
local enabledCache: { [number]: boolean } = {}
local awardedThisSession: { [Player]: { [number]: boolean } } = {}
local function isEnabled(badgeId: number): boolean
local cached = enabledCache[badgeId]
if cached ~= nil then
return cached
end
local ok, info = pcall(function()
return BadgeService:GetBadgeInfoAsync(badgeId)
end)
if not ok then
warn("[Badges] could not read badge", badgeId, info)
return false
end
enabledCache[badgeId] = info.IsEnabled == true
return enabledCache[badgeId]
end
function Badges.award(player: Player, badgeId: number)
local given = awardedThisSession[player]
if not given then
given = {}
awardedThisSession[player] = given
end
if given[badgeId] or not isEnabled(badgeId) then
return
end
given[badgeId] = true -- do not try again this session, even if a call below fails
local okHas, hasBadge = pcall(function()
return BadgeService:UserHasBadgeAsync(player.UserId, badgeId)
end)
if okHas and hasBadge then
return -- already earned in an earlier visit
end
local okAward, result = pcall(function()
return BadgeService:AwardBadgeAsync(player.UserId, badgeId)
end)
if not okAward then
warn("[Badges] could not award", badgeId, "to", player.Name, result)
given[badgeId] = nil -- allow a later retry
end
end
Players.PlayerRemoving:Connect(function(player: Player)
awardedThisSession[player] = nil
end)
return Badges
This follows the pattern on Roblox's badges page: read the badge info, check IsEnabled, then award inside pcall. It adds two things a real game needs: it asks once per player per session rather than on every touch, and it skips the award call for players who already own the badge, which keeps you well away from BadgeService's request limits on a busy server.
Step 3: Award a badge on join, on touch and for a goal
A welcome badge when a player joins
-- Script in ServerScriptService named WelcomeBadge.
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
type BadgesModule = { award: (Player, number) -> () }
local Badges = require(ServerScriptService:WaitForChild("Badges")) :: BadgesModule
local WELCOME_BADGE_ID = 0 -- paste your badge ID
Players.PlayerAdded:Connect(function(player: Player)
Badges.award(player, WELCOME_BADGE_ID)
end)
A badge for touching a part
-- Script in ServerScriptService named SummitBadge.
-- Awards a badge to players who touch the part named Summit, such as the end of an obby.
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
type BadgesModule = { award: (Player, number) -> () }
local Badges = require(ServerScriptService:WaitForChild("Badges")) :: BadgesModule
local SUMMIT_BADGE_ID = 0 -- paste your badge ID
local summit = workspace:WaitForChild("Summit") :: BasePart
summit.Touched:Connect(function(hit: BasePart)
local character = hit.Parent
local player = if character and character:IsA("Model") then Players:GetPlayerFromCharacter(character) else nil
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if player and humanoid and humanoid.Health > 0 then
Badges.award(player, SUMMIT_BADGE_ID)
end
end)
Touched fires many times per step, but Badges.award only does work the first time per player, so no extra debounce is needed here. In an obby, a cheater can still teleport to the summit; if that matters, also check on the server that they reached the checkpoints before it, as the checkpoint guide tracks.
A badge for reaching a goal
-- Script in ServerScriptService named CoinBadge.
-- Awards a badge when a player's Coins reach 1,000.
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
type BadgesModule = { award: (Player, number) -> () }
local Badges = require(ServerScriptService:WaitForChild("Badges")) :: BadgesModule
local RICH_BADGE_ID = 0 -- paste your badge ID
local GOAL = 1000
Players.PlayerAdded:Connect(function(player: Player)
local leaderstats = player:WaitForChild("leaderstats", 30)
local coins = leaderstats and leaderstats:WaitForChild("Coins", 30)
if not (coins and coins:IsA("IntValue")) then
return
end
local function check()
if coins.Value >= GOAL then
Badges.award(player, RICH_BADGE_ID)
end
end
coins.Changed:Connect(check)
check()
end)
This reads the coins the server keeps in leaderstats, which players cannot change, so the badge is only given for coins really earned. The leaderboard guide creates that Coins value.
Step 4: Give a perk to badge owners
UserHasBadgeAsync also works for perks: a door only veterans can open, a skin for players who beat the game. Check it once when the player joins and store the answer as an attribute the rest of your game can read.
-- Script in ServerScriptService named BadgePerks.
local BadgeService = game:GetService("BadgeService")
local Players = game:GetService("Players")
local SUMMIT_BADGE_ID = 0 -- paste your badge ID
Players.PlayerAdded:Connect(function(player: Player)
local ok, hasBadge = pcall(function()
return BadgeService:UserHasBadgeAsync(player.UserId, SUMMIT_BADGE_ID)
end)
player:SetAttribute("BeatTheObby", ok and hasBadge == true)
end)
If the badge is awarded during this visit, set the attribute to true at the same time, since this check only ran on join. The attributes guide shows how other scripts and LocalScripts react to it.
Step 5: Test it
- Publish the place and make sure the badge is enabled in Creator Hub.
- Press Play in Studio and do what earns the badge. Watch Output: the Badges module warns when a call fails, and prints nothing when it works.
- Check your Roblox inventory's Badges section. A badge your account already owns is skipped, so to test the award again, delete the badge from your inventory on the Roblox website first.
- Finally, join the live game from the Roblox app and earn it there once, which is how players will see it, with the badge notification.
Common errors and fixes
Open the Output window (Window menu, then Output) before you press Play.
Nothing happens and there is no error
The badge ID is still 0, the badge is disabled, or you already own it (the module skips owners). Print the ID in the script, check Creator Hub, and look in your Roblox inventory.
AwardBadgeAsync fails in a LocalScript
Badges can only be awarded by the server. Move the award to a Script in ServerScriptService; a LocalScript can ask with a RemoteEvent, and the server decides whether it was earned.
An Unable to cast error
You passed the Player or a string instead of the number. Use player.UserId and a number badge ID without quotes.
The badge belongs to another experience
An experience can only award its own badges. Create the badge for this experience.
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 in your Explorer, so there is no code to paste. You still create the badges themselves in Creator Hub.
Add a Badges module in ServerScriptService that awards a badge once, safely, with pcall and an enabled check. Award badge 0000 on join and badge 1111 when a player touches the part named Summit, server side only.
- Can find your Summit part and leaderstats with Search Game before it writes anything
- Writes the ModuleScript and Scripts with Create Script and is told to compile-check each one with Check Script
- Sets an undo point before and after each batch of changes, so Ctrl+Z steps back one batch at a time
Paste your real badge IDs into the prompt. RoCode cannot create badges in Creator Hub for you. How RoCode connects to Studio.
Questions
How do you make a badge in Roblox?
In the Creator Dashboard, open your experience's menu and choose Create Badge, upload a 512 by 512 image, name it and create it. Then award it from a server Script with BadgeService:AwardBadgeAsync.
Are badges free to make?
Up to 5 per experience in each 24 hours (GMT). Each extra badge in that period costs 100 Robux.
How do I find my badge ID?
In your experience's Badges page in Creator Hub, hover over the badge, click the three dots and choose Copy Asset ID.
Can I award a badge from a LocalScript?
No. Badges are awarded by the server only. Use a server Script, and decide on the server whether the player earned it.
How do I check if a player has a badge?
Call BadgeService:UserHasBadgeAsync(player.UserId, badgeId) on the server inside pcall. It returns true or false.
Sources and further reading
- Roblox Creator Docs: Badges. creating badges, the free limit, badge IDs and the safe award function
- Roblox Creator Docs: BadgeService. AwardBadgeAsync, UserHasBadgeAsync and GetBadgeInfoAsync
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.