Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/brockmartin/roblox-game-skill/llms.txt

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

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.

Core API

Method / EventPurpose
MarketplaceService:UserOwnsGamePassAsync(userId, gamePassId)Check if a player owns a GamePass
MarketplaceService:PromptGamePassPurchase(player, gamePassId)Show the purchase prompt
MarketplaceService.PromptGamePassPurchaseFinishedFires when the prompt closes (purchased or cancelled)

Complete GamePass System

-- ServerScriptService/GamePassService.lua
local 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 GamePass
local 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 play
MarketplaceService.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}`)
    end
end)

for _, player in Players:GetPlayers() do
    task.spawn(onPlayerAdded, player)
end
Players.PlayerAdded:Connect(onPlayerAdded)

Prompting from the Client

-- Client-side: prompt a GamePass purchase from a button or shop GUI
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")

local VIP_PASS_ID = 123456789

local function promptVIPPurchase()
    MarketplaceService:PromptGamePassPurchase(Players.LocalPlayer, VIP_PASS_ID)
end

script.Parent.MouseButton1Click:Connect(promptVIPPurchase)

Developer Products

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.

The ProcessReceipt Contract

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 ValueMeaning
Enum.ProductPurchaseDecision.PurchaseGrantedItem was granted. Roblox finalizes the purchase.
Enum.ProductPurchaseDecision.NotProcessedYetGranting 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.

Complete Developer Product System

-- ServerScriptService/DeveloperProductService.lua
local 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 success
local 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.PurchaseGranted
end

-- Only ONE script can set this callback
MarketplaceService.ProcessReceipt = processReceipt

Premium Payouts

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.

Detecting Premium Players

-- ServerScriptService/PremiumService.lua
local 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
    end
end

local 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)
end

for _, player in Players:GetPlayers() do
    task.spawn(onPlayerAdded, player)
end
Players.PlayerAdded:Connect(onPlayerAdded)

Premium Upsell

-- Client-side: prompt non-Premium players to subscribe
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")

local player = Players.LocalPlayer

if player.MembershipType ~= Enum.MembershipType.Premium then
    MarketplaceService:PromptPremiumPurchase(player)
end

Rewarded Video Ads

Players opt in to watching a short ad in exchange for an in-game reward. Revenue is generated per completed view.
-- ServerScriptService/RewardedAdService.lua
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local requestAdEvent = Instance.new("RemoteEvent")
requestAdEvent.Name = "RequestRewardedAd"
requestAdEvent.Parent = ReplicatedStorage

local adCooldowns: { [number]: number } = {}
local COOLDOWN_SECONDS = 300 -- 5-minute cooldown between ads

requestAdEvent.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] = nil
end)

Best Ad Placements

PlacementWhy It Works
Between roundsNatural break; player is already waiting
In lobby / waiting areaLow-stakes moment; nothing else to do
After death (optional revive)High motivation; clear value proposition
Daily bonus multiplier”Watch ad to double your daily reward”
Avoid: Mid-gameplay interruptions, mandatory ads, and ads that block progression.

Pricing Strategy

Common Roblox Price Points

RobuxTypical Use
25Minimum viable price. Small cosmetic, single-use consumable
50Minor cosmetic pack, small currency bundle
100Standard GamePass, decent currency pack
250Premium GamePass (2× coins, VIP), mid currency bundle
500Major GamePass (significant perk), large currency pack
1,000Top-tier GamePass, mega currency bundle
2,500+Whale-tier only — use sparingly

Key Pricing Tactics

Anchoring

Show the most expensive option first. When a player sees “Mega Pack: 1,000 R"first,the"StarterPack:100R" first, the "Starter Pack: 100 R” feels like a bargain.

Bundle Value

Offer multi-item bundles at a per-unit discount. Tag the middle tier “Best Value” and the largest tier “Most Popular.”

Odd Pricing

49 Rfeelscheaperthan50R feels cheaper than 50 R. Roblox players respond to psychological pricing the same as real-world consumers.

Price Floor

Never price below 25 R$. Roblox takes a 30% fee, and extremely low-priced items generate negligible revenue.

DevEx Math

Exchange Rate (2026)

1 Robux earned ≈ $0.0035 USD
Robux EarnedUSD Value
30,000 (minimum cashout)~$105
100,000~$350
1,000,000~$3,500

Revenue Projection Formula

Daily Revenue (R$)   = DAU × Conversion Rate × Avg Purchase (R$)
Monthly Revenue (R$) = Daily Revenue × 30
Monthly Revenue (USD) = Monthly Revenue (R$) × 0.0035
DAUConversion RateAvg PurchaseDaily R$Monthly USD
1002%100 R$200$21
1,0002%100 R$2,000$210
10,0003%150 R$45,000$4,725
100,0003%150 R$450,000$47,250
Typical conversion rates are 1–5% of DAU making a purchase on any given day. Premium Payouts add roughly 10–30% on top of direct purchase revenue.

Ethical Monetization

Roblox’s audience skews young. This carries a responsibility to monetize fairly, and Roblox actively enforces policies against predatory practices.
  • Provide genuine value for every purchase
  • Disclose exact odds on any randomized rewards (loot boxes, mystery eggs)
  • Allow core gameplay for free — purchases enhance, not gate
  • Price transparently — show the Robux cost clearly before any prompt
  • Offer earnable alternatives where practical
  • Respect declining — never immediately re-prompt a closed dialog

Best Practices

Graceful API Failure Handling

-- Wrap every MarketplaceService call in pcall
local 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

Receipt Logging

Log every purchase for customer support and debugging:
-- Inside ProcessReceipt, after granting
print(`[Receipt] Player={receiptInfo.PlayerId} Product={receiptInfo.ProductId} PurchaseId={receiptInfo.PurchaseId} CurrencySpent={receiptInfo.CurrencySpent}`)

Purchase Prompt Cooldown

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

Natural Prompt Placement

Good Placements

  • Dedicated shop GUI opened voluntarily
  • Contextually at a locked feature (“This area is VIP-only. Unlock VIP?”)
  • After the player has played several minutes and understands the game’s value

Bad Placements

  • Immediately on join before the player loads
  • Every 30 seconds via popup
  • Blocking the screen during active gameplay

Common Mistakes

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.
-- BAD: Returns PurchaseGranted without actually granting
MarketplaceService.ProcessReceipt = function(receiptInfo)
    -- "I'll grant it later"
    return Enum.ProductPurchaseDecision.PurchaseGranted -- Player never gets their item
end
-- BAD: crashes if player left or leaderstats missing
MarketplaceService.ProcessReceipt = function(receiptInfo)
    local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
    player.leaderstats.Coins.Value += 100 -- errors if player is nil
    return Enum.ProductPurchaseDecision.PurchaseGranted
end
-- BAD: if ProcessReceipt is retried (player was absent), they get double coins
MarketplaceService.ProcessReceipt = function(receiptInfo)
    local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
    if player then
        player.leaderstats.Coins.Value += 100
    end
    return Enum.ProductPurchaseDecision.PurchaseGranted
end

-- GOOD: check purchaseHistoryStore first (see complete system above)
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.

Build docs developers (and LLMs) love