### Basic Profile Loader Implementation Source: https://madstudioroblox.github.io/ProfileService/tutorial/basic_usage This snippet shows a complete setup for loading player profiles using ProfileService. It includes profile template definition, module requirements, player added/removing event handling, and profile release logic. Ensure ProfileTemplate is defined before game launch to include new values in existing profiles. ```lua -- ProfileTemplate table is what empty profiles will default to. -- Updating the template will not include missing template values -- in existing player profiles! local ProfileTemplate = { Cash = 0, Items = {}, LogInTimes = 0, } ----- Loaded Modules ----- local ProfileService = require(game.ServerScriptService.ProfileService) ----- Private Variables ----- local Players = game:GetService("Players") local ProfileStore = ProfileService.GetProfileStore( "PlayerData", ProfileTemplate ) local Profiles = {} -- [player] = profile ----- Private Functions ----- local function GiveCash(profile, amount) -- If "Cash" was not defined in the ProfileTemplate at game launch, -- you will have to perform the following: if profile.Data.Cash == nil then profile.Data.Cash = 0 end -- Increment the "Cash" value: profile.Data.Cash = profile.Data.Cash + amount end local function DoSomethingWithALoadedProfile(player, profile) profile.Data.LogInTimes = profile.Data.LogInTimes + 1 print(player.Name .. " has logged in " .. tostring(profile.Data.LogInTimes) .. " time" .. ((profile.Data.LogInTimes > 1) and "s" or "")) GiveCash(profile, 100) print(player.Name .. " owns " .. tostring(profile.Data.Cash) .. " now!") end local function PlayerAdded(player) local profile = ProfileStore:LoadProfileAsync("Player_" .. player.UserId) if profile ~= nil then profile:AddUserId(player.UserId) -- GDPR compliance profile:Reconcile() -- Fill in missing variables from ProfileTemplate (optional) profile:ListenToRelease(function() Profiles[player] = nil -- The profile could've been loaded on another Roblox server: player:Kick() end) if player:IsDescendantOf(Players) == true then Profiles[player] = profile -- A profile has been successfully loaded: DoSomethingWithALoadedProfile(player, profile) else -- Player left before the profile loaded: profile:Release() end else -- The profile couldn't be loaded possibly due to other -- Roblox servers trying to load this profile at the same time: player:Kick() end end ----- Initialize ----- -- In case Players have joined the server earlier than this script ran: for _, player in ipairs(Players:GetPlayers()) do task.spawn(PlayerAdded, player) end ----- Connections ----- Players.PlayerAdded:Connect(PlayerAdded) Players.PlayerRemoving:Connect(function(player) local profile = Profiles[player] if profile ~= nil then profile:Release() end end) ``` -------------------------------- ### Typo in Profile Release Source: https://madstudioroblox.github.io/ProfileService/troubleshooting This example demonstrates a common mistake where a typo prevents the profile from being released, potentially causing race conditions. Ensure 'profile:Release()' is spelled correctly. ```lua Players.PlayerRemoving:Connect(function(player) local profile = Profiles[player] if profile ~= nil then progile:Release() -- "progile" IS A TYPO! end end) ``` -------------------------------- ### Get Active Global Updates Source: https://madstudioroblox.github.io/ProfileService/api Retrieves all active global updates for a profile. Use immediately after loading a profile to process and lock pending active updates. ```lua GlobalUpdates:GetActiveUpdates() --> [table] { {update_id, update_data}, ...} Should be used immediately after a `Profile` is loaded to scan and progress any pending `Active` updates to `Locked` state: ``` ```lua for _, update in ipairs(profile.GlobalUpdates:GetActiveUpdates()) do profile.GlobalUpdates:LockActiveUpdate(update[1]) end ``` -------------------------------- ### Get Locked Global Updates Source: https://madstudioroblox.github.io/ProfileService/api Retrieves all locked global updates for a profile. Use immediately after loading a profile to process and clear pending locked updates. ```lua GlobalUpdates:GetLockedUpdates() --> [table] { {update_id, update_data}, ...} Should be used immediately after a `Profile` is loaded to scan and progress any pending `Locked` updates to `Cleared` state: ``` ```lua for _, update in ipairs(profile.GlobalUpdates:GetLockedUpdates()) do local update_id = update[1] local update_data = update[2] if update_data.Type == "AdminGift" and update_data.Item == "Coins" then profile.Data.Coins = profile.Data.Coins + update_data.Amount end profile.GlobalUpdates:ClearLockedUpdate(update_id) end ``` -------------------------------- ### Load Live and Mock Profiles in Parallel Source: https://madstudioroblox.github.io/ProfileService/api Demonstrates loading both a live profile and a mock profile using the same key. Mock profiles are stored separately and are forgotten when the game session ends, making them ideal for testing without affecting live data. ```lua local ProfileTemplate = {} local GameProfileStore = ProfileService.GetProfileStore( "PlayerData", ProfileTemplate ) local LiveProfile = GameProfileStore:LoadProfileAsync( "profile_key", "ForceLoad" ) local MockProfile = GameProfileStore.Mock:LoadProfileAsync( "profile_key", "ForceLoad" ) print(LiveProfile ~= MockProfile) --> true -- When done using mock profile on live servers: (Prevent memory leak) MockProfile:Release() GameProfileStore.Mock:WipeProfileAsync("profile_key") -- You don't really have to wipe mock profiles in studio testing ``` -------------------------------- ### Initialize Player Added and Marketplace Service Source: https://madstudioroblox.github.io/ProfileService/tutorial/developer_products Initializes the PlayerAdded function for all existing players and sets up the MarketplaceService.ProcessReceipt handler. ```lua for _, player in ipairs(Players:GetPlayers()) do task.spawn(PlayerAdded, player) end MarketplaceService.ProcessReceipt = ProcessReceipt ``` -------------------------------- ### Profile.KeyInfo Source: https://madstudioroblox.github.io/ProfileService/api Provides access to the `DataStoreKeyInfo` instance related to the profile. ```APIDOC ## Profile.KeyInfo ### Description The DataStoreKeyInfo (Official documentation) instance related to this profile. ### Parameters None ### Request Example ```json { "example": "" } ``` ### Response #### Success Response (200) - **KeyInfo** (DataStoreKeyInfo) - The DataStoreKeyInfo instance. #### Response Example ```json { "example": "" } ``` ``` -------------------------------- ### Developer Product Purchase Handling Source: https://madstudioroblox.github.io/ProfileService/tutorial/developer_products This script configures and handles developer product purchases within the ProcessReceipt callback. It defines product mappings and manages purchase IDs to ensure reliable transaction processing. ```lua local SETTINGS = { ProfileTemplate = { Cash = 0, }, Products = { -- developer_product_id = function(profile) [97662780] = function(profile) profile.Data.Cash += 100 end, [97663121] = function(profile) profile.Data.Cash += 1000 end, }, PurchaseIdLog = 50, -- Store this amount of purchase id's in MetaTags; -- This value must be reasonably big enough so the player would not be able -- to purchase products faster than individual purchases can be confirmed. -- Anything beyond 30 should be good enough. } ----- Loaded Modules ----- local ProfileService = require(game.ServerScriptService.ProfileService) ----- Private Variables ----- local Players = game:GetService("Players") local MarketplaceService = game:GetService("MarketplaceService") local GameProfileStore = ProfileService.GetProfileStore( "PlayerData", SETTINGS.ProfileTemplate ) local Profiles = {} -- {player = profile, ...} ----- Private Functions ----- local function PlayerAdded(player) local profile = GameProfileStore:LoadProfileAsync("Player_" .. player.UserId) if profile ~= nil then profile:AddUserId(player.UserId) -- GDPR compliance profile:Reconcile() -- Fill in missing variables from ProfileTemplate (optional) profile:ListenToRelease(function() Profiles[player] = nil player:Kick() -- The profile could've been loaded on another Roblox server end) if player:IsDescendantOf(Players) == true then Profiles[player] = profile else profile:Release() -- Player left before the profile loaded end else -- The profile couldn't be loaded possibly due to other -- Roblox servers trying to load this profile at the same time: player:Kick() end end function PurchaseIdCheckAsync(profile, purchase_id, grant_product_callback) --> Enum.ProductPurchaseDecision -- Yields until the purchase_id is confirmed to be saved to the profile or the profile is released if profile:IsActive() ~= true then return Enum.ProductPurchaseDecision.NotProcessedYet else local meta_data = profile.MetaData local local_purchase_ids = meta_data.MetaTags.ProfilePurchaseIds if local_purchase_ids == nil then local_purchase_ids = {} meta_data.MetaTags.ProfilePurchaseIds = local_purchase_ids end -- Granting product if not received: if table.find(local_purchase_ids, purchase_id) == nil then while #local_purchase_ids >= SETTINGS.PurchaseIdLog do table.remove(local_purchase_ids, 1) end table.insert(local_purchase_ids, purchase_id) task.spawn(grant_product_callback) end -- Waiting until the purchase is confirmed to be saved: local result = nil local function check_latest_meta_tags() local saved_purchase_ids = meta_data.MetaTagsLatest.ProfilePurchaseIds if saved_purchase_ids ~= nil and table.find(saved_purchase_ids, purchase_id) ~= nil then result = Enum.ProductPurchaseDecision.PurchaseGranted end end check_latest_meta_tags() local meta_tags_connection = profile.MetaTagsUpdated:Connect(function() check_latest_meta_tags() -- When MetaTagsUpdated fires after profile release: if profile:IsActive() == false and result == nil then result = Enum.ProductPurchaseDecision.NotProcessedYet end end) while result == nil do task.wait() end meta_tags_connection:Disconnect() return result end end local function GetPlayerProfileAsync(player) --> [Profile] / nil -- Yields until a Profile linked to a player is loaded or the player leaves local profile = Profiles[player] while profile == nil and player:IsDescendantOf(Players) == true do task.wait() profile = Profiles[player] end return profile end local function GrantProduct(player, product_id) -- We shouldn't yield during the product granting process! local profile = Profiles[player] local product_function = SETTINGS.Products[product_id] if product_function ~= nil then product_function(profile) else warn("ProductId " .. tostring(product_id) .. " has not been defined in Products table") end end local function ProcessReceipt(receipt_info) local player = Players:GetPlayerByUserId(receipt_info.PlayerId) ``` -------------------------------- ### Profile:ListenToRelease() Source: https://madstudioroblox.github.io/ProfileService/api Subscribes a listener function to be called when the profile is released, either locally or remotely. ```APIDOC ## Profile:ListenToRelease(listener) ### Description Listener functions subscribed to `Profile:ListenToRelease()` will be called when the profile is released remotely (Being `"ForceLoad"`'ed on a remote server) or locally (`Profile:Release()`). In common practice, the profile will rarely be released before the player leaves the game so it's recommended to simply :Kick() the Player when this happens. Warning After `Profile:ListenToRelease()` is triggered, it is too late to change `Profile.Data` for the final time. As long as the profile is active (`Profile:IsActive()` == `true`), you should store all profile related data immediately after it becomes available. An item trading operation between two profiles must happen without any yielding after it is confirmed that both profiles are active. ### Parameters #### Path Parameters - **listener** (function) - Required - The function to be called when the profile is released. ### Request Example ```json { "example": "" } ``` ### Response #### Success Response (200) - **ScriptConnection** (ScriptConnection) - A connection object for the listener. - **place_id / nil** (number/nil) - The place ID where the profile was released. - **game_job_id / nil** (string/nil) - The game job ID where the profile was released. #### Response Example ```json { "example": "" } ``` ``` -------------------------------- ### Profile:ListenToHopReady() Source: https://madstudioroblox.github.io/ProfileService/api Listens for the profile to be ready after a hop (teleport). The listener function is executed after the releasing UpdateAsync call finishes, typically within a second but potentially up to 7 seconds. ```APIDOC ## Profile:ListenToHopReady() ### Description Listens for the profile to be ready after a hop (teleport). The listener function is executed after the releasing UpdateAsync call finishes. ### Method Profile:ListenToHopReady(listener) ### Parameters #### Path Parameters - **listener** (function) - The function to be called when the profile is ready. ### Response Returns a ScriptConnection object. ### Request Example ```lua local TeleportService = game:GetService("TeleportService") local profile, player, place_id profile:Release() profile:ListenToHopReady(function() TeleportService:TeleportAsync(place_id, {player}) end) ``` ``` -------------------------------- ### ProfileStore.Mock API Source: https://madstudioroblox.github.io/ProfileService/api Provides a mock implementation of ProfileStore methods for testing purposes. It operates on a separate, detached 'fake' DataStore that is forgotten at the end of the game session, preventing interference with live data. ```APIDOC ## ProfileStore.Mock ### Description `ProfileStore.Mock` offers a mirrored set of methods from `ProfileStore` but operates on an isolated, temporary data store. This is ideal for testing environments where you need to simulate ProfileService functionality without affecting live user data. ### Key Features - **Isolation**: Mock profiles are completely separate from live profiles, even when using the same `ProfileStore` instance. - **Testing**: Enables testing of ProfileService features in Studio by simulating API services without writing to live DataStores. - **Lifecycle**: Mock data is temporary and is discarded when the game session ends. ### Example Usage ```lua local ProfileTemplate = {} local GameProfileStore = ProfileService.GetProfileStore("PlayerData", ProfileTemplate) -- Load a mock profile local MockProfile = GameProfileStore.Mock:LoadProfileAsync("profile_key", "ForceLoad") -- Perform operations on the mock profile... -- Release the mock profile when done MockProfile:Release() GameProfileStore.Mock:WipeProfileAsync("profile_key") ``` ### Studio Integration Example ```lua local RunService = game:GetService("RunService") local GameProfileStore = ProfileService.GetProfileStore("PlayerData", ProfileTemplate) if RunService:IsStudio() == true then GameProfileStore = GameProfileStore.Mock end ``` ### Important Considerations - `ProfileStore` and `ProfileStore.Mock` are entirely independent and should be treated as distinct objects. - It is possible to use both live and mock profiles concurrently on live servers, provided careful management. ``` -------------------------------- ### Identify Profile Source: https://madstudioroblox.github.io/ProfileService/api Returns a string containing the DataStore name, scope, and key for debugging purposes. ```lua Profile:Identify() --> [string] -- Example return: "[Store:\"GameData\";Scope:\"Live\";Key:\"Player_2312310\"]" ``` -------------------------------- ### Load Profile with Default Handler Source: https://madstudioroblox.github.io/ProfileService/api Use nil for the not_released_handler for basic profile loading. This will default to 'ForceLoad'. ```lua ProfileStore:LoadProfileAsync( profile_key, nil ) --> [Profile] or nil ``` -------------------------------- ### Profile:Identify() Source: https://madstudioroblox.github.io/ProfileService/api Returns a string containing the DataStore name, scope, and key for debugging purposes. ```APIDOC ## Profile:Identify() ### Description Returns a string identifying the profile's DataStore, scope, and key for debugging. ### Method Profile:Identify() ### Response Returns a string in the format: "[Store:"GameData";Scope:"Live";Key:"Player_2312310"] ### Response Example ```lua print(profile:Identify()) -- Output: "[Store:"GameData";Scope:"Live";Key:"Player_2312310"]" ``` ``` -------------------------------- ### Create Profile Version Query Source: https://madstudioroblox.github.io/ProfileService/api Creates a profile version query. Results are retrieved through ProfileVersionQuery:Next(). Date definitions are easier with the DateTime library. ```lua ProfileStore:ProfileVersionQuery(profile_key, sort_direction, min_date, max_date) --> [ProfileVersionQuery] -- profile_key [string] -- sort_direction nil or [Enum.SortDirection] -- Defaults to "Ascending" -- min_date nil or [DateTime] or [number] (epoch time millis) -- max_date nil or [DateTime] or [number] (epoch time millis) ``` -------------------------------- ### Convert Userdata to Tables for Storage Source: https://madstudioroblox.github.io/ProfileService/troubleshooting Shows how to manually convert userdata types like Vector3 into tables of numbers for storage. This is necessary because userdata cannot be directly serialized by DataStores. ```lua Profile.Data = {LastPosition = {position.X, position.Y, position.Z} } ``` -------------------------------- ### Conditionally Use Mock Profile in Studio Source: https://madstudioroblox.github.io/ProfileService/api This snippet shows how to conditionally assign the mock profile store when running in a Studio environment. This allows for testing API services without saving to live DataStore keys. ```lua local RunService = game:GetService("RunService") local GameProfileStore = ProfileService.GetProfileStore("PlayerData", ProfileTemplate) if RunService:IsStudio() == true then GameProfileStore = GameProfileStore.Mock end ``` -------------------------------- ### ProfileService.GetProfileStore() Method Source: https://madstudioroblox.github.io/ProfileService/api Retrieves a ProfileStore object, analogous to Roblox's DataStoreService :GetDataStore(). It accepts a profile store index (name or table with name/scope) and an optional profile template. ```lua ProfileService.GetProfileStore( profile_store_index, profile_template ) --> [ProfileStore] -- profile_store_index [string] -- DataStore name -- OR -- profile_store_index [table]: -- Allows the developer to define more GlobalDataStore variables -- { -- Name = "StoreName", -- [string] -- DataStore name -- -- Optional arguments: -- Scope = "StoreScope", -- [string] -- DataStore scope -- } -- profile_template [table] -- Profile.Data will default to -- given table (deep-copy) when no data was saved previously ``` -------------------------------- ### ProfileStore:ProfileVersionQuery() Source: https://madstudioroblox.github.io/ProfileService/api Creates a profile version query to retrieve historical versions of a player's profile. Results can be iterated using the :Next() method. ```APIDOC ## ProfileStore:ProfileVersionQuery() ### Description Creates a profile version query using DataStore:ListVersionsAsync(). Results are retrieved through `ProfileVersionQuery:Next()`. This method is useful for rolling back profile data or analyzing data mutations over time. ### Method `ProfileStore:ProfileVersionQuery(profile_key, sort_direction, min_date, max_date)` ### Parameters #### Path Parameters - **profile_key** (string) - Required - The unique identifier for the profile to query. - **sort_direction** (Enum.SortDirection or nil) - Optional - Defaults to `Enum.SortDirection.Ascending`. Specifies the order of results. Use `Enum.SortDirection.Descending` to get the most recent versions first. - **min_date** (DateTime or number or nil) - Optional - Filters results to include versions created after this date/time. Can be a `DateTime` object or a Unix timestamp in milliseconds. - **max_date** (DateTime or number or nil) - Optional - Filters results to include versions created before this date/time. Can be a `DateTime` object or a Unix timestamp in milliseconds. ### Request Example ```lua -- Example: Querying for the most recent profile version before a specific date local ProfileStore = ProfileService.GetProfileStore(store_name, template) local max_date = DateTime.fromUniversalTime(2021, 08, 13) -- UTC August 13th, 2021 local query = ProfileStore:ProfileVersionQuery( "Player_2312310", Enum.SortDirection.Descending, nil, max_date ) local profile = query:NextAsync() if profile ~= nil then print("Found profile version:", profile.KeyInfo.UpdatedTime) else print("No profile version found matching the criteria.") end ``` ### Response #### Success Response (ProfileVersionQuery Object) - **ProfileVersionQuery** - An object that allows iteration through profile versions using the `:NextAsync()` method. #### Response Example ```lua -- The query object itself is returned, not a direct profile. -- Use query:NextAsync() to retrieve profile data. local query = ProfileStore:ProfileVersionQuery(...) -- query now holds the query object ``` ### Error Handling - If no versions match the query criteria, `query:NextAsync()` will return `nil`. ``` -------------------------------- ### View Profile Asynchronously Source: https://madstudioroblox.github.io/ProfileService/api Use ProfileStore:ViewProfileAsync() to load the latest profile version or a specified version from the DataStore without claiming a session lock. Returns nil if the version does not exist. The returned Profile will not auto-save. ```lua ProfileStore:ViewProfileAsync(profile_key, version) --> [Profile] or nil -- profile_key [string] -- DataStore key -- version nil or [string] -- DataStore key version ``` -------------------------------- ### Measure Profile Load Time Source: https://madstudioroblox.github.io/ProfileService/troubleshooting Use this snippet to measure the time taken by ProfileStore:LoadProfileAsync(). If this consistently exceeds 1-2 seconds, investigate further. ```lua local start_time = tick() ProfileStore:LoadProfileAsync(profile_key) print(tick() - start_time) --> A value over 10 seconds ``` -------------------------------- ### Convert User IDs to String Keys Source: https://madstudioroblox.github.io/ProfileService/troubleshooting Illustrates converting numerical user IDs to string keys for table indexes when storing non-sequential numbers. This is a workaround for DataStore limitations. ```lua Profile.Data.Friends[tostring(user_id)] = {GoodFriend = true} ``` -------------------------------- ### Listen to Profile Hop Ready Event Source: https://madstudioroblox.github.io/ProfileService/api Use this to execute a listener function after the releasing UpdateAsync call finishes, typically after a universe teleport. It waits for the session lock to be removed from the profile. ```lua Profile:ListenToHopReady(listener) --> [ScriptConnection] () -- listener [function] () ``` ```lua local TeleportService = game:GetService("TeleportService") local profile, player, place_id profile:Release() profile:ListenToHopReady(function() TeleportService:TeleportAsync(place_id, {player}) end) ``` -------------------------------- ### Study Data Mutation Over Time Source: https://madstudioroblox.github.io/ProfileService/api Fetches and prints profile data snapshots over a specified time period. Useful for analyzing data changes and game progression. ```lua -- You have ProfileService working in your game. You join -- the game with your own account and go to https://www.unixtimestamp.com -- and save the current UNIX timestamp resembling present time. -- You can then make the game alter your data by giving you -- currency, items, experience, etc. local ProfileStore = ProfileService.GetProfileStore(store_name, template) -- UNIX timestamp you saved: local min_date = DateTime.fromUnixTimestamp(1628952101) local print_minutes = 5 -- Print the next 5 minutes of history local query = ProfileStore:ProfileVersionQuery( "Player_2312310", Enum.SortDirection.Ascending, min_date ) -- You can now attempt to print out every snapshot of your data saved -- at an average periodic interval of 30 seconds (ProfileService auto-save) -- starting from the time you took the UNIX timestamp! local finish_update_time = min_date.UnixTimestampMillis + (print_minutes * 60000) print("Fetching ", print_minutes, "minutes of saves:") local entry_count = 0 while true do entry_count +=1 local profile = query:NextAsync() if profile ~= nil then if profile.KeyInfo.UpdatedTime > finish_update_time then if entry_count == 1 then print("No entries found in set time period. (Start timestamp too early)") else print("Time period finished.") end break end print( "Entry", entry_count, "-", DateTime.fromUnixTimestampMillis(profile.KeyInfo.UpdatedTime):ToIsoDate() ) print(profile.Data) -- Printing table for studio expressive output else if entry_count == 1 then print("No entries found in set time period. (Start timestamp too late)") else print("No more entries in query.") end break end end ``` -------------------------------- ### ProfileService.GetProfileStore() Source: https://madstudioroblox.github.io/ProfileService/api Retrieves a ProfileStore object, which provides methods for loading, viewing, and updating player profiles. It's analogous to DataStoreService's GetDataStore(). ```APIDOC ## ProfileService.GetProfileStore() ### Description Retrieves a `ProfileStore` object, which exposes methods for loading/viewing profiles and sending global updates. Equivalent of `:GetDataStore()` in Roblox DataStoreService API. ### Parameters * **profile_store_index** (string | table) - The DataStore name or a table defining DataStore properties. * If `string`: The name of the DataStore. * If `table`: A table with the following properties: * **Name** (string) - Required - DataStore name. * **Scope** (string) - Optional - DataStore scope. * **profile_template** (table) - Optional - A table that `Profile.Data` will default to if no data was previously saved. This table is deep-copied for new profiles. ### Returns * `[ProfileStore]` - An object for managing player profiles. ### Notice By default, `profile_template` is only copied for `Profile.Data` for new profiles. Changes made to `profile_template` can be applied to `Profile.Data` of previously saved profiles by calling `Profile:Reconcile()`. You can also create your own function to fill in the missing components in `Profile.Data` as soon as it is loaded or have `nil` exceptions in your personal `:Get()` and `:Set()` method libraries. ``` -------------------------------- ### Delayed Profile Release Source: https://madstudioroblox.github.io/ProfileService/troubleshooting Avoid using 'wait()' or other yielding functions before 'profile:Release()'. Immediate release is crucial to prevent race conditions when players hop servers. ```lua Players.PlayerRemoving:Connect(function(player) local profile = Profiles[player] if profile ~= nil then wait(1) -- Or any function with "Wait", "Async" or "Yield" in its name profile:Release() end end) ``` -------------------------------- ### ProfileStore:GlobalUpdateProfileAsync() Source: https://madstudioroblox.github.io/ProfileService/api Applies global updates to a player's profile asynchronously. This can be called from any server in the game. ```APIDOC ## ProfileStore:GlobalUpdateProfileAsync() ### Description Applies and manages active global updates for a specified profile. This method can be invoked from any server within your game, ensuring updates are applied even if the profile is not currently loaded in that session. ### Method `ProfileStore:GlobalUpdateProfileAsync(profile_key, update_handler)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters - **profile_key** (string) - Required - The DataStore key of the profile to update. - **update_handler** (function) - Required - A function that receives a `GlobalUpdates` object. Inside this function, you define the updates to be applied using methods of the `GlobalUpdates` object (e.g., `AddActiveUpdate`). - `global_updates` (object) - An object with methods to define global updates. ### Request Example ```lua ProfileStore:GlobalUpdateProfileAsync( "Player_2312310", function(global_updates) global_updates:AddActiveUpdate({ Type = "AdminGift", Item = "Coins", Amount = 1000, }) end ) ``` ### Response #### Success Response (200) - **GlobalUpdates** (object) - An object representing the applied global updates. The exact structure depends on the updates defined. - **nil** - Returned if the update could not be processed. #### Response Example ```lua -- The function itself doesn't return a value directly to the caller, -- but the operation attempts to apply the updates. -- A successful call means the update request has been queued. ProfileStore:GlobalUpdateProfileAsync("Player_123", function(gu) gu:AddActiveUpdate({Type="Bonus"}) end) ``` ### Notice - `:GlobalUpdateProfileAsync()` can be used even for profiles that have not yet been created (profiles are created upon their first load with `:LoadProfileAsync()`). ### Warning - Yielding within the `update_handler` function will result in an error. - Avoid frequent calls to `ProfileStore:GlobalUpdateProfileAsync()`. Excessive usage can lead to dead session locks and potential loss of `Profile.Data` if the profile is loaded in the same session. This is due to an internal queue system that processes write requests every 7 seconds; if the queue exceeds the BindToClose timeout (approx. 30 seconds), some requests might be lost during game shutdown. ``` -------------------------------- ### Excessive GlobalUpdateProfileAsync Calls Source: https://madstudioroblox.github.io/ProfileService/troubleshooting Calling ProfileStore:GlobalUpdateProfileAsync() too frequently for the same profile key can lead to lost updates due to the 7-second queue execution and BindToClose timeout. Limit these calls. ```lua local profile_key, update_handler -- This simulates excessive UpdateAsync calls for the same Profile key: for i = 1, 6 do ProfileStore:GlobalUpdateProfileAsync(profile_key, update_handler) end ``` -------------------------------- ### ProfileStore:LoadProfileAsync() Source: https://madstudioroblox.github.io/ProfileService/api Loads a player's profile asynchronously. Handles cases where the profile might be locked by another server. ```APIDOC ## ProfileStore:LoadProfileAsync() ### Description Loads a player's profile asynchronously from the DataStore. It provides options for handling situations where the profile is currently locked by another Roblox server. ### Method `ProfileStore:LoadProfileAsync(profile_key, not_released_handler)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters - **profile_key** (string) - Required - The DataStore key for the profile. - **not_released_handler** (nil or function) - Optional - Handles cases where the profile is session-locked. - If `nil`, defaults to "ForceLoad". - If `"ForceLoad"` (string), forces the profile to load on the first call. - If `"Steal"` (string), steals the profile, ignoring its current session lock. - If a `function(place_id, game_job_id)`, this function is called when the profile is session-locked. It should return one of the following strings: "Repeat", "Cancel", "ForceLoad", or "Steal". - `place_id` (number or nil) - The place ID of the server holding the lock. - `game_job_id` (string or nil) - The job ID of the server holding the lock. ### Request Example ```lua local profile = ProfileStore:LoadProfileAsync("Player_2312310", nil) -- With a custom handler function local profile = ProfileStore:LoadProfileAsync( "Player_2312310", function(place_id, game_job_id) return "Repeat" -- or "Cancel", "ForceLoad", "Steal" end ) ``` ### Response #### Success Response (200) - **Profile** (object) - The loaded profile object. - **nil** - Returned if the profile could not be loaded (e.g., due to concurrent access). #### Response Example ```lua -- Assuming a successful load local profile = ... -- result of LoadProfileAsync -- If nil is returned local profile = ProfileStore:LoadProfileAsync("Player_123", nil) if profile == nil then -- Handle profile loading failure end ``` ### Notice - ProfileService saves profiles to live DataStore keys in Roblox Studio when Roblox API services are enabled. - Consider using `ProfileStore.Mock` for testing to avoid saving to live keys. ### Warning - `:LoadProfileAsync()` can return `nil` if another server is loading the profile concurrently. This is rare; consider kicking the player if a `Profile` object is not returned. - Do not attempt to load a profile with the same key again before it has been released. Loading a profile already session-locked on the same server will result in an error. You can reload a profile immediately after releasing it with `Profile:Release()`. ``` -------------------------------- ### Profile.KeyInfoUpdated Source: https://madstudioroblox.github.io/ProfileService/api A signal that triggers when `Profile.KeyInfo` is updated with a new `DataStoreKeyInfo` instance. ```APIDOC ## Profile.KeyInfoUpdated ### Description A signal that gets triggered every time `Profile.KeyInfo` is updated with a new DataStoreKeyInfo instance reference after every auto-save or profile release. ### Parameters None ### Request Example ```json { "example": "" } ``` ### Response #### Success Response (200) - **KeyInfoUpdated** (ScriptSignal) - A signal that emits the new `DataStoreKeyInfo` instance. #### Response Example ```json { "example": "" } ``` ``` -------------------------------- ### Confirm Profile Release with Print Statement Source: https://madstudioroblox.github.io/ProfileService/troubleshooting Add a print statement after 'profile:Release()' within the PlayerRemoving event to verify that profiles are being released as expected. ```lua Players.PlayerRemoving:Connect(function(player) local profile = Profiles[player] if profile ~= nil then profile:Release() print(player.Name .. "'s profile has been released!") end end) ``` -------------------------------- ### Set and Access Roblox Metadata Source: https://madstudioroblox.github.io/ProfileService/api Demonstrates how to set and access Roblox metadata for a profile. Be mindful of the strict 300-character limit for total table content. ```lua local profile -- A profile object you loaded -- Mimicking the Roblox hub example: profile.RobloxMetaData = {["ExperienceElement"] = "Fire"} -- You can read from it and write to it at will: print(profile.RobloxMetaData.ExperienceElement) profile.RobloxMetaData.ExperienceElement = nil profile.RobloxMetaData.UserCategory = "Casual" -- I think setting it to a whole table at profile load would -- be more safe considering the size limit for meta data -- is pretty tight: profile.RobloxMetaData = { UserCategory = "Casual", FavoriteColor = {1, 0, 0}, } ``` -------------------------------- ### Wipe Profile Data Asynchronously Source: https://madstudioroblox.github.io/ProfileService/api Use :WipeProfileAsync() to erase user data, typically for right of erasure requests. On live servers, this must be used on profiles created through ProfileStore.Mock after Profile:Release() and when the profile will not be loaded again. ```lua ProfileStore:WipeProfileAsync(profile_key) --> is_wipe_successful [bool] -- profile_key [string] -- DataStore key ``` -------------------------------- ### Profile:Reconcile() Source: https://madstudioroblox.github.io/ProfileService/api Fills in missing variables in `Profile.Data` using the `profile_template`. Useful for updating templates after game release. ```APIDOC ## Profile:Reconcile() ### Description Fills in missing variables inside `Profile.Data` from `profile_template` table that was provided when calling `ProfileService.GetProfileStore()`. It's often necessary to use `:Reconcile()` if you're applying changes to your `profile_template` over the course of your game's development after release. The right time to call this method can be seen in the basic usage example. ### Parameters None ### Request Example ```json { "example": "" } ``` ### Response #### Success Response (200) - **nil** - This method does not return a value. #### Response Example ```json { "example": "nil" } ``` ``` -------------------------------- ### Check for NaN Values Source: https://madstudioroblox.github.io/ProfileService/troubleshooting Demonstrates how to check for NaN values by comparing a number with itself. NaN values can arise from division by zero or specific math operations and must be avoided in Profile.Data. ```lua print(NaN == NaN) --> false ``` -------------------------------- ### Quickly Progress Global Updates or Meta Tag Propagation Source: https://madstudioroblox.github.io/ProfileService/api Call Profile:Save() to quickly progress the GlobalUpdates state or speed up the propagation of Profile.MetaData.MetaTags changes to Profile.MetaData.MetaTagsLatest. This method does not yield and should not be used for saving Profile.Data. Calling on a released profile will throw an error. ```lua Profile:Save() -- Call to quickly progress GlobalUpdates -- state or to speed up save validation processes -- (Does not yield) ``` -------------------------------- ### ProfileService.IssueSignal for Error Logging Source: https://madstudioroblox.github.io/ProfileService/api An analytics endpoint for logging DataStore errors. Connect to this signal to capture error details, profile store name, and profile key for analysis. ```lua ProfileService.IssueSignal [ScriptSignal](error_message [string], profile_store_name [string], profile_key [string]) ``` ```lua ProfileService.IssueSignal:Connect(function(error_message, profile_store_name, profile_key) pcall(function() AnalyticsService:FireEvent( "ProfileServiceIssue", error_message, profile_store_name, profile_key ) end) end) ``` -------------------------------- ### Overwrite Profile Async Source: https://madstudioroblox.github.io/ProfileService/api Only works for profiles loaded through :ViewProfileAsync() or :ProfileVersionQuery(). Use only for rollback payloads. Pushes the profile payload to the DataStore and releases the session lock. Avoid using for editing live player data to prevent progress loss; use LoadProfileAsync() instead. ```lua Profile:OverwriteAsync() ``` -------------------------------- ### Set Profile Meta Tag Source: https://madstudioroblox.github.io/ProfileService/api Sets a meta tag for the profile, equivalent to Profile.MetaData.MetaTags[tag_name] = value. Useful for tracking data versions or purchase confirmations. Values are available in Profile.MetaData.MetaTagsLatest after a save. ```lua Profile:SetMetaTag(tag_name, value) -- tag_name [string] -- value -- Any value supported by DataStore ``` -------------------------------- ### Profile:Save() Source: https://madstudioroblox.github.io/ProfileService/api Quickly progresses the GlobalUpdates state or speeds up save validation processes. This method does not yield and should not be used for saving Profile.Data or Profile.MetaData.MetaTags. ```APIDOC ## Profile:Save() ### Description Quickly progresses the GlobalUpdates state or speeds up save validation processes. This method does not yield and is not for saving profile data. ### Method Profile:Save() ### Warning Calling `Profile:Save()` when the `Profile` is released will throw an error. You can check `Profile:IsActive()` before using this method. ### Request Example ```lua profile:Save() ``` ``` -------------------------------- ### Process Marketplace Receipt Source: https://madstudioroblox.github.io/ProfileService/tutorial/developer_products Handles the processing of marketplace receipts, checking for existing profiles, and granting products. Returns NotProcessedYet if the player is nil or no profile is found. ```lua if player == nil then return Enum.ProductPurchaseDecision.NotProcessedYet end local profile = GetPlayerProfileAsync(player) if profile ~= nil then return PurchaseIdCheckAsync( profile, receipt_info.PurchaseId, function() GrantProduct(player, receipt_info.ProductId) end ) else return Enum.ProductPurchaseDecision.NotProcessedYet end end ``` -------------------------------- ### Apply Global Update to Profile Source: https://madstudioroblox.github.io/ProfileService/api Use GlobalUpdateProfileAsync to create and manage active global updates for a profile. The update_handler function receives a GlobalUpdates object to which updates can be added. ```lua ProfileStore:GlobalUpdateProfileAsync( "Player_2312310", function(global_updates) global_updates:AddActiveUpdate({ Type = "AdminGift", Item = "Coins", Amount = 1000, }) end ) ``` -------------------------------- ### Profile:Release() Source: https://madstudioroblox.github.io/ProfileService/api Removes the session lock for the profile on the current server and saves its data for the last time. ```APIDOC ## Profile:Release() ### Description Removes the session lock for this profile for this Roblox server. Call this method after you're done working with the `Profile` object. Profile data will be immediately saved for the last time. ### Parameters None ### Request Example ```json { "example": "" } ``` ### Response #### Success Response (200) - **nil** - This method does not return a value. #### Response Example ```json { "example": "nil" } ``` ``` -------------------------------- ### Profile:OverwriteAsync() Source: https://madstudioroblox.github.io/ProfileService/api Pushes the profile payload to the DataStore and releases the session lock. Use only for rollback payloads. Loading the profile via LoadProfileAsync() is recommended for editing live player data. ```APIDOC ## Profile:OverwriteAsync() ### Description Pushes the profile payload to the DataStore and releases the session lock. This method should primarily be used for rollback operations. ### Method Profile:OverwriteAsync() ### Warning Using this method for editing live player data can lead to lost progress. For editing live data, use `LoadProfileAsync()` instead, which waits for the profile to be released by the remote server. ### Request Example ```lua profile:OverwriteAsync() ``` ``` -------------------------------- ### ProfileStore:ViewProfileAsync() Source: https://madstudioroblox.github.io/ProfileService/api Retrieves a player's profile from the DataStore without claiming a session lock. This is the preferred method for viewing player data without intending to edit it. ```APIDOC ## ProfileStore:ViewProfileAsync() ### Description Attempts to load the latest profile version (or a specified version via the `version` argument) from the DataStore without claiming a session lock. Returns `nil` if such version does not exist. The returned `Profile` will not auto-save and releasing it will not perform any action. Data in the returned `Profile` can be modified to create a payload that can be saved via `Profile:OverwriteAsync()`. `:ViewProfileAsync()` is the preferred way of viewing player data without editing it. ### Method `ProfileStore:ViewProfileAsync(profile_key, version)` ### Parameters #### Path Parameters - **profile_key** (string) - Required - The DataStore key for the profile. - **version** (nil or string) - Optional - The specific version of the profile to load. If `nil`, the latest version is loaded. ### Request Example ```lua -- Example of viewing the latest profile local profile = ProfileStore:ViewProfileAsync("player_123") -- Example of viewing a specific version local profile_v2 = ProfileStore:ViewProfileAsync("player_123", "version_abc") ``` ### Response #### Success Response (200) - **Profile** (object) - A `Profile` object containing the player's data, or `nil` if the profile version does not exist. #### Response Example ```json { "profile_data": { "name": "PlayerName", "level": 10 } } ``` ### Error Handling - Passing a `version` argument in mock mode (or offline mode) will throw an error, as mock versioning is not supported. ``` -------------------------------- ### ProfileService.CriticalStateSignal for DataStore Errors Source: https://madstudioroblox.github.io/ProfileService/api An analytics endpoint for critical DataStore errors, indicating potential widespread issues affecting game performance. This can be used to alert players about outages. ```lua ProfileService.CriticalStateSignal [ScriptSignal] (is_critical_state [bool]) ``` -------------------------------- ### Profile.RobloxMetaData Source: https://madstudioroblox.github.io/ProfileService/api Represents metadata associated with a profile's DataStore key. Changes are saved on the next auto-update or when the profile is released. Be mindful of the strict size limit (300 characters). ```APIDOC ## Profile.RobloxMetaData ### Description Table that gets saved as Metadata (Official documentation) of a DataStore key belonging to the profile. The way this table is saved is equivalent to using `DataStoreSetOptions:SetMetaData(Profile.RobloxMetaData)` and passing the `DataStoreSetOptions` object to a `:SetAsync()` call, except changes will truly get saved on the next auto-update cycle or when the profile is released. The periodic saving and saving upon releasing behaviour is identical to that of `Profile.Data` - After the profile is released further changes to this value will not be saved. ### Parameters None ### Request Example ```json { "example": "{\"ExperienceElement\": \"Fire\"}" } ``` ### Response #### Success Response (200) - **RobloxMetaData** (table) - The metadata table associated with the profile. #### Response Example ```json { "example": "{\"ExperienceElement\": \"Fire\"}" } ``` ``` -------------------------------- ### Profile Object Properties Source: https://madstudioroblox.github.io/ProfileService/api Details the properties of a Profile object, including Data, MetaData, and MetaTagsUpdated. ```APIDOC ## Profile Object Properties ### Profile.Data #### Description `Profile.Data` is the primary table where all user-specific data is stored. Developers can freely read and write to this table, and changes are automatically saved to the DataStore. Saving stops once the profile is released. #### Type `table` (Non-strict reference) ### Profile.MetaData #### Description `Profile.MetaData` is a read-only table containing metadata about the profile itself. #### Properties - **ProfileCreateTime** (number): An os.time() timestamp indicating when the profile was created. - **SessionLoadCount** (number): The number of times the profile has been loaded during the current session. - **ActiveSession** (table or nil): Contains `{place_id, game_job_id}` if a Roblox server currently owns the profile, otherwise `nil`. - **MetaTags** (table): A writable table for storing custom metadata tags. Changes are saved and auto-saved like `Profile.Data`. - **MetaTagsLatest** (table): A read-only table representing the most recent version of `MetaTags` that has been saved to the DataStore. ### Profile.MetaTagsUpdated #### Description `Profile.MetaTagsUpdated` is a `ScriptSignal` that fires after each auto-save event, specifically after `Profile.MetaData.MetaTagsLatest` has been updated with the saved version. It fires regardless of whether `MetaTagsLatest` actually changed. #### Event Arguments - **meta_tags_latest** (table): The latest version of the MetaTags that has been saved. #### Usage Notes - This signal also fires after the profile is saved for the last time and released. - Remember that changes to `Profile.Data` are not saved after release. `Profile:IsActive()` will return `false` if the profile has been released. ```