MarketplaceService

Show Deprecated
Not Creatable
Service

MarketplaceService is responsible for in-experience transactions. The most notable methods are PromptProductPurchase and PromptPurchase, as well as the callback ProcessReceipt which must be defined so that developer product transactions do not fail.

MarketplaceService also has methods that fetch information about developer products (GetProductInfo and GetDeveloperProductsAsync), passes (UserOwnsGamePassAsync), and other assets (PlayerOwnsAsset, PlayerOwnsBundle).

Understanding MarketplaceService is the first step towards learning to monetize an experience on Roblox, as well as learning to use DataStoreService, which is responsible for saving and loading all data related to purchases.

Summary

Methods

Events

Callbacks

Properties

Methods

GetDeveloperProductsAsync

Yields

Returns

Code Samples

MarketplaceService:GetDeveloperProductsAsync

local MarketplaceService = game:GetService("MarketplaceService")
local developerProducts = MarketplaceService:GetDeveloperProductsAsync():GetCurrentPage()
for _, developerProduct in pairs(developerProducts) do
for field, value in pairs(developerProduct) do
print(field .. ": " .. value)
end
print(" ")
end

GetProductInfo

Yields

Parameters

assetId: number
infoType: Enum.InfoType
Default Value: "Asset"

Returns

Code Samples

Getting Product Info

local MarketplaceService = game:GetService("MarketplaceService")
local ASSET_ID = 125378389
local asset = MarketplaceService:GetProductInfo(ASSET_ID)
print(asset.Name .. " :: " .. asset.Description)

GetSubscriptionProductInfoAsync

Yields

Parameters

subscriptionId: string

Returns

GetUserSubscriptionDetailsAsync

Yields

Parameters

user: Player
subscriptionId: string

Returns

GetUserSubscriptionPaymentHistoryAsync

Yields

Parameters

user: Player
subscriptionId: string

Returns

Code Samples

MarketplaceService:GetUserSubscriptionPaymentHistoryAsync

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local SUBSCRIPTION_ID = "EXP-0"
local function checkSubscriptionHistory(player: Player)
local subscriptionHistory = {}
local success, err = pcall(function()
subscriptionHistory = MarketplaceService:GetUserSubscriptionPaymentHistoryAsync(player, SUBSCRIPTION_ID)
end)
if not success then
warn(`Error while checking subscription history: {err}`)
return
end
if next(subscriptionHistory) then
-- User has some subscription history within the past 12 months.
-- Print the details of each payment entry from the subscription history.
print(`Player {player.Name} has subscribed to {SUBSCRIPTION_ID} before:`)
for entryNum, paymentEntry in subscriptionHistory do
local paymentStatus = tostring(paymentEntry.PaymentStatus)
local cycleStartTime = paymentEntry.CycleStartTime:FormatLocalTime("LLL", "en-us")
local cycleEndTime = paymentEntry.CycleEndTime:FormatLocalTime("LLL", "en-us")
print(`{entryNum}: {paymentStatus} ({cycleStartTime} - {cycleEndTime})`)
end
else
print(`Player {player.Name} has never subscribed to {SUBSCRIPTION_ID} before.`)
end
end
-- Call checkSubscriptionHistory for any players already in the game
for _, player in ipairs(Players:GetPlayers()) do
checkSubscriptionHistory(player)
end
-- Call checkSubscriptionHistory for all future players
Players.PlayerAdded:Connect(checkSubscriptionHistory)

GetUserSubscriptionStatusAsync

Yields

Parameters

user: Player
subscriptionId: string

Returns

Code Samples

Check User Subscription Status

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local subscriptionID = "EXP-00000"
local function checkSubStatus(player)
local subStatus = {}
local success, message = pcall(function()
-- returns IsRenewing and IsSubscribed
subStatus = MarketplaceService:GetUserSubscriptionStatusAsync(player, subscriptionID)
end)
if not success then
warn("Error while checking if player has subscription: " .. tostring(message))
return
end
if subStatus["IsSubscribed"] then
print(player.Name .. " is subscribed with " .. subscriptionID)
-- Give player permissions associated with the subscription
end
end
Players.PlayerAdded:Connect(checkSubStatus)

