Object
At a high level, Core Lua types can be divided into two groups: data structures and Objects. Data structures are owned by Lua, while Objects are owned by the engine and could be destroyed while still referenced by Lua. Any such object will inherit from this type. These include CoreObject, Player, and Projectile.
Properties
| Property Name | Return Type | Description | Tags | 
|---|---|---|---|
| serverUserData | table | Table in which users can store any data they want on the server. | Read-Write, Server-Only | 
| clientUserData | table | Table in which users can store any data they want on the client. | Read-Write, Client-Only | 
Class Functions
| Class Function Name | Return Type | Description | Tags | 
|---|---|---|---|
| Object.IsValid(Object object) | boolean | Returns true if object is still a valid Object, or false if it has been destroyed. Also returns false if passed a nil value or something that's not an Object, such as a Vector3 or a string. | None | 
Examples
Example using:
IsValid
The example below shows the importance of using Object.IsValid() instead of a simple nil check (that is if object then). An object can be in a situation where it's invalid, but not yet nil. This can happen if a script is retaining a reference to it or it began the destroy process but hasn't completed it yet.
In this example, the script has a cube child that it finds with the GetChildren() call. It then prints information about the cube as it progresses through the steps of being destroyed and its variable reference cleared.
local CUBE = script:GetChildren()[1]
function PrintCubeInfo()
    print(".:.:. Information about CUBE object .:.:.")
    if CUBE then
        print("CUBE is NOT nil")
    else
        print("CUBE is nil")
    end
    if Object.IsValid(CUBE) then
        print("CUBE is valid")
    else
        print("CUBE is NOT valid")
    end
    local childrenCount = #script:GetChildren()
    print("Number of children of this script: " .. tostring(childrenCount))
    print("")
end
PrintCubeInfo()
-- The cube is destroyed, but we still have a variable pointing to it.
CUBE:Destroy()
PrintCubeInfo()
-- Variable reference is cleared, releasing the cube.
CUBE = nil
PrintCubeInfo()
See also: CoreObject.Destroy | CoreLua.print | Object.IsValid
Example using:
clientUserData
In this example, multiple copies of the same script are placed into the scene. At startup, they search for each other and build a follow chain. The last script that can't find another script to follow is set to follow the local player. As the player moves around the chain of objects follows along in a smooth motion. The clientUserData property is leveraged in building the chain of object references.
For this to work all scripts should be in a client context. In order to visualize the effect, objects (for example a Cube) can be added as children of the scripts.
As the name implies, clientUserData is a non-dynamic property on the client only.
local allScripts = World.FindObjectsByName(script.name)
for _, otherScript in ipairs(allScripts) do
    if otherScript ~= script
    and otherScript.clientUserData.target == nil then
        script.clientUserData.target = otherScript
        break
    end
end
if script.clientUserData.target == nil then
    script.clientUserData.target = Game.GetLocalPlayer()
end
local velocity = Vector3.ZERO
local DRAG = 0.96
local ACCELERATION = 0.5
function Tick()
    if not script.clientUserData.target then return end
    local myPos = script:GetWorldPosition()
    myPos = myPos + velocity
    velocity = velocity * DRAG
    local targetPos = script.clientUserData.target:GetWorldPosition()
    local direction = (targetPos - myPos):GetNormalized()
    velocity = velocity + direction * ACCELERATION
    script:SetWorldPosition(myPos)
end
See also: World.FindObjectsByName | Game.GetLocalPlayer | CoreObject.GetWorldPosition | Vector3:GetNormalized() | CoreLua.Tick
Example using:
serverUserData
In this example we are trying to figure out which player was the first to join the game and promote them with some gameplay advantage. That's easy for the first player joining, but because players can join and leave at any moment, the first player to join might leave, at which point we need to promote the next (oldest) player. To accomplish this, we keep count of how many players have joined and save that number onto each player's serverUserData--a kind of waiting list.
As the name implies, serverUserData is a non-dynamic property on the server only.
local primaryPlayer = nil
local joinCounter = 0
function OnPlayerJoined(player)
    joinCounter = joinCounter + 1
    -- Save the waiting number onto the player itself
    player.serverUserData.joinNumber = joinCounter
end
function PromotePlayer(player)
    -- Give some gameplay advantage or leadership ability
    print("PROMOTING: " .. player.name)
end
function Tick()
    if (not Object.IsValid(primaryPlayer)) then
        -- Find the oldest player
        local oldestPlayer = nil
        local oldestJoinNumber = 999999
        local allConnectedPlayers = Game.GetPlayers()
        for _, player in ipairs(allConnectedPlayers) do
            local joinNumber = player.serverUserData.joinNumber
            if joinNumber < oldestJoinNumber then
                oldestJoinNumber = joinNumber
                oldestPlayer = player
            end
        end
        -- If we found a player, promote them
        if oldestPlayer then
            primaryPlayer = oldestPlayer
            PromotePlayer(oldestPlayer)
        end
    end
end
Game.playerJoinedEvent:Connect(OnPlayerJoined)
See also: CoreLua.print | Player.name | Object.IsValid | Game.GetPlayers | Event.Connect