Tutorial · Rewards, pets and trading

How to make and award a badge in Roblox

Badges are the achievements that show in a player's Roblox inventory: "Welcome", "Beat the obby", "Found the secret room". They cost nothing for players and give them a reason to come back and finish things. This guide creates a badge, gets its ID, and awards it from the server three ways: on joining, on touching a part and on reaching a goal, with the checks Roblox recommends so a badge call can never break your game.

Updated 5 November 20266 min readBeginnerLuauWritten by

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 older AwardBadge is 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
-- 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

Explorer: the module and a script that uses 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
  1. Get the script
  2. 1. Create a badge
  3. 2. Award function
  4. 3. Award it
  5. 4. Badge perks
  6. 5. Test it
  7. Common errors
  8. With RoCode
  9. Questions
  10. Sources

Step 1: Create the badge and copy its ID

Your place must be published first: badges belong to an experience. Then:

  1. Open the Creator Dashboard on create.roblox.com, hover over your experience, click the three dots and choose Create Badge.
  2. Upload an image. Use a 512 by 512 pixel picture; Roblox trims it to a circle, so keep anything important away from the corners.
  3. Give it a name and a description that says how to earn it, leave Badge is Enabled on, and click Create Badge.
  4. 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
-- 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
-- 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
-- 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
-- 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
-- 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

  1. Publish the place and make sure the badge is enabled in Creator Hub.
  2. 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.
  3. 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.
  4. 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.

You type

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.

RoCode does
  • 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

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.

Try RoCode on your own place.

Free every day, no card required. Install the plugin, describe the feature, and review what lands in Studio.