How to Make a Gamepass in Roblox (GamePass & Developer Products)
Last updated:
Quick answer: Create the pass in the Creator Dashboard, prompt the purchase with MarketplaceService:PromptGamePassPurchase from a LocalScript, and grant the perk only when the server confirms it with UserOwnsGamePassAsync — Developer Products are the consumable alternative and must be granted inside MarketplaceService.ProcessReceipt.
A Roblox gamepass script has three separate jobs: creating the pass in the Creator Dashboard, prompting the purchase from a client script with MarketplaceService:PromptGamePassPurchase, and checking or granting the perk on the server with UserOwnsGamePassAsync. Developer Products follow the same creation flow but are granted exclusively through MarketplaceService.ProcessReceipt, which is the only receipt pattern Roblox guarantees against double-charging.
What's the difference between a gamepass and a Developer Product?
Both are catalog items you attach to your experience and both are bought with Robux, but they behave completely differently:
| GamePass | Developer Product | |
|---|---|---|
| Purchases | One per account, forever | Unlimited, repeatable |
| Typical perk | VIP tag, 2x walk speed, private island | 100 coins, health potion, XP boost |
| Detection | UserOwnsGamePassAsync | ProcessReceipt callback |
| Survives rejoin | Yes, ownership is permanent | You persist the granted effect yourself |
A gamepass is a permanent unlock: once the account owns it, every future server can ask "does this player own it?" and get yes. A Developer Product is consumable — the player buys it again every time they want the effect, and it is your job to record what was granted (usually in a DataStore).
Rule of thumb: if the player should have it forever, it is a gamepass. If they should be able to buy it again tomorrow, it is a Developer Product.
How do I create a gamepass in the Creator Dashboard?
Open the Creator Dashboard, select your experience, then Monetization → Passes → Create a pass. Give it a name, icon and description, and set a price in Robux — a pass with no price stays Off Sale and cannot be prompted. Developer Products live under Monetization → Developer Products and additionally ask for a price per purchase.
After saving, copy the numeric ID of the item (it is in the page URL, /store/1234567890/...). That ID is what your scripts use — not the name. Keep them in one config ModuleScript so a price or product change is a one-line edit:
-- ModuleScript: ReplicatedStorage/GamepassConfig
return {
VIP_PASS_ID = 1234567890,
TWO_X_SPEED_PASS_ID = 1234567891,
COINS_100_PRODUCT_ID = 1234567892,
}
Config in ReplicatedStorage is readable by both the server and the client, so the LocalScript that prompts the purchase and the server script that validates it use the same IDs.
How do I prompt a gamepass purchase from a GUI button?
Purchase prompts must be triggered from the client — a server script calling PromptGamePassPurchase will silently do nothing useful. Put a LocalScript next to your TextButton (or in StarterPlayerScripts looking the button up) and prompt on click:
-- LocalScript inside a TextButton
local MarketplaceService = game:GetService("MarketplaceService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local config = require(ReplicatedStorage:WaitForChild("GamepassConfig"))
local button = script.Parent
button.Activated:Connect(function()
local player = game:GetService("Players").LocalPlayer
MarketplaceService:PromptGamePassPurchase(player, config.VIP_PASS_ID)
end)
This opens Roblox's own purchase dialog. Never build your own Robux transfer flow and never trust a client message that says "I bought it" — the dialog is just the request; the confirmation is what the server verifies.
How do I know when the purchase finished?
MarketplaceService.PromptGamePassPurchaseFinished fires when the dialog closes, on both the client and the server, with the player, the pass ID, and a wasPurchased boolean. Handle it on the server, because that is where the perk lives:
-- Server Script in ServerScriptService
local MarketplaceService = game:GetService("MarketplaceService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local config = require(ReplicatedStorage:WaitForChild("GamepassConfig"))
local ownedPasses = {} -- [player] = { [passId] = true }
local function grantPass(player, passId)
ownedPasses[player] = ownedPasses[player] or {}
ownedPasses[player][passId] = true
if passId == config.TWO_X_SPEED_PASS_ID then
local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.WalkSpeed = 32
end
end
end
MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player, passId, wasPurchased)
if wasPurchased then
grantPass(player, passId)
end
end)
Handling the finished prompt means the buyer gets the perk immediately, without leaving and rejoining. The ownership check in the next section is still what protects you on every later join.
How do I check gamepass ownership on the server?
MarketplaceService:UserOwnsGamePassAsync(userId, passId) is the authoritative answer, and it is a web request, so wrap it in pcall. Run it when the player joins and cache the result — checking ownership on every game tick spams Roblox's API for nothing:
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local config = require(ReplicatedStorage:WaitForChild("GamepassConfig"))
local ownedPasses = {} -- [player] = { [passId] = true }
local function checkOwnership(player)
ownedPasses[player] = {}
for _, passId in { config.VIP_PASS_ID, config.TWO_X_SPEED_PASS_ID } do
local ok, owns = pcall(function()
return MarketplaceService:UserOwnsGamePassAsync(player.UserId, passId)
end)
if ok and owns then
ownedPasses[player][passId] = true
elseif not ok then
warn("Ownership check failed for " .. player.Name .. ": " .. tostring(owns))
end
end
end
Players.PlayerAdded:Connect(function(player)
checkOwnership(player)
-- apply the cached perks to the character here
end)
Any expensive perk — damage multipliers, area access, sell prices — must read from this server-side cache, never from a client claim. If a RemoteEvent message could grant a perk, an exploiter can fire that message directly.
Where do I handle Developer Product receipts?
Developer products have no "does the player own it" question — every purchase is an event, and the delivery point is MarketplaceService.ProcessReceipt. This callback is the only safe receipt pattern in Roblox: connect it exactly once, from exactly one server Script, and return a decision:
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local config = require(ReplicatedStorage:WaitForChild("GamepassConfig"))
local productActions = {
[config.COINS_100_PRODUCT_ID] = function(player)
-- add 100 coins to the player's server-side balance
end,
}
MarketplaceService.ProcessReceipt = function(receiptInfo)
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
-- the buyer left the server: ask Roblox to retry later
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local action = productActions[receiptInfo.ProductId]
if not action then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
action(player)
return Enum.ProductPurchaseDecision.PurchaseGranted
end
Two rules make this safe. First, assign the property (ProcessReceipt = function ...) rather than connecting a signal — Roblox runs exactly one callback, and any others are silently ignored. Second, make the grant idempotent: Roblox retries the callback until you return PurchaseGranted, so record each processed receiptInfo.PurchaseId in a DataStore and grant at most once per ID. Granting coins inside the callback and then crashing before the return is exactly how players get refunded items for free.
Gamepasses do not go through ProcessReceipt. That callback is for Developer Products only; passes are covered by PromptGamePassPurchaseFinished plus the UserOwnsGamePassAsync check.
Common mistakes
- Prompting from the server.
PromptGamePassPurchasemust run in a LocalScript; the server's job is only to verify the outcome. - Granting perks from a client message. "I own the pass" arriving over a RemoteEvent is an exploiter's favourite packet. Ask
UserOwnsGamePassAsyncinstead. - Connecting
ProcessReceiptfrom several scripts. Only the last assignment wins and earlier products silently never grant. One script, one callback, a product table inside it. - Forgetting the return value. A callback that returns nothing leaves the purchase in limbo and Roblox keeps retrying it.
- Granting the same product twice. The retry behaviour means your handler can run again for one purchase — store
PurchaseIdvalues and skip the ones you already processed. - Skipping
pcallaroundUserOwnsGamePassAsync. It is a web request like any other; one hiccup without protection kills the join script. This is the same failure mode the leaderstats tutorial avoids when it sets up player state.
Learn Luau by doing
Monetization code is small but unforgiving, and the failure modes only make sense once you have wired the prompt, the finished handler and the ownership cache yourself. On Luablox you can practise Luau interactively through /theory, sharpen the tricky parts with drills on /problems, and follow structured Luablox lessons that build from basics to full gameplay systems.
Frequently asked questions
How do I check if a player owns a gamepass?
Call MarketplaceService:UserOwnsGamePassAsync(player.UserId, passId) on the server, wrapped in pcall because it is a web request. Run it when the player joins and cache the result in a table so gameplay checks never hit the API repeatedly.
Gamepass or Developer Product — which one do I need?
A gamepass is a one-time, permanent unlock (VIP status, 2x speed, an extra island) detected with UserOwnsGamePassAsync. A Developer Product is consumable and repeatable (coins, potions, boosts) granted through ProcessReceipt. If the player should own it forever, use a gamepass.
Where do I handle ProcessReceipt?
In exactly one server Script, assigned as MarketplaceService.ProcessReceipt = function(receiptInfo) ... end. Look up the player with Players:GetPlayerByUserId, grant the product, remember the PurchaseId to prevent double-granting, and return Enum.ProductPurchaseDecision.PurchaseGranted (or NotProcessedYet to make Roblox retry).
Why doesn't PromptGamePassPurchaseFinished fire in my server script?
Usually the prompt was never opened: PromptGamePassPurchase must be called from a LocalScript with a valid pass ID that has a price set in the Creator Dashboard. A pass left Off Sale, or an ID copied from the wrong item page, opens no dialog at all.
Do gamepass purchases work while testing in Roblox Studio?
You can prompt purchases in Studio and the dialog opens, but test accounts complete them only in live games; in Studio, UserOwnsGamePassAsync returns false for test players. Verify ownership logic in a published test place or temporarily bypass the check behind a Studio-only debug flag.
Learn core Roblox scripting and Luau programming concepts, including: roblox gamepass script, roblox developer products, PromptGamePassPurchase, UserOwnsGamePassAsync, ProcessReceipt roblox.