Roblox assigns each user a unique domain-scoped user ID per game or app they interact with. This page explains how domain-scoped identification works and how to use the User type in your game code.
The following concepts are central to user identification:
| Concept | Description |
|---|---|
| User | A persistent account on Roblox. |
| Player | The representation of a user within a running game's data model (see Players). |
| User data type | A data type that represents a user's identity within a specific domain. This is the standard identifier for referencing users in game code. |
| Domain-scoped user ID | A numeric identifier that uniquely identifies a user within a specific domain. This identifier is different in every game and app. |
Domain-scoped user IDs
A domain-scoped user ID is a unique identifier assigned to a user within a specific game or app. Unlike a global user ID, a domain-scoped user ID is only meaningful within the domain that issued it, so the same user has a different domain-scoped user ID in each game or app they interact with. Players who joined your game before the scoped ID rollout keep their original global user ID in that game (see Returning players).
Domain types are represented by Enum.DomainType:
| Domain type | Scoped to | Example |
|---|---|---|
| Enum.DomainType.EXPERIENCE | A specific game (universe) | A player's ID in your game |
| Enum.DomainType.OAUTH | A specific OAuth application | A user's ID in your external tool |
How domain-scoped IDs work
When a player joins your game, they receive a domain-scoped user ID that is unique to that game. The same player joining a different game receives a different domain-scoped user ID. This means:
- Domain-scoped user IDs are integers, similar to existing global user IDs.
- A domain-scoped user ID does not collide with any existing global user ID.
- The same numeric value may appear in different domains for different users. The unique key is the combination of domain type, domain ID, and domain-scoped user ID.
Example scenario
The following example demonstrates how different games reference the same user:
| Scenario | User context | User ID seen by game |
|---|---|---|
| User connects to Game A | Returning player (visited before scoped IDs) | 123 (global ID, unchanged) |
| User connects to Game B | First visit after scoped ID rollout | 500789 (domain-scoped user ID for Game B) |
| User connects to Game C | First visit after scoped ID rollout | 302441 (domain-scoped user ID for Game C) |
Returning players
If a player already joined your game before the scoped user ID rollout, they keep their original global user ID in that game. This ensures existing data store keys, leaderboards, and inventories continue to work without any changes.
Working with the User Type
User is the standard way to identify users in game code. A User value carries the domain-scoped user ID alongside the domain type and domain ID, so the identity context always travels with the identifier. For full API details, see the User reference.
Get a User from a Player
The primary way to obtain a User is from a connected Player via Player.User:
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
local user = player.User
print("Domain user ID:", user.Id)
print("Domain type:", user.DomainType)
print("Domain ID:", user.DomainId)
end)
Construct a User from an ID
To create a User from a known user ID within the current game, use User.fromId():
local user = User.fromId(knownUserId)
Using User with engine APIs
Engine APIs that accept a user ID parameter also accept User values directly. Using User is recommended:
-- Both forms work:BadgeService:AwardBadge(player.User, badgeId) -- RecommendedBadgeService:AwardBadge(player.UserId, badgeId)
-- Both forms work:local target = Players:GetPlayerByUserId(player.User) -- Recommendedlocal target = Players:GetPlayerByUserId(someUserId)
When a numeric user ID is passed to an engine API that accepts user IDs, it is automatically wrapped into a User internally.
Engine APIs that return user IDs
Engine APIs that return numeric user IDs continue to use the same field names and types. Each returned ID is either a global ID or a domain-scoped user ID depending on whether the user joined the game before or after the scoped ID rollout:
- Returning players (first joined before scoped ID launch) — the API returns the user's global user ID.
- New players (first join after scoped ID launch) — the API returns the user's domain-scoped user ID.
Both global and domain-scoped user IDs are standard integers that do not collide, so existing code that reads these numeric fields continues to work.
Storing user identity
For data store keys within a single game, use user.Id directly:
local key = ("player_%d"):format(player.User.Id)PlayerDataStore:SetAsync(key, data)
For values that may be read by other systems or need full domain context, serialize the User with ToString():
local encoded = player.User:ToString()SomeDataStore:SetAsync("support_ticket_user", encoded)-- Later, restore from the stringlocal user = User.fromString(encoded)
For more details on serialization guarantees, see the User reference.
Best practices
- Use Player.User as the standard identifier in new code.
- Do not use a numeric user ID from one domain to identify a user in another domain.