Use this file to discover all available pages before exploring further.
Roblox provides four primary monetization channels: GamePasses (one-time permanent unlocks), Developer Products (consumable, repeatable purchases), Premium Payouts (passive revenue from Premium subscribers), and Rewarded Video Ads (ad-based earnings). Each serves a different purpose and should be combined strategically to maximize revenue without alienating players.The cardinal rule: all purchase granting must happen on the server. Never trust the client to determine what a player owns or has purchased.
GamePasses are one-time permanent purchases tied to the player’s account forever. Ideal for VIP perks, permanent stat boosts, cosmetic bundles, and feature unlocks.
-- ServerScriptService/GamePassService.lualocal MarketplaceService = game:GetService("MarketplaceService")local Players = game:GetService("Players")-- Map each GamePass ID to a grant function.-- Add new passes here; the rest handles them automatically.local GAME_PASSES = { [123456789] = { name = "VIP", grant = function(player: Player) player:SetAttribute("IsVIP", true) local character = player.Character or player.CharacterAdded:Wait() local humanoid = character:FindFirstChildOfClass("Humanoid") if humanoid then humanoid.WalkSpeed = 24 end end, }, [987654321] = { name = "2x Coins", grant = function(player: Player) player:SetAttribute("CoinMultiplier", 2) end, },}-- Grant perks on join: check every configured GamePasslocal function onPlayerAdded(player: Player) for gamePassId, passInfo in GAME_PASSES do local success, ownsPass = pcall(function() return MarketplaceService:UserOwnsGamePassAsync(player.UserId, gamePassId) end) if success and ownsPass then local grantSuccess, grantErr = pcall(passInfo.grant, player) if not grantSuccess then warn(`[GamePass] Failed to grant "{passInfo.name}" to {player.Name}: {grantErr}`) end elseif not success then warn(`[GamePass] Ownership check failed for {passInfo.name}: {ownsPass}`) end end -- Re-grant character-specific perks on every respawn player.CharacterAdded:Connect(function() for gamePassId, passInfo in GAME_PASSES do local success, ownsPass = pcall(function() return MarketplaceService:UserOwnsGamePassAsync(player.UserId, gamePassId) end) if success and ownsPass then pcall(passInfo.grant, player) end end end)end-- Grant perks mid-session when a player purchases during playMarketplaceService.PromptGamePassPurchaseFinished:Connect(function( player: Player, gamePassId: number, wasPurchased: boolean) if not wasPurchased then return end local passInfo = GAME_PASSES[gamePassId] if not passInfo then return end local success, err = pcall(passInfo.grant, player) if success then print(`[GamePass] Granted "{passInfo.name}" to {player.Name} (mid-session)`) else warn(`[GamePass] Failed to grant "{passInfo.name}" to {player.Name}: {err}`) endend)for _, player in Players:GetPlayers() do task.spawn(onPlayerAdded, player)endPlayers.PlayerAdded:Connect(onPlayerAdded)
-- Client-side: prompt a GamePass purchase from a button or shop GUIlocal MarketplaceService = game:GetService("MarketplaceService")local Players = game:GetService("Players")local VIP_PASS_ID = 123456789local function promptVIPPurchase() MarketplaceService:PromptGamePassPurchase(Players.LocalPlayer, VIP_PASS_ID)endscript.Parent.MouseButton1Click:Connect(promptVIPPurchase)
Developer Products are consumable, repeatable purchases. Players can buy them any number of times. Ideal for currency packs, temporary boosts, extra lives, loot crates, and skip-timers.
ProcessReceipt is the most critical callback in Roblox monetization. Only one script can set it — if two scripts assign it, only the last one takes effect.
Return Value
Meaning
Enum.ProductPurchaseDecision.PurchaseGranted
Item was granted. Roblox finalizes the purchase.
Enum.ProductPurchaseDecision.NotProcessedYet
Granting failed. Roblox will retry calling ProcessReceipt later (including on rejoin).
Return PurchaseGranted only after successfully granting the item. Returning PurchaseGranted without granting is a Roblox policy violation and causes player support tickets. If DataStore save fails, return NotProcessedYet so Roblox retries.
-- ServerScriptService/DeveloperProductService.lualocal MarketplaceService = game:GetService("MarketplaceService")local DataStoreService = game:GetService("DataStoreService")local Players = game:GetService("Players")local purchaseHistoryStore = DataStoreService:GetDataStore("PurchaseHistory")-- Map each product ID to a handler that returns true on successlocal PRODUCTS = { [111111111] = { name = "100 Coins", grant = function(player: Player): boolean local leaderstats = player:FindFirstChild("leaderstats") if not leaderstats then return false end local coins = leaderstats:FindFirstChild("Coins") if not coins then return false end coins.Value += 100 return true end, }, [222222222] = { name = "500 Coins", grant = function(player: Player): boolean local leaderstats = player:FindFirstChild("leaderstats") if not leaderstats then return false end local coins = leaderstats:FindFirstChild("Coins") if not coins then return false end coins.Value += 500 return true end, }, [333333333] = { name = "Speed Boost (60s)", grant = function(player: Player): boolean local character = player.Character if not character then return false end local humanoid = character:FindFirstChildOfClass("Humanoid") if not humanoid then return false end humanoid.WalkSpeed = 32 task.delay(60, function() if humanoid and humanoid.Parent then humanoid.WalkSpeed = 16 end end) return true end, },}local function processReceipt(receiptInfo): Enum.ProductPurchaseDecision -- 1. Idempotency guard: check if already granted local purchaseKey = `{receiptInfo.PlayerId}_{receiptInfo.PurchaseId}` local alreadyGranted = false local lookupSuccess, lookupErr = pcall(function() alreadyGranted = purchaseHistoryStore:GetAsync(purchaseKey) end) if not lookupSuccess then warn(`[Product] DataStore lookup failed for {purchaseKey}: {lookupErr}`) return Enum.ProductPurchaseDecision.NotProcessedYet end if alreadyGranted then return Enum.ProductPurchaseDecision.PurchaseGranted end -- 2. Find the product handler local productInfo = PRODUCTS[receiptInfo.ProductId] if not productInfo then warn(`[Product] No handler for product ID {receiptInfo.ProductId}`) return Enum.ProductPurchaseDecision.NotProcessedYet end -- 3. Find the player (may have left before ProcessReceipt fires) local player = Players:GetPlayerByUserId(receiptInfo.PlayerId) if not player then return Enum.ProductPurchaseDecision.NotProcessedYet end -- 4. Grant the item local grantSuccess = false local grantOk, grantErr = pcall(function() grantSuccess = productInfo.grant(player) end) if not grantOk then warn(`[Product] Grant error for "{productInfo.name}": {grantErr}`) return Enum.ProductPurchaseDecision.NotProcessedYet end if not grantSuccess then warn(`[Product] Grant returned false for "{productInfo.name}"`) return Enum.ProductPurchaseDecision.NotProcessedYet end -- 5. Record the purchase BEFORE returning PurchaseGranted local saveSuccess, saveErr = pcall(function() purchaseHistoryStore:SetAsync(purchaseKey, true) end) if not saveSuccess then warn(`[Product] CRITICAL: Grant succeeded but history save failed for {purchaseKey}: {saveErr}`) end print(`[Product] Granted "{productInfo.name}" to {player.Name} (PurchaseId: {receiptInfo.PurchaseId})`) return Enum.ProductPurchaseDecision.PurchaseGrantedend-- Only ONE script can set this callbackMarketplaceService.ProcessReceipt = processReceipt
Roblox automatically pays developers based on how much time Premium subscribers spend in the game. No purchase prompt — purely passive. The more quality engagement time from Premium players, the higher the payout.
-- ServerScriptService/PremiumService.lualocal Players = game:GetService("Players")local function grantPremiumPerks(player: Player) player:SetAttribute("IsPremium", true) -- Examples: extra daily reward, exclusive cosmetics, bonus XP, premium-only areas local leaderstats = player:FindFirstChild("leaderstats") if leaderstats then local coins = leaderstats:FindFirstChild("Coins") if coins then coins.Value += 50 -- daily Premium login bonus end endendlocal function onPlayerAdded(player: Player) if player.MembershipType == Enum.MembershipType.Premium then grantPremiumPerks(player) end -- Real-time detection: player may subscribe mid-session player:GetPropertyChangedSignal("MembershipType"):Connect(function() if player.MembershipType == Enum.MembershipType.Premium then grantPremiumPerks(player) end end)endfor _, player in Players:GetPlayers() do task.spawn(onPlayerAdded, player)endPlayers.PlayerAdded:Connect(onPlayerAdded)
Players opt in to watching a short ad in exchange for an in-game reward. Revenue is generated per completed view.
-- ServerScriptService/RewardedAdService.lualocal Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local requestAdEvent = Instance.new("RemoteEvent")requestAdEvent.Name = "RequestRewardedAd"requestAdEvent.Parent = ReplicatedStoragelocal adCooldowns: { [number]: number } = {}local COOLDOWN_SECONDS = 300 -- 5-minute cooldown between adsrequestAdEvent.OnServerEvent:Connect(function(player: Player) local now = os.time() local lastAd = adCooldowns[player.UserId] or 0 if now - lastAd < COOLDOWN_SECONDS then local remaining = COOLDOWN_SECONDS - (now - lastAd) warn(`[Ads] {player.Name} on cooldown, {remaining}s remaining`) return end local leaderstats = player:FindFirstChild("leaderstats") if leaderstats then local coins = leaderstats:FindFirstChild("Coins") if coins then coins.Value += 25 -- reward equivalent to 3–10 Robux in value end end adCooldowns[player.UserId] = now print(`[Ads] Granted rewarded ad bonus to {player.Name}`)end)Players.PlayerRemoving:Connect(function(player: Player) adCooldowns[player.UserId] = nilend)
-- Wrap every MarketplaceService call in pcalllocal success, result = pcall(function() return MarketplaceService:UserOwnsGamePassAsync(player.UserId, passId)end)if not success then warn(`[Purchase] API call failed: {result}`) -- Do NOT assume they own it; do NOT assume they don't. -- Cache the last known state and retry later.end
local lastPromptTime: { [number]: number } = {}local function safePrompt(player: Player, productId: number) local now = os.time() if lastPromptTime[player.UserId] and now - lastPromptTime[player.UserId] < 60 then return -- too soon, skip end lastPromptTime[player.UserId] = now MarketplaceService:PromptProductPurchase(player, productId)end
Client-side purchase granting is exploitable. Exploiters can fire RemoteEvents and manipulate client-side logic. Always grant on the server via ProcessReceipt or PromptGamePassPurchaseFinished + UserOwnsGamePassAsync.
Improper ProcessReceipt: returning PurchaseGranted without granting
-- BAD: Returns PurchaseGranted without actually grantingMarketplaceService.ProcessReceipt = function(receiptInfo) -- "I'll grant it later" return Enum.ProductPurchaseDecision.PurchaseGranted -- Player never gets their itemend
No error handling in ProcessReceipt
-- BAD: crashes if player left or leaderstats missingMarketplaceService.ProcessReceipt = function(receiptInfo) local player = Players:GetPlayerByUserId(receiptInfo.PlayerId) player.leaderstats.Coins.Value += 100 -- errors if player is nil return Enum.ProductPurchaseDecision.PurchaseGrantedend
No idempotency check causing duplicate grants
-- BAD: if ProcessReceipt is retried (player was absent), they get double coinsMarketplaceService.ProcessReceipt = function(receiptInfo) local player = Players:GetPlayerByUserId(receiptInfo.PlayerId) if player then player.leaderstats.Coins.Value += 100 end return Enum.ProductPurchaseDecision.PurchaseGrantedend-- GOOD: check purchaseHistoryStore first (see complete system above)
Misleading descriptions
Do not describe a GamePass as “2× Everything” if it only doubles coins. Do not show 1,000 coins in the product icon but grant 100. Roblox can remove misleading assets, and players will leave negative reviews.