GetUsersPriceLevelsAsync

Yields

Parameters

userIds: Array

Returns

PlayerOwnsAsset

Yields

Parameters

player: Instance
assetId: number

Returns

Code Samples

Check for Item Ownership

local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")
-- The item we're checking for: https://www.roblox.com/catalog/30331986/Midnight-Shades
local ASSET_ID = 30331986
local ASSET_NAME = "Midnight Shades"
local function onPlayerAdded(player)
local success, doesPlayerOwnAsset = pcall(MarketplaceService.PlayerOwnsAsset, MarketplaceService, player, ASSET_ID)
if not success then
local errorMessage = doesPlayerOwnAsset
warn(`Error checking if {player.Name} owns {ASSET_NAME}: {errorMessage}`)
return
end
if doesPlayerOwnAsset then
print(`{player.Name} owns {ASSET_NAME}`)
else
print(`{player.Name} doesn't own {ASSET_NAME}`)
end
end
Players.PlayerAdded:Connect(onPlayerAdded)

PlayerOwnsBundle

Yields

Parameters

player: Player
bundleId: number

Returns

Code Samples

Check for Bundle Ownership

local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")
-- The bundle we're checking for: https://www.roblox.com/bundles/589/Junkbot
local BUNDLE_ID = 589
local BUNDLE_NAME = "Junkbot"
Players.PlayerAdded:Connect(function(player)
local success, doesPlayerOwnBundle = pcall(function()
return MarketplaceService:PlayerOwnsBundle(player, BUNDLE_ID)
end)
if success == false then
print("PlayerOwnsBundle call failed: ", doesPlayerOwnBundle)
return
end
if doesPlayerOwnBundle then
print(player.Name .. " owns " .. BUNDLE_NAME)
else
print(player.Name .. " doesn't own " .. BUNDLE_NAME)
end
end)

PromptBulkPurchase

()

Parameters

player: Player
lineItems: Array
options: Dictionary

Returns

()

Code Samples

Prompt Bulk Purchase Client

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local promptBulkPurchaseEvent = ReplicatedStorage:WaitForChild("PromptBulkPurchaseEvent")
local part = Instance.new("Part")
part.Parent = workspace
local clickDetector = Instance.new("ClickDetector")
clickDetector.Parent = part
clickDetector.MouseClick:Connect(function()
promptBulkPurchaseEvent:FireServer({
{ Type = Enum.MarketplaceProductType.AvatarAsset, Id = "16630147" },
{ Type = Enum.MarketplaceProductType.AvatarBundle, Id = "182" },
})
end)
Prompt Bulk Purchase Server

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MarketplaceService = game:GetService("MarketplaceService")
local promptBulkPurchaseEvent = Instance.new("RemoteEvent")
promptBulkPurchaseEvent.Name = "PromptBulkPurchaseEvent"
promptBulkPurchaseEvent.Parent = ReplicatedStorage
--Listen for the RemoteEvent to fire from a Client and then trigger the bulk purchase prompt
promptBulkPurchaseEvent.OnServerEvent:Connect(function(player, items)
MarketplaceService:PromptBulkPurchase(player, items, {})
end)

PromptBundlePurchase

()

Parameters

player: Instance
bundleId: number

Returns

()

PromptCancelSubscription

()

Parameters

user: Player
subscriptionId: string

Returns

()

PromptGamePassPurchase

()

Parameters

player: Instance
gamePassId: number

Returns

()

PromptPremiumPurchase

()

Parameters

player: Instance

Returns

()

Code Samples

