roblox-gamepasses
Implement Roblox game passes, developer products, subscriptions, and Robux transfers. Use for purchase prompts, server-side fulfillment, ownership checks, recurring benefits, and monetization policy.
Install
npx skills add https://github.com/nonlooped/roblox-suite/tree/main/roblox-gamepasses
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install nonlooped-roblox-suite@llmmart
git clone https://github.com/nonlooped/roblox-suite.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole nonlooped/roblox-suite collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
roblox-gamepasses
Primary official source: https://create.roblox.com/docs/en-us/production/monetization/passes (plus developer-products.md for contrast, and the MarketplaceService class reference).
Grant benefits on the server after verifying the purchase or ownership. Keep purchase prompts and shop display on the client.
See roblox-datastores for how to store "player owns this pass" state or associated perks, and roblox-networking for the Remote validation layer around purchase prompts.
Game passes and developer products
- Game Pass: One-time purchase. Permanent privilege for that specific experience (VIP access, permanent item, extra slot, cosmetic unlock, etc.). Roblox tracks ownership per user per experience.
- Developer Product: Repeatable / consumable (currency packs, potions, revives, temporary boosts). Can be bought many times. Requires ProcessReceipt callback on the server for fulfillment.
- As of May 30, 2026, cross-experience game pass and developer product sales are disabled. Design experience-specific passes or use the Robux Transfers API for donation-style flows.
MarketplaceService:PromptRobuxTransferAsyncmust be called from the server. Only Roblox Plus subscribers can initiate transfers, transfer amounts must be between 10 and 500 Robux, and the sender cannot equal the receiver. The recipient receives 90% and the experience earns the other 10%; Roblox takes no transfer fee. ABindReceiptHandlercallback must process transfer receipts. Roblox permits an experience to grant items or perks after a successful receipt; choosing not to attach rewards is an opinionated anti-abuse/design recommendation, not a platform rule. - You (the creator) are 100% responsible for actually delivering the benefit. Roblox only handles the transaction and the UserOwnsGamePassAsync query.
- Passes can be used for randomized virtual items only if you follow the Paid Random Items policy.
Creation and asset ID
- Creator Dashboard → your published experience → Monetization → Passes → Create a Pass.
- Upload circular-friendly icon (≤512×512, jpg/png/bmp; important content must survive circular crop).
- Name + description.
- After creation: hover the pass → ⋯ → Copy Asset ID. This number is the passID you use in all scripts.
For external sales on the game page Store tab: go to the pass → Sales → enable "Item for Sale" and set Robux price (1 to 1B).
Prompt purchases and grant benefits
Client side (LocalScript or UI module; only for prompting and optimistic display):
- Call MarketplaceService:UserOwnsGamePassAsync (pcall) to decide "Buy" vs "Owned" button state.
- If not owned, call MarketplaceService:PromptGamePassPurchase(player, passID).
Server side (Script in ServerScriptService; the only place that grants benefits):
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local PASS_ID = 1234567890
local granted = {} -- idempotency guard: granted[player][passID]
local function grantPassBenefits(player: Player, passID: number)
if not player or not player:IsDescendantOf(Players) then
return
end
local playerGranted = granted[player]
if not playerGranted then
playerGranted = {}
granted[player] = playerGranted
end
if playerGranted[passID] then
return
end
playerGranted[passID] = true
-- Apply the actual privilege (attributes, table entry, DataStore flag, Remote to client for visuals, etc.)
-- This is the source of truth.
end
MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player: Player, purchasedPassID: number, wasPurchased: boolean)
-- This event also fires on the client; never grant benefits from the client copy.
if not player or typeof(purchasedPassID) ~= "number" then
return
end
if wasPurchased and purchasedPassID == PASS_ID then
local ok, err = pcall(function()
grantPassBenefits(player, PASS_ID)
end)
if not ok then
warn("Failed to grant pass benefits:", err)
end
end
end)
Players.PlayerAdded:Connect(function(player: Player)
local success, owns = pcall(function()
return MarketplaceService:UserOwnsGamePassAsync(player.UserId, PASS_ID)
end)
if success and owns then
local ok, err = pcall(function()
grantPassBenefits(player, PASS_ID)
end)
if not ok then
warn("Failed to apply owned pass benefits:", err)
end
elseif not success then
warn("UserOwnsGamePassAsync failed for", player.Name)
end
end)
Players.PlayerRemoving:Connect(function(player: Player)
granted[player] = nil
end)
GetProductInfoAsync for dynamic UI (price, name, description, IsForSale):
Use MarketplaceService:GetProductInfoAsync(id, Enum.InfoType.GamePass). Do this on the client for display, but never grant based on the result. The non-async counterpart GetProductInfo still exists, but GetProductInfoAsync is preferred.
Capability requirements
Game passes and developer products require a published experience. Enable Studio Access to API Services only when a specific API used by a dedicated test experience requires it; do not treat that Studio setting as a blanket prerequisite for every MarketplaceService purchase API. Verify each API's current requirements in its Engine Reference entry.
Product recommendations
MarketplaceService:RankProductsAsync(arrayOfIdentifiers): pass a table of up to 50{InfoType = Enum.InfoType.GamePass, Id = ...}. Returns a personalized ranking for the current user as{ProductIdentifier, ProductInfo}items. Use sparingly; call once at join.MarketplaceService:RecommendTopProductsAsync({Enum.InfoType.GamePass, Enum.InfoType.Product}): returns up to 50 recommended products the user is likely to engage with. Results usually exclude already-owned items, but verify in your UI. Use sparingly; call once at join.
Use these results in a "Recommended for you" section of your shop. Measure whether they improve conversion in your experience.
Promotions (buy robux page bonus pool)
You can opt passes into the promotion pool so that users buying Robux packages may receive the pass for free (contextually relevant to their history).
Requirements (from the passes doc):
- Unique pass recommended.
- If on sale, price between 50 and 800 Robux.
- Must have thumbnail.
- Must comply with Community Standards.
- Cannot be a paid random item.
Opt-in via the pass's Promotions tab in the dashboard.
Analytics and iteration
In Creator Dashboard → experience → Monetization → Passes → Analytics tab you get:
- Top passes by sales and net Robux.
- Time-series graphs.
- Attribution for passes acquired via Buy Robux promotions and subsequent joins.
Use this data to decide pricing, which perks are compelling, and when to run promotions.
Security, data, and policy constraints
- Prompt only from client. Grant and record only on the server in the PromptGamePassPurchaseFinished handler or on PlayerAdded re-check.
- Always pcall
UserOwnsGamePassAsyncandPrompt...calls. - Re-check ownership on every relevant join/session start before granting gameplay or economy perks.
- Store your own record of ownership + associated state in DataStores if you need history or custom metadata (Roblox does not expose full per-user pass purchase history via the Engine API).
- For RTBF / right-to-be-forgotten, include pass-related keys in your deletion patterns (see roblox-datastores skill).
- Never hardcode Robux prices in UI that the player sees: use
GetProductInfoAsyncso regional pricing and optimizations work. - Test purchases only on dedicated test experiences.
- For paid random items (loot boxes / gacha passes), use
PolicyServicefirst: checkPolicyService:GetPolicyInfoForPlayerAsync(player).ArePaidRandomItemsRestrictedandIsPaidItemTradingAllowedbefore offering randomized paid content. - For developer products,
MarketplaceService.ProcessReceiptcan only be assigned once globally; assign it once in a single server script. The callback must returnEnum.ProductPurchaseDecision.PurchaseGrantedafter successful fulfillment, orEnum.ProductPurchaseDecision.NotProcessedYetif fulfillment fails, because Roblox may redeliver the receipt untilPurchaseGrantedis returned. - Donation / tipping games using
PromptRobuxTransferAsyncare high-risk for abuse; implement rate limits, alt-account detection, moderation pipelines, and avoid granting in-experience advantages in exchange for transfers.
Subscriptions (recurring benefits)
Subscriptions offer users recurring benefits for a monthly fee, auto-renewing in Robux or local currency. Unlike passes (permanent), subscription benefits persist only while the user keeps paying. Up to 50 per experience; single-tiered (no mutually exclusive Bronze/Silver/Gold); regional pricing enabled by default for Robux-priced subs.
API surface (subscription IDs are strings like "EXP-11111111"):
MarketplaceService:GetUserSubscriptionStatusAsync(player, subscriptionId): server-only, returns{IsSubscribed: boolean}.MarketplaceService:PromptSubscriptionPurchase(player, subscriptionId): client prompt.MarketplaceService.PromptSubscriptionPurchaseFinished(player, subscriptionId, didTryPurchasing): notedidTryPurchasingis an attempt signal, not success; re-check status after a delay.Players.UserSubscriptionStatusChanged(player, subscriptionId): server-only, fires on purchase/renewal/cancellation.MarketplaceService:GetSubscriptionProductInfoAsync(subscriptionId)andGetUserSubscriptionPaymentHistoryAsync(player, subscriptionId).
Security (same posture as passes): prompt on client, check status and grant/revoke on server only, pcall everything, re-check on every join (subscriptions lapse), respect PolicyService:IsEligibleToPurchaseSubscription per player, persist nothing sensitive on the client.
Payouts: Robux-priced subs pay 70% each month. Local-currency subs pay 70% first month, 100% thereafter, with a 30-day hold. Robux subs are not refundable; local-currency subs are refundable within the hold window.
See references/subscriptions.md for the complete flow, client/server code, migration from passes, and gotchas.
Creator Rewards (replaces Premium Payouts)
As of July 24, 2025, Engagement-Based Payouts (formerly "Premium Payouts") and the Creator Affiliate program were discontinued and replaced by Creator Rewards. There is no longer a per-Premium-play-minute payout to integrate against.
Creator Rewards is a platform program with two payment types. It requires no in-experience integration:
- Daily Engagement Rewards: 5 Robux per day per Active Spender who spends 10+ minutes in your experience, provided it's one of the first three experiences they visit that day.
- Audience Expansion Rewards: 35% revenue share on a new/reactivated user's first $100 of qualifying purchases in their first 60 days, attributed via Share Links, direct experience links, or experience-name search.
Official source: https://create.roblox.com/docs/en-us/creator-rewards
When to use game passes vs other monetization
- One-time permanent unlock or access → Game Pass.
- Repeatable purchase (currency, consumables, temporary power) → Developer Product (with proper ProcessReceipt).
- Recurring monthly benefit → Subscription (see references/subscriptions.md;
MarketplaceService:GetUserSubscriptionStatusAsyncetc.).
See the developer-products doc for the repeatable flow.
Scripts
scripts/PassPurchaseHelper.lua: a client-side helper for game pass button state, price display, and prompting.
Reference index
- creation-and-setup.md: Create a pass, find its asset ID, or configure sales and test access.
- policyservice.md: Gate a feature using per-player monetization or content restrictions.
- purchase-flow-and-granting.md: Implement pass purchase prompts, ownership checks, and server grants.
- rules-policies-and-security.md: Review monetization restrictions, paid random items, or purchase security.
- subscriptions.md: Create recurring benefits or handle subscription status and renewals.
Files (roblox-suite)
-
references
-
creation-and-setup.md 4.9 KB
--- read_when: "Create a pass, find its asset ID, or configure sales and test access" last_reviewed: 2026-06-17 --- # Creation and setup of game passes **Official guide:** https://create.roblox.com/docs/en-us/production/monetization/passes ## Creating a pass 1. Go to Creations in Creator Dashboard. 2. Select your experience (must be published). 3. Monetization → Passes → "Create a Pass". 4. Upload icon: Must be suitable for circular crop (important visual content must be inside the circle). Recommended formats: .jpg, .png, .bmp. Max 512x512. 5. Provide name and description. 6. Submit. After creation, the pass appears in the list. ## Obtaining the pass ID (asset ID) - Hover over the pass thumbnail in the list. - Click the ⋯ menu. - Select "Copy Asset ID". This ID (a number like 1234567890) is what you use in all `MarketplaceService` calls: - `GetProductInfoAsync(id, Enum.InfoType.GamePass)` (preferred; the non-async `GetProductInfo` still exists but `GetProductInfoAsync` is recommended) - `PromptGamePassPurchase(player, id)` - `UserOwnsGamePassAsync(userId, id)` ## Enabling sales (external / game page store) For the pass to appear on the game's Store tab on Roblox.com: 1. In the Passes list, select the pass or go to its detail. 2. Sales tab. 3. Toggle "Item for Sale". 4. Enter Robux price (minimum 1, max 1,000,000,000). 5. Save. The price affects your Robux earnings after Roblox fees. ## Icon best practices and validation errors Icons are validated strictly. Common issues: - Content outside the circular boundary gets cropped. - Low resolution or poor contrast. - Text or important details near edges. Test by viewing the pass in the in-experience purchase prompt and on the web store. ## Group vs individual ownership When publishing the animation or pass: - If the experience is group-owned, select the group as the creator during publish/export for the asset. - Same applies conceptually for passes (the experience ownership determines who can manage). ## Testing setup - If a backend API used in Studio requires it, enable **Studio Access to API Services** only for a dedicated test experience. Do not grant Studio sessions access to production data. - Create a separate test experience/universe that mirrors your production one. - Test the full flow: prompt → purchase (use small test Robux if possible, or Roblox test accounts). - Verify `UserOwnsGamePassAsync` returns true after purchase on new servers. - Test re-join behavior. ## Initial Script skeleton (before full flow) Place in ServerScriptService: ```lua local MarketplaceService = game:GetService("MarketplaceService") local Players = game:GetService("Players") local PASS_ID = 1234567890 -- Replace with your pass ID local granted = {} -- idempotency guard: granted[player][passID] local function grantPassBenefits(player: Player, passID: number) if not player or not player:IsDescendantOf(Players) then return end local playerGranted = granted[player] if not playerGranted then playerGranted = {} granted[player] = playerGranted end if playerGranted[passID] then return end playerGranted[passID] = true -- Apply permanent benefit here -- e.g. player:SetAttribute("HasVIP", true) -- or save to DataStore via your data manager end -- Server grant handler MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player: Player, passID: number, wasPurchased: boolean) -- This event also fires on the client; never grant benefits from the client copy. if not player or typeof(passID) ~= "number" then return end if wasPurchased and passID == PASS_ID then local ok, err = pcall(function() grantPassBenefits(player, PASS_ID) end) if not ok then warn("Failed to grant pass benefits:", err) end end end) -- Re-apply on join Players.PlayerAdded:Connect(function(player: Player) local owns = false local ok, err = pcall(function() owns = MarketplaceService:UserOwnsGamePassAsync(player.UserId, PASS_ID) end) if ok and owns then local grantOk, grantErr = pcall(function() grantPassBenefits(player, PASS_ID) end) if not grantOk then warn("Failed to apply owned pass benefits:", grantErr) end elseif not ok then warn("Failed to check gamepass ownership for", player.Name, err) end end) Players.PlayerRemoving:Connect(function(player: Player) granted[player] = nil end) ``` See the purchase-flow reference for the complete client prompting code and error handling. ## Common setup mistakes - Using the wrong InfoType (must be GamePass, not Product). - Forgetting that passes are experience-specific (after the 2026 change). - Not handling the case where the player already owns it (show "Owned" UI instead of prompting again). - Placing grant logic in a LocalScript. Next reference: purchase-flow-and-granting.md for the full end-to-end code. -
policyservice.md 8.8 KB
--- read_when: "Gate a feature using per-player monetization or content restrictions" last_reviewed: 2026-06-17 --- # PolicyService reference **Official source:** https://create.roblox.com/docs/en-us/reference/engine/classes/PolicyService `PolicyService` queries per-player policy compliance based on geolocation, age group, and platform. Use it to gate monetization, content sharing, ads, commerce, and region-specific behavior **per player**, not globally. The same experience can have players in different policy regimes at once. `PolicyService` is a service (`game:GetService("PolicyService")`). It is `NotCreatable`, `NotReplicated`, and tagged as a Service. Its async methods **yield** and **must** be wrapped in `pcall`. They are **thread-unsafe** and require the `Basic` capability. ## Methods ### `GetPolicyInfoForPlayerAsync(player: Player): Dictionary` Returns a dictionary of policy flags for the player. **Yields**; wrap in `pcall`. Server-callable; on the client, only callable for `Players.LocalPlayer`. The returned dictionary contains these fields: | Field | Type | Gates | | --- | --- | --- | | `AreAdsAllowed` | boolean | Immersive ads. If false, do not show immersive ads to this player. | | `ArePaidRandomItemsRestricted` | boolean | Paid random items (loot boxes, gacha). If true, the player **cannot** interact with paid random item generators, whether via in-experience currency bought with Robux or Robux directly. | | `IsContentSharingAllowed` | boolean | UGC sharing (screen captures, video, image feeds). If false, disable features that let the user share content others can see. | | `IsEligibleToPurchaseCommerceProduct` | boolean | Real-world [commerce products](https://create.roblox.com/docs/en-us/production/monetization/commerce-products). If false, the player cannot purchase commerce products in-experience. | | `IsEligibleToPurchaseSubscription` | boolean | Subscriptions (see [subscriptions.md](subscriptions.md)). If false, do not offer a subscription purchase to this player. | | `IsPaidItemTradingAllowed` | boolean | Trading virtual items purchased with in-experience currency or Robux. If false, disable paid-item trading for this player. | | `IsPhotoToAvatarAllowed` | boolean | `AvatarCreationService:PromptSelectAvatarGenerationImageAsync()`. If false, the Photo-to-Avatar API is unavailable to this player. | | `IsSubjectToChinaPolicies` | boolean | If true, enforce China compliance changes (see the [Roblox China program post](https://devforum.roblox.com/t/new-programs-available-roblox-china-licensed-to-operate/1023361)). | | `IsEndlessContentLoadAllowed` | boolean | Features where content loads automatically and endlessly as the user scrolls (a feed). If false, require manual load/pagination instead. | | `IsEndlessContentAutoplayAllowed` | boolean | Media content (video or audio) that auto-plays endlessly without user initiation. If false, require explicit play actions. | | `AllowedExternalLinkReferences` | array | **Legacy.** Always returns an empty array. Do not rely on it. | ### `CanViewBrandProjectAsync(player: Player, brandProjectId: string): boolean` Determines whether a player may see a specific brand project's assets. Requires a brand project ID provided by Roblox (request one via the [brand project form](https://docs.google.com/forms/d/e/1FAIpQLSfGTRQwATB2wUg0P4HUSTtyXrhptFahJifo1ew84SyqtfSBfg/viewform)). **Yields**; wrap in `pcall`. **Server-only**. Calling from the client errors. Pattern: query on the server, then `RemoteEvent:FireClient(player, assetToShow)` with either the branded asset or a default fallback. ## Error handling Like any async call, wrap in `pcall`. Documented error messages: | Message | Reason | | --- | --- | | `Instance was not a player` | `player` parameter is not a `Player`. | | `Players not found` | Internal error; the `Players` service is missing. | | `This method cannot be called on the client for a non-local player` | Client-side call for a non-local `Player`. | | `GetPolicyInfoForPlayerAsync is called too many times` | More than ~100 concurrent calls before an HTTP response returns. Throttle. | ## Patterns ### Gate paid random items (loot boxes) ```lua local PolicyService = game:GetService("PolicyService") local Players = game:GetService("Players") local player = Players.LocalPlayer local success, result = pcall(function() return PolicyService:GetPolicyInfoForPlayerAsync(player) end) if not success then warn("PolicyService error: " .. tostring(result)) elseif result.ArePaidRandomItemsRestricted then warn("Player cannot interact with paid random item generators") -- Hide/disable the gacha UI for this player end ``` ### Gate subscription offers (server-side, per join) ```lua -- Server local PolicyService = game:GetService("PolicyService") local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local showSubsRemote = ReplicatedStorage:WaitForChild("ShowSubscriptionOffers") -- RemoteEvent Players.PlayerAdded:Connect(function(player: Player) local ok, info = pcall(function() return PolicyService:GetPolicyInfoForPlayerAsync(player) end) if not ok then warn("PolicyService failed for " .. player.Name .. ": " .. tostring(info)) showSubsRemote:FireClient(player, false) return end showSubsRemote:FireClient(player, info.IsEligibleToPurchaseSubscription == true) end) ``` ### Gate brand projects (server queries, client renders) ```lua -- Server local PolicyService = game:GetService("PolicyService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local Players = game:GetService("Players") local brandedAsset = ReplicatedStorage:WaitForChild("BrandedAsset") local defaultAsset = Instance.new("Part") local RemoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent") Players.PlayerAdded:Connect(function(player: Player) local success, canView = pcall(function() return PolicyService:CanViewBrandProjectAsync(player, "BRP-0123456789") end) if success and canView then RemoteEvent:FireClient(player, brandedAsset) else RemoteEvent:FireClient(player, defaultAsset) end end) ``` ```lua -- Client local ReplicatedStorage = game:GetService("ReplicatedStorage") local RemoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent") RemoteEvent.OnClientEvent:Connect(function(partToLoad) local clonedPart = partToLoad:Clone() clonedPart.Parent = workspace end) ``` ## Rules and gotchas - **Every call yields and must be pcall'd.** A thrown error here is not a player kick. Handle it and degrade gracefully (usually: hide the gated feature). - **Throttle.** More than ~100 in-flight `GetPolicyInfoForPlayerAsync` calls before HTTP responses return will error. Cache the result per-player for the session; you rarely need to re-query mid-session unless a player's region/platform could change (rare). - **Call from the correct side.** `GetPolicyInfoForPlayerAsync` is server-safe and client-safe *for the local player only*; `CanViewBrandProjectAsync` is **server-only** and errors on the client. - **Don't gate globally.** Two players in the same server can have different flags. Gate per-player, not with a single experience-wide boolean. - **Combine with `LocalizationService:GetCountryRegionForPlayerAsync`** when you need the actual country/region code (a string) for finer-grained logic. `PolicyService` tells you the *restrictions*, `LocalizationService` tells you the *where*. - **Don't trust the client for grant decisions.** If the client reads policy flags and the server grants based on a client Remote saying "ads are allowed for me," that's exploitable. Re-query on the server for any decision that affects economy or access. - **`AllowedExternalLinkReferences` is legacy and always empty.** Do not build logic on it. - **China (`IsSubjectToChinaPolicies`)** requires experience-specific compliance changes: see the Roblox China program documentation before relying on this flag. ## When to use what | Need | Method | Side | | --- | --- | --- | | "Can this player see paid random items / trade paid items / share content / see ads / buy subscriptions / buy commerce products?" | `GetPolicyInfoForPlayerAsync` | Server (or client for LocalPlayer) | | "Can this player see this brand's assets?" | `CanViewBrandProjectAsync` | Server only | | "What country/region is this player in?" | `LocalizationService:GetCountryRegionForPlayerAsync` | Either | ## Sources - https://create.roblox.com/docs/en-us/reference/engine/classes/PolicyService - https://create.roblox.com/docs/en-us/production/monetization/virtual-items (Paid Random Items policy) - https://create.roblox.com/docs/en-us/production/monetization/subscriptions (`IsEligibleToPurchaseSubscription`) - https://create.roblox.com/docs/en-us/production/monetization/commerce-products (`IsEligibleToPurchaseCommerceProduct`) - https://devforum.roblox.com/t/new-programs-available-roblox-china-licensed-to-operate/1023361 (China policies) -
purchase-flow-and-granting.md 6.4 KB
--- read_when: "Implement pass purchase prompts, ownership checks, and server grants" last_reviewed: 2026-06-17 --- # Purchase flow and server granting ## Full recommended flow ### Client side (prompting and UI state) ```lua local MarketplaceService = game:GetService("MarketplaceService") local Players = game:GetService("Players") local player = Players.LocalPlayer local PASS_ID = 1234567890 -- Replace with your pass ID local function getProductInfoAsync() local success, productInfo = pcall(function() return MarketplaceService:GetProductInfoAsync(PASS_ID, Enum.InfoType.GamePass) end) if success then return productInfo end warn("Failed to get product info:", productInfo) return nil end local function updatePassButton(button) local success, owns = pcall(function() return MarketplaceService:UserOwnsGamePassAsync(player.UserId, PASS_ID) end) if success then if owns then button.Text = "Owned" button.Active = false else button.Text = "Buy Pass" button.Active = true end else button.Text = "Check Failed" end end -- Example button connection local buyButton = script.Parent buyButton.Activated:Connect(function() local success, owns = pcall(function() return MarketplaceService:UserOwnsGamePassAsync(player.UserId, PASS_ID) end) if success and not owns then local promptOk, promptErr = pcall(function() MarketplaceService:PromptGamePassPurchase(player, PASS_ID) end) if not promptOk then warn("Failed to prompt purchase:", promptErr) end elseif success and owns then -- Already owns else warn("Ownership check failed") end end) -- Optional: refresh state after purchase finished (via Remote or on re-join) ``` ### Server side (authoritative granting) The critical part. **Important:** `PromptGamePassPurchaseFinished` fires on both the client and the server. You must grant benefits only in the server handler; the client copy is for UI updates only. ```lua local MarketplaceService = game:GetService("MarketplaceService") local Players = game:GetService("Players") local PASS_ID = 1234567890 -- Replace with your pass ID local granted = {} -- idempotency guard: granted[player][passID] local function grantPassBenefits(player: Player, passID: number) if not player or not player:IsDescendantOf(Players) then return end local playerGranted = granted[player] if not playerGranted then playerGranted = {} granted[player] = playerGranted end if playerGranted[passID] then return end playerGranted[passID] = true -- Apply the benefit. This must be server-only. -- Examples: -- player:SetAttribute("VIP", true) -- YourDataManager:GrantPassPerks(player, PASS_ID) -- Fire a RemoteEvent to client for visual unlock (but don't trust client to apply logic) -- Persist if needed (cross-ref roblox-datastores skill) end -- Handle new purchases MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player: Player, purchasedID: number, wasPurchased: boolean) if not player or typeof(purchasedID) ~= "number" then return end if wasPurchased and purchasedID == PASS_ID then print(player.Name .. " successfully purchased the pass.") local ok, err = pcall(function() grantPassBenefits(player, PASS_ID) end) if not ok then warn("Failed to grant pass benefits:", err) end end end) -- Re-grant on every join (in case of data loss, new server, etc.) Players.PlayerAdded:Connect(function(player: Player) local success, ownsPass = pcall(function() return MarketplaceService:UserOwnsGamePassAsync(player.UserId, PASS_ID) end) if success and ownsPass then local ok, err = pcall(function() grantPassBenefits(player, PASS_ID) end) if not ok then warn("Failed to apply owned pass benefits for", player.Name, err) end elseif not success then warn("Gamepass ownership check failed for", player.Name) -- Optionally retry later or fall back to cached data end end) Players.PlayerRemoving:Connect(function(player: Player) granted[player] = nil end) ``` ## Key rules for the flow - `PromptGamePassPurchase` and initial `UserOwnsGamePassAsync` checks can be on the client for UX. - All actual granting of power/economy/items **must** happen on the server in the `PromptGamePassPurchaseFinished` event or the `PlayerAdded` re-check. - `PromptGamePassPurchaseFinished` fires on the client too; never grant benefits from the client copy. - Always wrap ownership and prompt calls in `pcall`. - `UserOwnsGamePassAsync` will return true for a freshly purchased pass when the player joins a new server after the purchase. - Do not rely solely on the purchase-finished event for players who bought the pass while offline or on another server. - Use an idempotency guard so benefits are not granted multiple times if the event fires more than once. - For developer products, `MarketplaceService.ProcessReceipt` can only be assigned **once** globally. The callback must return `Enum.ProductPurchaseDecision.PurchaseGranted` after successful fulfillment, or `Enum.ProductPurchaseDecision.NotProcessedYet` if fulfillment fails, because Roblox may redeliver the receipt until `PurchaseGranted` is returned. ## Handling purchase failures / edge cases - Network issues during prompt: the `PromptGamePassPurchaseFinished` may fire with `wasPurchased = false`. - Player cancels the prompt. - Insufficient Robux. - The pass is no longer for sale. In the finished handler, only act on `wasPurchased == true`. For UI, you can listen for the finished event on the client too (via a Remote from server) to update "Owned" state immediately without waiting for re-join. ## Integration with data stores After granting in the server handler, immediately save the fact that this player owns the pass (or the specific perks) using your data persistence system. On load (PlayerAdded), prefer the `UserOwnsGamePassAsync` result as the source of truth, then merge with any local saved state. See roblox-datastores skill for safe profile loading patterns. ## Scripts Folder example A simple client helper can live in scripts/PassPurchaseHelper.lua (adapt and require from your UI modules). -
rules-policies-and-security.md 4.8 KB
--- read_when: "Review monetization restrictions, paid random items, or purchase security" last_reviewed: 2026-10-17 --- # Rules, policies, and security for game passes ## Policy changes in 2026 As of **May 30, 2026**, cross-experience game pass and developer product sales are disabled. You can no longer sell a pass or dev product from Experience A inside Experience B. Sales on an experience's own details page (EDP) remain available for passes and developer products owned by that experience. If your game previously relied on cross-experience pass sales (common in donation/tipping games), migrate to experience-specific passes and/or the [Robux Transfers API](https://create.roblox.com/docs/en-us/production/monetization/robux-transfers). `MarketplaceService:PromptRobuxTransferAsync` must be called from the server. Only **Roblox Plus** subscribers can initiate transfers, transfer amounts must be between **10 and 500 Robux**, and the sender cannot equal the receiver. The recipient receives **90%** and the experience earns the other **10%**; Roblox takes no transfer fee. A `BindReceiptHandler` callback must process transfer receipts. Roblox permits items or perks after a successful transfer receipt. Avoiding quid-pro-quo rewards can still be a useful anti-abuse or game-design choice, but it is not a platform requirement. ## What game passes can and cannot do Allowed: - Permanent access (VIP areas, servers) - Permanent unlocks (items, classes, slots) - Cosmetic or quality-of-life upgrades Restricted / Policy-sensitive: - Randomized virtual items / loot boxes (must follow Paid Random Items policy) - Anything that could be seen as gambling without proper disclosures Passes must comply with Roblox Community Standards. ### Paid Random Items policy check Before offering any paid randomized virtual item (loot box, gacha, random crate, etc.), query `PolicyService:GetPolicyInfoForPlayerAsync(player)` and check: - `ArePaidRandomItemsRestricted`: if true for this user, do not offer paid random items. - `IsPaidItemTradingAllowed`: if false, do not allow paid item trading. Respecting these flags per-player is required by Roblox policy. ## Capability requirements Do not apply a blanket "API Services required" rule to all `MarketplaceService` purchase APIs. Publish the experience where the relevant product or transfer API requires it, and check that API's current Engine Reference requirements. Enable **Studio Access to API Services** only for APIs that require it and only in a dedicated test experience. ## Security rules (critical) 1. **Prompt on client only.** Never call `PromptGamePassPurchase` from the server in response to untrusted client input. 2. **Grant on server only.** The `PromptGamePassPurchaseFinished` handler (and `PlayerAdded` re-check) are the only places that should mutate player state based on pass ownership. 3. **Always re-verify.** Even after a successful purchase event, call `UserOwnsGamePassAsync` on the next join. Do not cache the "just purchased" state forever. 4. **pcall everything.** Marketplace calls can fail due to network, throttling, or player actions. 5. **Do not trust client signals.** A RemoteEvent saying "I just bought the pass" must be ignored for granting purposes. ## Data persistence integration When a player owns a pass, you typically want to: - Apply runtime benefits (attributes, speed multipliers, access flags) - Persist the ownership or derived perks in your DataStore profile Recommended: Treat `UserOwnsGamePassAsync` as the source of truth on load. Use your DataStore only for additional pass-related custom data (e.g. "unlocked skin variants for this pass"). See roblox-datastores skill for proper loading/saving patterns around this. ## Right to be forgotten (RTBF) If you implement automated data deletion for user requests, include any keys that store pass-related custom data. Use the Data Stores Manager or Open Cloud to inspect/delete when needed. ## Testing and compliance - Test the full ownership flow on a test experience. - Verify that players who buy the pass while in one server see the benefit when they join a different server. - Make sure "Already owns" states are shown correctly so players aren't prompted again. - Document what the pass actually gives (in description and in-game UI) to avoid support tickets and policy issues. ## Common violations to avoid - Granting benefits based only on a client Remote. - Selling the same permanent benefit as both a game pass and a developer product (causes confusion and potential policy problems). - Hardcoding Robux prices in UI (breaks regional pricing and optimizations). - Using passes for temporary effects that should be developer products. Treat these as a starting checklist, then re-check current policy, threat-model the experience, and test each purchase/receipt path. This guidance cannot guarantee security or policy compliance. -
subscriptions.md 12 KB
--- read_when: "Create recurring benefits or handle subscription status and renewals" last_reviewed: 2026-06-17 --- # Subscriptions **Official source:** https://create.roblox.com/docs/en-us/production/monetization/subscriptions Subscriptions offer users recurring benefits for a monthly fee. Unlike [passes](purchase-flow-and-granting.md), whose benefits are granted indefinitely, subscription benefits persist only while the user keeps paying. Subscriptions are managed through `MarketplaceService` and the Creator Dashboard. ## When to use subscriptions vs passes vs developer products - **Pass**: one-time permanent unlock (VIP, permanent item). - **Developer Product**: repeatable/consumable (currency pack, potion, revive). - **Subscription**: recurring monthly benefit (monthly cosmetic bundle, ongoing XP boost, VIP-tier perks that should gate while unpaid). ## Characteristics - **Auto-renewing**, not one-time. Priced in Robux **or** local currency. - **Single-tiered:** multiple subscriptions in the same experience can be owned simultaneously; mutually exclusive "Bronze/Silver/Gold" tiering of the *same* benefit set is **not** supported. - **Regional Pricing** is enabled by default for Robux-priced subscriptions and cannot be turned off. Not available for local-currency subscriptions. - Up to **50 subscriptions** per experience (active + inactive combined). - Subscriptions are **ineligible for cross-selling** by other experiences and for affiliate fees. ## Robux vs local currency | | Robux | Local currency | | --- | --- | --- | | Eligibility | All creators | Requires ID- or phone-verified account | | Platforms | All platforms | Web, App Store, Google Play | | Countries | All Roblox-supported | Excludes Argentina, China, Colombia, India, Indonesia, Japan, Russia, Taiwan, Türkiye, UAE, Ukraine, Vietnam | | Price | Any amount ≥ 49 Robux | One of $2.99 / $4.99 / $7.99 / $9.99 / $14.99 | | Regional Pricing | Enabled by default | Unavailable | | Payout | 70% of subscription value each month | 70% first month, 100% thereafter | | Refunds | Not eligible | Eligible within the 30-day hold window | Local-currency earnings follow a 30-day hold; Robux-priced earnings follow the standard ~5-day hold (same as passes/products). ## Product types When creating, choose one: - **Durable**: permanent items that persist after acquisition (e.g. a weapon). If a bundle mixes durable + consumable, choose Durable. - **Consumable**: temporary, re-purchasable, expires after use (e.g. a potion that grants a temporary boost). - **Currency**: an in-experience medium of exchange. You **cannot** change the product type after creation. The price of a Robux subscription can be changed only **once every 60 days**, and price increases require Roblox to give users ≥30 days' notice. Local-currency subscription prices cannot be changed. Delete and recreate to change price. ## Creating and activating 1. Creator Dashboard → your experience → Monetization → Subscriptions → Create Subscription. 2. Upload cover image, unique name, clear description. 3. Pick payment option (Robux ≥49, or one of the local-currency tiers). 4. Pick product type (Durable / Consumable / Currency). 5. Create. 6. To put it up for sale: ⋮ → Activate. Active subscriptions appear on the experience's Store tab. Before first activation you must confirm a **shortened experience name**. This is permanent and cannot be changed, and it appears alongside the subscription name at purchase time. It does **not** change your experience's name on Roblox. ## Subscription states - **Active**: available for sale; subscribers can renew at the start of the next period. - **Inactive**: unavailable for sale. To take off sale: ⋯ → Take Off Sale. You can either let existing subscribers renew, or cancel future renewals. If you're not removing the benefits permanently, let subscribers renew. ## Deleting Deleting an active subscription triggers **full refunds for active subscribers** and **zero Robux for you**. Prefer: take off sale → cancel renewals → wait out the period → then delete. Deleting a local-currency subscription requires refunding all current subscribers (Robux subscriptions are not refundable). Deletion requires the last four digits of the subscription ID for confirmation. ## API surface Subscription IDs are **strings** like `"EXP-11111111"`, not numbers. | Method | Side | Purpose | | --- | --- | --- | | `MarketplaceService:GetUserSubscriptionStatusAsync(player, subscriptionId)` | Server | Returns `{IsSubscribed: boolean}` (plus internal fields). **Server-only.** | | `MarketplaceService:PromptSubscriptionPurchase(player, subscriptionId)` | Client | Prompts the user to purchase. | | `MarketplaceService.PromptSubscriptionPurchaseFinished` | Both | `(player, subscriptionId, didTryPurchasing)`. Fires after the prompt closes. | | `Players.UserSubscriptionStatusChanged` | Server | `(player, subscriptionId)`. Fires on purchase, renewal, cancellation. **Server-only.** | | `MarketplaceService:GetSubscriptionProductInfoAsync(subscriptionId)` | Server | Returns product info, including whether priced in Robux or local currency. | | `MarketplaceService:GetUserSubscriptionPaymentHistoryAsync(player, subscriptionId)` | Server | Returns the user's payment history for the subscription. | ### Checking status (server) ```lua local MarketplaceService = game:GetService("MarketplaceService") local Players = game:GetService("Players") local SUBSCRIPTION_ID = "EXP-11111111" -- Replace with your subscription ID local function grantAward(player: Player) -- Grant the subscription benefit here (server-only, source of truth) end local function revokeAwardIfGranted(player: Player) -- Called for players who do NOT have the subscription. -- If you persist subscription state to DataStores, undo it here. end local function checkSubStatus(player: Player) local success, response = pcall(function() return MarketplaceService:GetUserSubscriptionStatusAsync(player, SUBSCRIPTION_ID) end) if not success then warn(`Error while checking subscription: {response}`) return end if response.IsSubscribed then grantAward(player) else revokeAwardIfGranted(player) end end local function onUserSubscriptionStatusChanged(player: Player, subscriptionId: string) if subscriptionId == SUBSCRIPTION_ID then checkSubStatus(player) end end Players.PlayerAdded:Connect(checkSubStatus) Players.UserSubscriptionStatusChanged:Connect(onUserSubscriptionStatusChanged) ``` ### Prompting a purchase (client) `PromptSubscriptionPurchaseFinished` fires with `didTryPurchasing` (note: this indicates the user *attempted* to purchase, not that it succeeded). Subscription registration can take time, so re-check status ~10 seconds after the prompt closes. ```lua local MarketplaceService = game:GetService("MarketplaceService") local Players = game:GetService("Players") local SUBSCRIPTION_ID = "EXP-11111111" local purchaseButton = script.Parent.PromptPurchaseSubscription -- your button local function playerHasSubscription() local success, result = pcall(function() return MarketplaceService:GetUserSubscriptionStatusAsync(Players.LocalPlayer, SUBSCRIPTION_ID) end) if not success then return false end return result.IsSubscribed end local function hideButtonIfPlayerHasSubscription() if playerHasSubscription() then purchaseButton.Visible = false end end local function onPromptSubscriptionPurchaseFinished(player: Player, subscriptionId: string, didTryPurchasing: boolean) if didTryPurchasing then task.delay(10, hideButtonIfPlayerHasSubscription) end end hideButtonIfPlayerHasSubscription() purchaseButton.Activated:Connect(function() MarketplaceService:PromptSubscriptionPurchase(Players.LocalPlayer, SUBSCRIPTION_ID) hideButtonIfPlayerHasSubscription() end) MarketplaceService.PromptSubscriptionPurchaseFinished:Connect(onPromptSubscriptionPurchaseFinished) ``` ### Secure client→server status fetch (RemoteFunction) `GetUserSubscriptionStatusAsync` is server-only. To let the client query status, expose a `RemoteFunction`: ```lua -- Server local ReplicatedStorage = game:GetService("ReplicatedStorage") local MarketplaceService = game:GetService("MarketplaceService") local getSubscriptionStatusRemote = Instance.new("RemoteFunction") getSubscriptionStatusRemote.Name = "GetSubscriptionStatus" getSubscriptionStatusRemote.Parent = ReplicatedStorage getSubscriptionStatusRemote.OnServerInvoke = function(player: Player, subscriptionId: string) assert(typeof(subscriptionId) == "string") return MarketplaceService:GetUserSubscriptionStatusAsync(player, subscriptionId) end ``` ```lua -- Client local ReplicatedStorage = game:GetService("ReplicatedStorage") local getSubscriptionStatusRemote = ReplicatedStorage:WaitForChild("GetSubscriptionStatus") local function playerHasSubscription(subscriptionId: string) local success, result = pcall(function() return getSubscriptionStatusRemote:InvokeServer(subscriptionId) end) if not success then return false end return result.IsSubscribed end ``` ## Replacing a pass with a subscription When migrating, existing pass holders must keep the benefit they paid for; take the pass off sale so new users buy the subscription. Subscription benefits can be revoked (pass benefits cannot), so if you previously persisted pass benefits to a DataStore you must "undo" them when the subscription lapses. Listen for both `PromptGamePassPurchaseFinished` (legacy) and `UserSubscriptionStatusChanged` (new). ## Security rules (critical) - **Prompt on client, check status on server.** `GetUserSubscriptionStatusAsync` is server-only by design. - **Grant/revoke on server only**, in `UserSubscriptionStatusChanged` and the `PlayerAdded` re-check. - **Always pcall** `GetUserSubscriptionStatusAsync`, `PromptSubscriptionPurchase`, and the remote fetch. - **Re-check on every join.** Don't trust a cached "subscribed" state forever; subscriptions lapse. - **Persist nothing sensitive on the client.** Use your DataStore profile (see roblox-datastores) for any subscription-derived state. - **Respect region/platform eligibility.** Only offer subscriptions in supported regions and platforms. `PolicyService:IsEligibleToPurchaseSubscription` tells you whether the player can buy (see roblox-gamepasses PolicyService reference). - **Idempotency guard** your grant/revoke so duplicate `UserSubscriptionStatusChanged` events don't double-apply. ## Analytics Creator Dashboard → Monetization → Subscriptions → Analytics tab tracks: - **Subscriptions** (total active), **Estimated revenue** (net of fees). - **Subscriber breakdown**: New / Renewed / Resurrected (previously canceled). - **Cancellations** (not the same as refunds; canceled = won't renew but paid in full for the cycle). - **Subscriptions by platform** and **Platform earnings**. Real-time subscription events (cancelled, purchased, refunded, renewed) are also available via **Open Cloud webhooks** (see roblox-open-cloud skill). ## Gotchas - `PromptSubscriptionPurchaseFinished`'s `didTryPurchasing` is **not** a success signal. Re-check status after a delay. - Subscription registration can lag; a 10-second delay before re-checking is the documented pattern. - Local-currency refunds within the hold window cancel the payout; outside the window they deduct from your Robux balance (and from the group owner's balance if the group can't cover it). - Changing a Robux subscription's price is rate-limited to once per 60 days; local-currency prices are immutable. - Shortened experience name is permanent: set it carefully. - Subscriptions don't support cross-experience selling or affiliate fees. ## Sources - https://create.roblox.com/docs/en-us/production/monetization/subscriptions - https://create.roblox.com/docs/en-us/reference/engine/classes/MarketplaceService (subscription methods) - https://create.roblox.com/docs/en-us/reference/engine/classes/Players (`UserSubscriptionStatusChanged`) - https://create.roblox.com/docs/en-us/cloud/webhooks/webhook-notifications (subscription webhook events)
-
-
scripts
-
PassPurchaseHelper.lua 6.8 KB · in bundle
-
-
SKILL.md 12.7 KB
--- name: roblox-gamepasses description: "Implement Roblox game passes, developer products, subscriptions, and Robux transfers. Use for purchase prompts, server-side fulfillment, ownership checks, recurring benefits, and monetization policy." last_reviewed: 2026-07-16 --- # roblox-gamepasses **Primary official source:** https://create.roblox.com/docs/en-us/production/monetization/passes (plus developer-products.md for contrast, and the MarketplaceService class reference). Grant benefits on the server after verifying the purchase or ownership. Keep purchase prompts and shop display on the client. See roblox-datastores for how to store "player owns this pass" state or associated perks, and roblox-networking for the Remote validation layer around purchase prompts. ## Game passes and developer products - **Game Pass**: One-time purchase. Permanent privilege for that specific experience (VIP access, permanent item, extra slot, cosmetic unlock, etc.). Roblox tracks ownership per user per experience. - **Developer Product**: Repeatable / consumable (currency packs, potions, revives, temporary boosts). Can be bought many times. Requires ProcessReceipt callback on the server for fulfillment. - As of **May 30, 2026**, cross-experience game pass and developer product sales are disabled. Design experience-specific passes or use the [Robux Transfers API](https://create.roblox.com/docs/en-us/production/monetization/robux-transfers) for donation-style flows. `MarketplaceService:PromptRobuxTransferAsync` must be called from the server. Only **Roblox Plus** subscribers can initiate transfers, transfer amounts must be between **10 and 500 Robux**, and the sender cannot equal the receiver. The recipient receives **90%** and the experience earns the other **10%**; Roblox takes no transfer fee. A `BindReceiptHandler` callback must process transfer receipts. Roblox permits an experience to grant items or perks after a successful receipt; choosing not to attach rewards is an opinionated anti-abuse/design recommendation, not a platform rule. - You (the creator) are 100% responsible for actually delivering the benefit. Roblox only handles the transaction and the UserOwnsGamePassAsync query. - Passes can be used for randomized virtual items only if you follow the Paid Random Items policy. ## Creation and asset ID 1. Creator Dashboard → your published experience → Monetization → Passes → Create a Pass. 2. Upload circular-friendly icon (≤512×512, jpg/png/bmp; important content must survive circular crop). 3. Name + description. 4. After creation: hover the pass → ⋯ → Copy Asset ID. This number is the passID you use in all scripts. For external sales on the game page Store tab: go to the pass → Sales → enable "Item for Sale" and set Robux price (1 to 1B). ## Prompt purchases and grant benefits **Client side (LocalScript or UI module; only for prompting and optimistic display):** - Call MarketplaceService:UserOwnsGamePassAsync (pcall) to decide "Buy" vs "Owned" button state. - If not owned, call MarketplaceService:PromptGamePassPurchase(player, passID). **Server side (Script in ServerScriptService; the only place that grants benefits):** ```lua local MarketplaceService = game:GetService("MarketplaceService") local Players = game:GetService("Players") local PASS_ID = 1234567890 local granted = {} -- idempotency guard: granted[player][passID] local function grantPassBenefits(player: Player, passID: number) if not player or not player:IsDescendantOf(Players) then return end local playerGranted = granted[player] if not playerGranted then playerGranted = {} granted[player] = playerGranted end if playerGranted[passID] then return end playerGranted[passID] = true -- Apply the actual privilege (attributes, table entry, DataStore flag, Remote to client for visuals, etc.) -- This is the source of truth. end MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player: Player, purchasedPassID: number, wasPurchased: boolean) -- This event also fires on the client; never grant benefits from the client copy. if not player or typeof(purchasedPassID) ~= "number" then return end if wasPurchased and purchasedPassID == PASS_ID then local ok, err = pcall(function() grantPassBenefits(player, PASS_ID) end) if not ok then warn("Failed to grant pass benefits:", err) end end end) Players.PlayerAdded:Connect(function(player: Player) local success, owns = pcall(function() return MarketplaceService:UserOwnsGamePassAsync(player.UserId, PASS_ID) end) if success and owns then local ok, err = pcall(function() grantPassBenefits(player, PASS_ID) end) if not ok then warn("Failed to apply owned pass benefits:", err) end elseif not success then warn("UserOwnsGamePassAsync failed for", player.Name) end end) Players.PlayerRemoving:Connect(function(player: Player) granted[player] = nil end) ``` **GetProductInfoAsync for dynamic UI (price, name, description, IsForSale):** Use `MarketplaceService:GetProductInfoAsync(id, Enum.InfoType.GamePass)`. Do this on the client for display, but never grant based on the result. The non-async counterpart `GetProductInfo` still exists, but `GetProductInfoAsync` is preferred. ## Capability requirements Game passes and developer products require a published experience. Enable **Studio Access to API Services** only when a specific API used by a dedicated test experience requires it; do not treat that Studio setting as a blanket prerequisite for every `MarketplaceService` purchase API. Verify each API's current requirements in its Engine Reference entry. ## Product recommendations - `MarketplaceService:RankProductsAsync(arrayOfIdentifiers)`: pass a table of up to 50 `{InfoType = Enum.InfoType.GamePass, Id = ...}`. Returns a personalized ranking for the current user as `{ProductIdentifier, ProductInfo}` items. Use sparingly; call once at join. - `MarketplaceService:RecommendTopProductsAsync({Enum.InfoType.GamePass, Enum.InfoType.Product})`: returns up to 50 recommended products the user is likely to engage with. Results usually exclude already-owned items, but verify in your UI. Use sparingly; call once at join. Use these results in a "Recommended for you" section of your shop. Measure whether they improve conversion in your experience. ## Promotions (buy robux page bonus pool) You can opt passes into the promotion pool so that users buying Robux packages may receive the pass for free (contextually relevant to their history). Requirements (from the passes doc): - Unique pass recommended. - If on sale, price between 50 and 800 Robux. - Must have thumbnail. - Must comply with Community Standards. - Cannot be a paid random item. Opt-in via the pass's Promotions tab in the dashboard. ## Analytics and iteration In Creator Dashboard → experience → Monetization → Passes → Analytics tab you get: - Top passes by sales and net Robux. - Time-series graphs. - Attribution for passes acquired via Buy Robux promotions and subsequent joins. Use this data to decide pricing, which perks are compelling, and when to run promotions. ## Security, data, and policy constraints - Prompt only from client. Grant and record only on the server in the PromptGamePassPurchaseFinished handler or on PlayerAdded re-check. - Always pcall `UserOwnsGamePassAsync` and `Prompt...` calls. - Re-check ownership on every relevant join/session start before granting gameplay or economy perks. - Store your own record of ownership + associated state in DataStores if you need history or custom metadata (Roblox does not expose full per-user pass purchase history via the Engine API). - For RTBF / right-to-be-forgotten, include pass-related keys in your deletion patterns (see roblox-datastores skill). - Never hardcode Robux prices in UI that the player sees: use `GetProductInfoAsync` so regional pricing and optimizations work. - Test purchases only on dedicated test experiences. - For paid random items (loot boxes / gacha passes), use `PolicyService` first: check `PolicyService:GetPolicyInfoForPlayerAsync(player).ArePaidRandomItemsRestricted` and `IsPaidItemTradingAllowed` before offering randomized paid content. - For developer products, `MarketplaceService.ProcessReceipt` can only be assigned **once** globally; assign it once in a single server script. The callback must return `Enum.ProductPurchaseDecision.PurchaseGranted` after successful fulfillment, or `Enum.ProductPurchaseDecision.NotProcessedYet` if fulfillment fails, because Roblox may redeliver the receipt until `PurchaseGranted` is returned. - Donation / tipping games using `PromptRobuxTransferAsync` are high-risk for abuse; implement rate limits, alt-account detection, moderation pipelines, and avoid granting in-experience advantages in exchange for transfers. ## Subscriptions (recurring benefits) Subscriptions offer users recurring benefits for a monthly fee, auto-renewing in Robux or local currency. Unlike passes (permanent), subscription benefits persist only while the user keeps paying. Up to 50 per experience; single-tiered (no mutually exclusive Bronze/Silver/Gold); regional pricing enabled by default for Robux-priced subs. **API surface** (subscription IDs are **strings** like `"EXP-11111111"`): - `MarketplaceService:GetUserSubscriptionStatusAsync(player, subscriptionId)`: **server-only**, returns `{IsSubscribed: boolean}`. - `MarketplaceService:PromptSubscriptionPurchase(player, subscriptionId)`: client prompt. - `MarketplaceService.PromptSubscriptionPurchaseFinished(player, subscriptionId, didTryPurchasing)`: note `didTryPurchasing` is an *attempt* signal, not success; re-check status after a delay. - `Players.UserSubscriptionStatusChanged(player, subscriptionId)`: **server-only**, fires on purchase/renewal/cancellation. - `MarketplaceService:GetSubscriptionProductInfoAsync(subscriptionId)` and `GetUserSubscriptionPaymentHistoryAsync(player, subscriptionId)`. **Security (same posture as passes):** prompt on client, check status and grant/revoke on server only, pcall everything, re-check on every join (subscriptions lapse), respect `PolicyService:IsEligibleToPurchaseSubscription` per player, persist nothing sensitive on the client. **Payouts:** Robux-priced subs pay 70% each month. Local-currency subs pay 70% first month, 100% thereafter, with a 30-day hold. Robux subs are not refundable; local-currency subs are refundable within the hold window. See [references/subscriptions.md](references/subscriptions.md) for the complete flow, client/server code, migration from passes, and gotchas. ## Creator Rewards (replaces Premium Payouts) As of **July 24, 2025**, Engagement-Based Payouts (formerly "Premium Payouts") and the Creator Affiliate program were **discontinued and replaced by Creator Rewards**. There is no longer a per-Premium-play-minute payout to integrate against. Creator Rewards is a platform program with two payment types. It requires no in-experience integration: - **Daily Engagement Rewards**: 5 Robux per day per Active Spender who spends 10+ minutes in your experience, provided it's one of the first three experiences they visit that day. - **Audience Expansion Rewards**: 35% revenue share on a new/reactivated user's first $100 of qualifying purchases in their first 60 days, attributed via Share Links, direct experience links, or experience-name search. Official source: https://create.roblox.com/docs/en-us/creator-rewards ## When to use game passes vs other monetization - One-time permanent unlock or access → Game Pass. - Repeatable purchase (currency, consumables, temporary power) → Developer Product (with proper ProcessReceipt). - Recurring monthly benefit → Subscription (see references/subscriptions.md; `MarketplaceService:GetUserSubscriptionStatusAsync` etc.). See the developer-products doc for the repeatable flow. ## Scripts - `scripts/PassPurchaseHelper.lua`: a client-side helper for game pass button state, price display, and prompting. <!-- catalog:references:start --> ## Reference index - [creation-and-setup.md](references/creation-and-setup.md): Create a pass, find its asset ID, or configure sales and test access. - [policyservice.md](references/policyservice.md): Gate a feature using per-player monetization or content restrictions. - [purchase-flow-and-granting.md](references/purchase-flow-and-granting.md): Implement pass purchase prompts, ownership checks, and server grants. - [rules-policies-and-security.md](references/rules-policies-and-security.md): Review monetization restrictions, paid random items, or purchase security. - [subscriptions.md](references/subscriptions.md): Create recurring benefits or handle subscription status and renewals. <!-- catalog:references:end -->
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.