In short
Create the pass in Creator Hub (Creations, your game, Monetization, Passes, Create pass) and copy its ID. Then put a Script in ServerScriptService that calls MarketplaceService:UserOwnsGamePassAsync when each player joins and listens to MarketplaceService.PromptGamePassPurchaseFinished, so it gives the perk to owners and to anyone who buys during the session. A LocalScript only opens Roblox's purchase window with PromptGamePassPurchase.
- Wrap
UserOwnsGamePassAsyncinpcall: it asks Roblox's servers and can fail. Remember the answer for each player instead of asking again. - Grant perks on the server only. Roblox's API reference says
PromptGamePassPurchaseFinishedshould only be listened to in a server script, wherewasPurchasedis the final outcome. - A pass is bought once and kept. Anything a player can buy again, such as a bag of coins, is a developer product, handled with
ProcessReceipt.
On this page
How game passes work in 2026
A pass charges a player a one-time Robux fee for a privilege inside one game: Roblox's own examples are entry to a restricted area, an in-game avatar item and a permanent power-up. Roblox records who owns which pass. It does not apply the perk: your scripts look up ownership and give the player whatever the pass promises.
Roblox's documentation now calls game passes simply passes. The API still uses the old name, so you will see UserOwnsGamePassAsync, PromptGamePassPurchase and Enum.InfoType.GamePass next to "pass" in Creator Hub. Three things in Roblox's current pass documentation are newer than many tutorials:
- Cross-game pass sales are disabled from 30 May 2026. Prompt passes that belong to the game the player is in. Roblox points games that relied on selling other games' passes to Robux transfers instead.
- Managed pricing is on by default for passes. Roblox can show players in some regions a lower price than the default you set. Its regional pricing page says a regional price is never more than 70% below your default price and never above it.
- Players can buy passes away from your button. Roblox's automatic in-game Shop lists your passes, and Roblox can show passes outside your game, for example on the Roblox homepage and in search results. A player can arrive already owning the pass, which is why the join check matters.
| Job | Where it runs | Why there |
|---|---|---|
| Check who already owns the pass | Server Script, when the player joins | The server's answer is the one your game can trust |
| Open the purchase window | LocalScript, when the button is pressed | Roblox draws the window on the player's screen and takes the payment |
| Give the perk after a purchase | Server Script, on PromptGamePassPurchaseFinished | On the server, wasPurchased is the real result |
| Show "Owned" on the button | LocalScript, reading an attribute the server sets | Display only: changing it on the client unlocks nothing |
Step 1: Create the pass in Creator Hub
A pass belongs to one game, and the game must be published first. If you have only ever saved your place to your computer, publish it from Studio (File, Publish to Roblox) before you start.
- Go to Creations in Creator Hub (create.roblox.com) and select your game.
- Go to Monetization, then Passes.
- Click Create pass.
- Upload an image for the pass icon:
.jpg,.pngor.bmp, no larger than 512 by 512 pixels, with nothing important outside the circle Roblox crops it to. - Enter a name and a description.
- Choose a category. Roblox uses it to group items in your game's in-game Shop.
- Click Create pass.
Before anyone can buy the pass, it has to be on sale with a price in Robux. Open the pass from the Passes list and check its Sales page. Roblox's Passes page does not walk through this screen (its regional pricing page is where the Sales page is named), so the exact switch and field names may look slightly different on your screen.
Step 2: Get the pass ID (not an asset ID)
- In Creator Hub, go to Monetization, Passes.
- Hover over the pass's thumbnail, click the ⋯ button and choose Copy Asset ID.
The menu item says "Asset ID", but Roblox's Passes page uses exactly this step to get the pass ID, which is the number every script on this page needs. Two other numbers look similar and do not work:
- A legacy asset ID. Passes made long ago also have an asset ID. Roblox's
GamePassServicereference says new passes only have a pass ID, and that asset IDs belong to the legacy functions (GamePassService:PlayerHasPassandMarketplaceService:PromptPurchase). - The ProductId from GetProductInfoAsync. The
PromptGamePassPurchasereference says the pass ID is theTargetIdthatGetProductInfoAsyncreturns for a pass, not itsProductId.
Your game ID, place ID and developer product IDs are different numbers again. Paste the pass ID into your script as a number, without quotes.
Step 3: Keep every pass ID in one ModuleScript
The server needs the IDs to check ownership and the buy button needs them to open the purchase window. Keeping them in one ModuleScript in ReplicatedStorage means a new pass is one line in one place. The rest of this guide sells three example passes: VIP (walk through a VIP door), Speed (walk faster) and GravityCoil (a Tool).
ReplicatedStorage
PassConfig ModuleScript new
ServerScriptService
GamePassHandler Script new
ServerStorage
PassItems Folder new
GravityCoil Tool new
Handle Part
StarterPlayer
StarterPlayerScripts
PassButton LocalScript new
Workspace
VIPDoor Part new
Hover over ReplicatedStorage in the Explorer, click +, insert a ModuleScript, rename it PassConfig and replace its contents with this:
-- Every pass the game sells, by name. The server and LocalScripts both read this.
local PassConfig = {}
-- Paste each pass ID from Creator Hub in place of 0.
-- A pass left at 0 is skipped, so you can set them up one at a time.
local ids: { [string]: number } = {
VIP = 0, -- walk through the VIP door
Speed = 0, -- walk faster
GravityCoil = 0, -- a Tool kept in ServerStorage.PassItems
}
PassConfig.ids = ids
-- The Player attribute the server sets to true once a player owns the pass
function PassConfig.attributeFor(passName: string): string
return "OwnsPass_" .. passName
end
return PassConfig
Pass IDs do not need hiding. The buy button needs the ID to open the purchase window anyway, and owning a pass is decided by Roblox, not by anything a player can read or change in ReplicatedStorage.
Step 4: Check ownership and give perks on the server
This Script does all the deciding. When a player joins it asks Roblox whether they own each pass, and when a player buys one during the session it grants it straight away, with no rejoin. It keeps the answers in a table for each player, so other code never has to ask Roblox twice.
First build the two objects the perks use:
- Add a Part to Workspace, name it
VIPDoor, size it to fill a doorway, and tick Anchored and CanCollide. A Transparency of about 0.5 lets players see it is a door. - Hover over ServerStorage, insert a Folder named
PassItems, and put a Tool namedGravityCoilinside it. For a first test the Tool only needs one unanchored Part namedHandle. Any Tool you already have works too, renamed to match.
Then hover over ServerScriptService, insert a Script named GamePassHandler and paste this in:
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local ServerStorage = game:GetService("ServerStorage")
-- What the PassConfig module returns, so strict type checking can check how it is used
type PassConfigModule = {
ids: { [string]: number },
attributeFor: (passName: string) -> string,
}
local PassConfig = require(ReplicatedStorage:WaitForChild("PassConfig")) :: PassConfigModule
local passItems = ServerStorage:WaitForChild("PassItems")
-- Roblox's docs say Studio does not mock pass purchasing. Set this to true to try every
-- perk while testing in Studio. RunService:IsStudio() is false on live servers, so this
-- never gives perks away in the published game.
local GRANT_ALL_IN_STUDIO = false
local SPEED_PASS_WALK_SPEED = 24 -- the default WalkSpeed is 16
local VIP_PLAYERS_GROUP = "VIPPlayers"
local VIP_DOOR_GROUP = "VIPDoor"
local CHECK_ATTEMPTS = 3
-- passesOwned[player] holds the passes that player owns, e.g. passesOwned[player].VIP == true
local passesOwned: { [Player]: { [string]: boolean } } = {}
-- VIP door: parts in the VIPDoor group block everyone except characters in VIPPlayers
for _, groupName in { VIP_PLAYERS_GROUP, VIP_DOOR_GROUP } do
if not workspace:IsCollisionGroupRegistered(groupName) then
workspace:RegisterCollisionGroup(groupName)
end
end
workspace:CollisionGroupSetCollidable(VIP_PLAYERS_GROUP, VIP_DOOR_GROUP, false)
local vipDoor = workspace:FindFirstChild("VIPDoor")
if vipDoor and vipDoor:IsA("BasePart") then
vipDoor.CollisionGroup = VIP_DOOR_GROUP
else
warn("No Part named VIPDoor in Workspace, so the VIP pass opens no door")
end
local function giveTool(player: Player, toolName: string)
local template = passItems:FindFirstChild(toolName)
if not (template and template:IsA("Tool")) then
warn(`ServerStorage.PassItems has no Tool named {toolName}`)
return
end
-- StarterGear: Roblox copies it into the Backpack every time the character spawns.
-- It can appear a moment after the player joins, so wait for it briefly.
local starterGear = player:WaitForChild("StarterGear", 10)
if starterGear and starterGear:IsA("StarterGear") then
template:Clone().Parent = starterGear
else
warn(`No StarterGear for {player.Name}, so {toolName} will not come back after a respawn`)
end
-- Backpack: if the character is already in the world, hand over a copy now too
local backpack = player:FindFirstChildOfClass("Backpack")
if backpack and player.Character then
template:Clone().Parent = backpack
end
end
-- Perks that live on the character. A respawn creates a new character without them,
-- so this runs for the current character and again for every new one.
local function applyCharacterPerks(player: Player, character: Model)
local owned = passesOwned[player]
if not owned then
return
end
if owned.VIP then
for _, descendant in character:GetDescendants() do
if descendant:IsA("BasePart") then
descendant.CollisionGroup = VIP_PLAYERS_GROUP
end
end
end
if owned.Speed then
local humanoid = character:WaitForChild("Humanoid", 10)
if humanoid and humanoid:IsA("Humanoid") then
humanoid.WalkSpeed = SPEED_PASS_WALK_SPEED
end
end
end
local function onCharacterAdded(player: Player, character: Model)
-- Accessories and other parts can arrive after the character spawns
character.DescendantAdded:Connect(function(descendant: Instance)
local owned = passesOwned[player]
if owned and owned.VIP and descendant:IsA("BasePart") then
descendant.CollisionGroup = VIP_PLAYERS_GROUP
end
end)
applyCharacterPerks(player, character)
end
-- Gives a player everything one pass includes. Safe to call more than once.
local function grantPass(player: Player, passName: string)
local owned = passesOwned[player]
if not owned or owned[passName] then
return -- the player has left, or already has this pass
end
owned[passName] = true
-- Attributes set by the server replicate, so the player's LocalScripts can show "Owned"
player:SetAttribute(PassConfig.attributeFor(passName), true)
if passName == "GravityCoil" then
giveTool(player, "GravityCoil")
end
local character = player.Character
if character then
applyCharacterPerks(player, character)
end
end
-- Asks Roblox whether the player owns one pass, retrying if the request fails
local function checkPass(player: Player, passName: string, passId: number)
for attempt = 1, CHECK_ATTEMPTS do
local ok, result = pcall(function()
return MarketplaceService:UserOwnsGamePassAsync(player.UserId, passId)
end)
if ok then
if result == true then
grantPass(player, passName)
end
return
end
warn(`Could not check the {passName} pass for {player.Name} (try {attempt}): {result}`)
if attempt < CHECK_ATTEMPTS then
task.wait(attempt * 2)
if not passesOwned[player] then
return -- the player left while we waited
end
end
end
end
local function onPlayerAdded(player: Player)
passesOwned[player] = {}
player.CharacterAdded:Connect(function(character: Model)
onCharacterAdded(player, character)
end)
local character = player.Character
if character then
onCharacterAdded(player, character)
end
for passName, passId in PassConfig.ids do
if GRANT_ALL_IN_STUDIO and RunService:IsStudio() then
task.spawn(grantPass, player, passName)
elseif passId ~= 0 then
-- One thread per pass: Roblox can batch checks started together into fewer requests
task.spawn(checkPass, player, passName, passId)
end
end
end
-- A player bought a pass in this server. Listen on the server only: here
-- wasPurchased is the final result, and a LocalScript's copy is not to be relied on.
MarketplaceService.PromptGamePassPurchaseFinished:Connect(
function(player: Player, passId: number, wasPurchased: boolean)
if not wasPurchased then
return
end
for passName, id in PassConfig.ids do
if id == passId then
grantPass(player, passName)
end
end
end
)
Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in Players:GetPlayers() do
task.spawn(onPlayerAdded, player)
end
Players.PlayerRemoving:Connect(function(player: Player)
passesOwned[player] = nil
end)
Roblox's API reference says UserOwnsGamePassAsync caches its results, updates the cache when PromptGamePassPurchaseFinished fires, and always returns true the first time a player enters a server after buying the pass. The passesOwned table is still worth keeping: it is the server's own answer that other scripts and the perk code read without waiting on Roblox.
Ownership is checked in two places on purpose. The join check covers everyone who bought the pass before, including in Roblox's in-game Shop or outside your game. PromptGamePassPurchaseFinished covers a purchase made from your button during this session, so the perk arrives as soon as the purchase window closes. Roblox's Shop page says purchases made through its in-game Shop use your existing purchase handling. grantPass ignores a second grant, so both firing for one player does no harm.
Other server scripts can read the same answer from the attribute. For example, a coin pad that pays VIPs double can check player:GetAttribute("OwnsPass_VIP") == true before paying. Only read it on the server for anything that matters: a player can change attributes on their own copy, but the change never reaches the server.
Perk examples: VIP door, extra speed and a tool
The three example passes show the three kinds of perk most games sell. The difference that matters is whether the perk lives on the character, and so has to be applied again after every respawn, or on the player.
| Pass | What owners get | How the script gives it | Why it survives a respawn |
|---|---|---|---|
| VIP | Walk through the VIPDoor part | Puts every part of the character in the VIPPlayers collision group, which does not collide with the VIPDoor group | applyCharacterPerks runs for every new character, and DescendantAdded catches accessories that load late |
| Speed | WalkSpeed 24 instead of the default 16 | Sets Humanoid.WalkSpeed | Each respawn creates a new Humanoid at 16, so the same function sets it again |
| GravityCoil | A Tool in the hotbar | Clones the Tool from ServerStorage.PassItems into the player's StarterGear, and into the Backpack if the character exists | Roblox copies StarterGear into the new Backpack every time the character spawns |
Collision groups are set up the way Roblox's collisions page shows for characters: register the groups once on the server, turn off collisions between the two, then assign each part's CollisionGroup. A part can belong to one group only, so if another script already puts characters in its own group (to stop players colliding with each other, for example), merge the two into one script.
To add your own perk, add the pass to PassConfig.ids, then add an if owned.YourPass then branch to applyCharacterPerks for character perks, or a line in grantPass for things given once, like a tool or a title.
Step 6: Test it in Studio and in the live game
Roblox's main Passes page says nothing about testing in Studio. The one direct statement is in its Season Passes feature package guide, which says to test pass purchases by publishing the game and testing in the Roblox client, because Studio does not mock pass purchasing. So test in two halves.
In Studio: the perks
- Set
GRANT_ALL_IN_STUDIOtotruein GamePassHandler and press Play. Every pass in PassConfig is granted, even ones still at ID 0. - Walk through the VIP door, check you run faster, and look for the GravityCoil in your hotbar.
- Reset your character from the in-game menu. All three perks should come back on the new character.
- Switch the Explorer to the server view with the Client/Server toggle, select your player under
Players, and check theOwnsPass_attributes in the Properties window. - Set
GRANT_ALL_IN_STUDIOback tofalse. On a live server it does nothing anyway, but leaving it off means Studio shows the real join check.
In the published game: the purchase
- Paste your real pass IDs into PassConfig, make sure each pass is on sale, and publish.
- Join the live game from the Roblox app with an account that does not own the pass. The button should show the price. If the button says "VIP owned" before you buy anything, the join check found that this account already owns the pass, so test the purchase with another account.
- Buy the pass. The perk should arrive without rejoining, and the button should switch to "VIP owned".
- Leave and rejoin. The join check should grant the perk again straight away.
We found no Roblox documentation of a free test purchase for passes, so treat a purchase in the live game as a real one. If anything misbehaves on a live server, press F9 in the Roblox app to open the Developer Console, where server warnings from GamePassHandler appear.
Game passes vs developer products
Roblox sells two kinds of in-game Robux purchase. Choosing the wrong one leaves players with a perk they lose or a purchase they cannot repeat. The test is simple: can a player sensibly buy it a second time?
| Pass | Developer product | |
|---|---|---|
| Bought | Once, kept for good | As many times as the player likes |
| Good for | VIP, a permanent speed boost, a tool, a restricted area | Coins, potions, revives, temporary boosts |
| Who records it | Roblox records ownership; UserOwnsGamePassAsync asks for it | You: save what the purchase gave, such as the new coin total |
| Grant it with | UserOwnsGamePassAsync on join and PromptGamePassPurchaseFinished on the server | The ProcessReceipt callback on the server |
| Prompt it with | PromptGamePassPurchase | PromptProductPurchase |
| Created in | Monetization, Passes | Monetization, Developer Products |
Developer products need a different pattern. Roblox's API reference says not to use PromptProductPurchaseFinished to process purchases, because the prompt closing does not mean the purchase succeeded. Instead, set MarketplaceService.ProcessReceipt once, in one server Script, and return PurchaseGranted only after the player has what they paid for:
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local COINS_100_PRODUCT_ID = 0 -- replace 0 with your developer product ID
-- One function per developer product. Each returns true only once the player has what they paid for.
local productHandlers: { [number]: (player: Player) -> boolean } = {}
productHandlers[COINS_100_PRODUCT_ID] = function(player: Player): boolean
local leaderstats = player:FindFirstChild("leaderstats")
local coins = leaderstats and leaderstats:FindFirstChild("Coins")
if coins and coins:IsA("IntValue") then
coins.Value += 100
return true
end
return false
end
local function processReceipt(receiptInfo: { [string]: any }): Enum.ProductPurchaseDecision
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
-- The buyer left. Roblox keeps the purchase open and offers it again when they rejoin.
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local handler = productHandlers[receiptInfo.ProductId]
if not handler then
warn(`No handler for developer product {receiptInfo.ProductId}`)
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local ok, granted = pcall(handler, player)
if ok and granted == true then
return Enum.ProductPurchaseDecision.PurchaseGranted
end
warn(`Could not grant product {receiptInfo.ProductId} to {player.Name}: {granted}`)
return Enum.ProductPurchaseDecision.NotProcessedYet
end
-- Set this once, in one server Script, for all of your developer products
MarketplaceService.ProcessReceipt = processReceipt
This assumes a leaderstats folder with a Coins IntValue, as in the leaderboard guide. Returning NotProcessedYet leaves the purchase open: Roblox calls ProcessReceipt again when the player rejoins or makes another developer product purchase.
This version is enough to try developer products. Before you sell anything valuable, read the limits on Roblox's ProcessReceipt reference: if you set no callback, receipts are acknowledged automatically and cannot be recovered; the callback for one purchase can run on two servers at once if the player joins a second server before it returns; and a granted result can still fail to record on Roblox's side, leaving the purchase open. The usual answer is to save the new coin total, together with the receipt's PurchaseId, in a data store before returning PurchaseGranted, so a receipt delivered twice is not paid twice. The DataStore tutorial covers the saving side.
Roblox's API reference also documents BindReceiptHandler, a newer way to route developer product receipts to separate handlers by product ID. A receipt that no bound handler claims falls through to ProcessReceipt, so you can move over one product at a time.
Common errors and fixes
Open the Output window (Window menu, then Output) in Studio, or the Developer Console with F9 in a live game, before anything else. GamePassHandler and PassButton print a warning naming the pass when a check or a lookup fails.
UserOwnsGamePassAsync always returns false
Check the ID first. It must be the pass ID from Monetization, Passes, Copy Asset ID, entered as a number: not the game ID, the place ID, a developer product ID, the ProductId that GetProductInfoAsync returns, or an old pass's legacy asset ID. If the player bought the pass on the Roblox website while already in your game, the API reference says the cached answer can take several minutes to update; the pass shows up as owned the next time they enter a server.
The perk only works after the player rejoins
Nothing on the server handles PromptGamePassPurchaseFinished, or it is connected in a LocalScript. Connect it in a server Script and grant the perk there, as GamePassHandler does.
Players get the perk without buying the pass
Something trusts the client: a LocalScript that grants the perk itself, a LocalScript reacting to its own copy of PromptGamePassPurchaseFinished, or a RemoteEvent that lets the client report a purchase. Remove them. Only the server, through UserOwnsGamePassAsync and the server-side event, should decide who owns a pass.
The speed boost is gone after the player dies
WalkSpeed belongs to the Humanoid, and every respawn creates a new Humanoid at the default speed of 16. Apply character perks on CharacterAdded, as applyCharacterPerks does.
The pass tool disappears after a respawn
Roblox replaces the Backpack when the character dies. Put a copy of the Tool in the player's StarterGear, which Roblox copies into each new Backpack.
The purchase window does not open, or shows an error
If Output says to paste the pass ID, the ID in PassConfig is still 0. Otherwise check that the ID belongs to a pass of this game, that the pass is on sale with a Robux price, and that the game is published. Roblox's Passes page says cross-game pass sales are disabled from 30 May 2026, so prompt only passes that belong to this game.
VIP players cannot get through the door
Check that the part is named VIPDoor and sits directly in Workspace when the server starts, and that no other script changes the CollisionGroup of character parts. A part can be in one collision group only, so the last script to set it wins.
An old tutorial uses GamePassService:PlayerHasPass or PromptPurchase
Those are for legacy passes identified by an asset ID. Roblox's GamePassService reference says PlayerHasPass does not work with new passes. Use UserOwnsGamePassAsync and PromptGamePassPurchase with the pass ID.
Build it with RoCode
If you would rather not wire this up by hand, RoCode can write it in the place you have open. RoCode is an independent AI agent for Roblox Studio: you describe the feature in its web chat at rocode.app, and its free Studio plugin creates the scripts and parts in your Explorer, so you are not copying code out of a chat window.
Add a VIP game pass with pass ID 0 for now. Owners walk through a VIPDoor part and get WalkSpeed 24, checked on join and granted right after purchase on the server. Add a Get VIP button that prompts the purchase.
- Can look through your place first with Search Scripts and Read Script for existing MarketplaceService code, so it can extend your purchase handling instead of adding a second copy
- Creates the door part with Create Instance, and writes the ModuleScript, the server Script and the LocalScript with Create Script
- Compile-checks each script it wrote inside Studio with Check Script. That is a compile check, not a type check or a playtest
- Sends its changes in batches, and each batch is marked as an undo point in Studio's history
You still create the pass and copy its ID in Creator Hub yourself, since RoCode has no tools for passes or developer products, and you test the purchase in your published game: RoCode does not start playtests. How RoCode connects to Studio. RoCode is an independent product, not affiliated with, endorsed by or sponsored by Roblox Corporation. Roblox and Roblox Studio are trademarks of Roblox Corporation.
Questions
Is a gamepass the same as a pass?
Yes. Roblox's documentation now calls game passes "passes", while the API names, such as UserOwnsGamePassAsync and PromptGamePassPurchase, still say GamePass. Gamepass, game pass and pass all mean the same thing.
How do I make a gamepass on mobile?
Roblox documents pass creation in Creator Hub at create.roblox.com and does not describe a separate phone or tablet flow. We have not checked those pages in a phone browser. Either way the pass needs a published game to belong to, and the scripts are written in Roblox Studio on a computer.
How do I make a game pass for PLS DONATE?
The pass is created the same way, in one of your own published games. How PLS DONATE finds and lists passes is up to that game and is not covered by Roblox's documentation, and we have not tested it. Roblox's Passes page says cross-game pass sales are disabled from 30 May 2026 and points games built on them to Robux transfers, so check PLS DONATE's own current instructions.
Do I need to save game passes in a data store?
Not for ownership: UserOwnsGamePassAsync asks Roblox every time a player joins. Roblox does not keep a per-player purchase history for you, though, so store one yourself if you need it. Developer products are different: you must save what they gave, such as coins.
Can a LocalScript check whether a player owns a pass?
Yes. Roblox's API reference says UserOwnsGamePassAsync works on the client and the server. Use the client answer only for display, such as hiding a Buy button, and grant perks on the server.
Should I use a game pass or a developer product for double coins?
A permanent double coins perk is a pass: bought once, checked with UserOwnsGamePassAsync. A boost that lasts 30 minutes and can be bought again is a developer product, granted in ProcessReceipt.
Sources and further reading
- Roblox Creator Docs: Passes. creating a pass, Copy Asset ID, selling inside the game, cross-game sales ending 30 May 2026, assigning privileges on join
- Roblox Creator Docs: Developer Products. repeatable purchases and the ProcessReceipt pattern
- Roblox Engine API: MarketplaceService. UserOwnsGamePassAsync caching, PromptGamePassPurchase, PromptGamePassPurchaseFinished, GetProductInfoAsync, ProcessReceipt limits and BindReceiptHandler
- Roblox Engine API: GamePassService. legacy asset IDs versus pass IDs
- Roblox Creator Docs: Regional pricing. managed pricing on by default for passes, and the pass Sales page
- Roblox Creator Docs: Shop. the automatic in-game Shop that lists your passes
- Roblox Creator Docs: Season Passes feature package. testing pass purchases in the published game rather than Studio
- Roblox Creator Docs: Collisions. collision groups and the character collision example
- Roblox Creator Docs: Network ownership (security). why a client controls its own character
How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 9 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.