Prompt Premium Purchase

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local teleporter = script.Parent
local showModal = true
local TELEPORT_POSITION = Vector3.new(1200, 200, 60)
-- Teleport character to exclusive area
local function teleportPlayer(player)
-- Request streaming around target location
player:RequestStreamAroundAsync(TELEPORT_POSITION)
-- Teleport character
local character = player.Character
if character and character.Parent then
local currentPivot = character:GetPivot()
character:PivotTo(currentPivot * CFrame.new(TELEPORT_POSITION))
end
end
-- Detect character parts touching teleporter
teleporter.Touched:Connect(function(otherPart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if not player then
return
end
if not player:GetAttribute("CharacterPartsTouching") then
player:SetAttribute("CharacterPartsTouching", 0)
end
player:SetAttribute("CharacterPartsTouching", player:GetAttribute("CharacterPartsTouching") + 1)
if player.MembershipType == Enum.MembershipType.Premium then
-- User has Premium; teleport character to exclusive area within experience
teleportPlayer(player)
else
-- Show purchase modal, using debounce to show once every few seconds at most
if not showModal then
return
end
showModal = false
task.delay(5, function()
showModal = true
end)
MarketplaceService:PromptPremiumPurchase(player)
end
end)
-- Detect character parts exiting teleporter
teleporter.TouchEnded:Connect(function(otherPart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if player and player:GetAttribute("CharacterPartsTouching") then
player:SetAttribute("CharacterPartsTouching", player:GetAttribute("CharacterPartsTouching") - 1)
end
end)
-- Handle membership changed event
Players.PlayerMembershipChanged:Connect(function(player)
warn("User membership changed; new membership is " .. tostring(player.MembershipType))
-- Teleport character if membership type is Premium and character is on teleporter
if player.MembershipType == Enum.MembershipType.Premium and player:GetAttribute("CharacterPartsTouching") > 0 then
teleportPlayer(player)
end
end)

PromptProductPurchase

()

Parameters

player: Instance
productId: number
equipIfPurchased: boolean
Default Value: true
currencyType: Enum.CurrencyType
Default Value: "Default"

Returns

()

Code Samples

MarketplaceService:PromptProductPurchase

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local productId = 0000000 -- Change this to your developer product ID
-- Function to prompt purchase of the developer product
local function promptPurchase()
MarketplaceService:PromptProductPurchase(player, productId)
end
promptPurchase()

PromptPurchase

()

Parameters

player: Instance
assetId: number
equipIfPurchased: boolean
Default Value: true
currencyType: Enum.CurrencyType
Default Value: "Default"

Returns

()

Code Samples

LocalScript (Client)

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local promptPurchaseEvent = ReplicatedStorage:WaitForChild("PromptPurchaseEvent")
local part = Instance.new("Part")
part.Parent = workspace
local clickDetector = Instance.new("ClickDetector")
clickDetector.Parent = part
clickDetector.MouseClick:Connect(function()
promptPurchaseEvent:FireServer(16630147)
end)
Script (Server)

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MarketplaceService = game:GetService("MarketplaceService")
local promptPurchaseEvent = Instance.new("RemoteEvent")
promptPurchaseEvent.Name = "PromptPurchaseEvent"
promptPurchaseEvent.Parent = ReplicatedStorage
-- Listen for the RemoteEvent to fire from a Client and then trigger the purchase prompt
promptPurchaseEvent.OnServerEvent:Connect(function(player, id)
MarketplaceService:PromptPurchase(player, id)
end)

PromptSubscriptionPurchase

()

Parameters

user: Player
subscriptionId: string

Returns

()

UserOwnsGamePassAsync

Yields

Parameters

userId: number
gamePassId: number

Returns

Events

PromptBulkPurchaseFinished

Parameters


PromptBundlePurchaseFinished

Parameters

player: Instance
bundleId: number
wasPurchased: boolean

PromptGamePassPurchaseFinished

Parameters

player: Instance
gamePassId: number
wasPurchased: boolean

Code Samples

Handling Gamepass Purchase Finished

local MarketplaceService = game:GetService("MarketplaceService")
local function gamepassPurchaseFinished(...)
-- Print all the details of the prompt, for example:
-- PromptGamePassPurchaseFinished PlayerName 123456 false
print("PromptGamePassPurchaseFinished", ...)
end
MarketplaceService.PromptGamePassPurchaseFinished:Connect(gamepassPurchaseFinished)

PromptPremiumPurchaseFinished


PromptProductPurchaseFinished

Parameters

userId: number
productId: number
isPurchased: boolean

PromptPurchaseFinished

Parameters

player: Instance
assetId: number
isPurchased: boolean

Code Samples

Handling PromptPurchaseFinished Event

local MarketplaceService = game:GetService("MarketplaceService")
local function onPromptPurchaseFinished(player, assetId, isPurchased)
if isPurchased then
print(player.Name, "bought an item with AssetID:", assetId)
else
print(player.Name, "didn't buy an item with AssetID:", assetId)
end
end
MarketplaceService.PromptPurchaseFinished:Connect(onPromptPurchaseFinished)

PromptSubscriptionPurchaseFinished

Parameters

user: Player
subscriptionId: string
didTryPurchasing: boolean

Callbacks

Parameters

receiptInfo: Dictionary

Returns

Code Samples

ProcessReceipt Callback

local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
-- Data store setup for tracking purchases that were successfully processed
local purchaseHistoryStore = DataStoreService:GetDataStore("PurchaseHistory")
local productIdByName = {
fullHeal = 123123,
gold100 = 456456,
}
-- A dictionary to look up the handler function to grant a purchase corresponding to a product ID
-- These functions return true if the purchase was granted successfully
-- These functions must never yield since they're called later within an UpdateAsync() callback
local grantPurchaseHandlerByProductId = {
[productIdByName.fullHeal] = function(_receipt, player)
local character = player.Character
local humanoid = character and character:FindFirstChild("Humanoid")
-- Ensure the player has a humanoid to heal
if not humanoid then
return false
end
-- Heal the player to full Health
humanoid.Health = humanoid.MaxHealth
-- Indicate a successful grant
return true
end,
[productIdByName.gold100] = function(_receipt, player)
local leaderstats = player:FindFirstChild("leaderstats")
local goldStat = leaderstats and leaderstats:FindFirstChild("Gold")
if not goldStat then
return false
end
-- Add 100 gold to the player's gold stat
goldStat.Value += 100
-- Indicate a successful grant
return true
end,
}
-- The core ProcessReceipt callback function
-- This implementation handles most failure scenarios but does not completely mitigate cross-server data failure scenarios
local function processReceipt(receiptInfo)
local success, result = pcall(
purchaseHistoryStore.UpdateAsync,
purchaseHistoryStore,
receiptInfo.PurchaseId,
function(isPurchased)
if isPurchased then
-- This purchase was already recorded as granted, so it must have previously been handled
-- Avoid calling the grant purchase handler here to prevent granting the purchase twice
-- While the value in the data store is already true, true is returned again so that the pcall result variable is also true
-- This will later be used to return PurchaseGranted from the receipt processor
return true
end
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
-- Avoids granting the purchase if the player is not in the server
-- When they rejoin, this receipt processor will be called again
return nil
end
local grantPurchaseHandler = grantPurchaseHandlerByProductId[receiptInfo.ProductId]
if not grantPurchaseHandler then
-- If there's no handler defined for this product ID, the purchase cannot be processed
-- This will never happen as long as a handler is set for every product ID sold in the experience
warn(`No purchase handler defined for product ID '{receiptInfo.ProductId}'`)
return nil
end
local handlerSucceeded, handlerResult = pcall(grantPurchaseHandler, receiptInfo, player)
if not handlerSucceeded then
local errorMessage = handlerResult
warn(
`Grant purchase handler errored while processing purchase from '{player.Name}' of product ID '{receiptInfo.ProductId}': {errorMessage}`
)
return nil
end
local didHandlerGrantPurchase = handlerResult == true
if not didHandlerGrantPurchase then
-- The handler did not grant the purchase, so record it as not granted
return nil
end
-- The purchase is now granted to the player, so record it as granted
-- This will later be used to return PurchaseGranted from the receipt processor
return true
end
)
if not success then
local errorMessage = result
warn(`Failed to process receipt due to data store error: {errorMessage}`)
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local didGrantPurchase = result == true
return if didGrantPurchase
then Enum.ProductPurchaseDecision.PurchaseGranted
else Enum.ProductPurchaseDecision.NotProcessedYet
end
-- Set the callback; this can only be done once by one script on the server
MarketplaceService.ProcessReceipt = processReceipt