Lua Scripting Guide for Lobby Service

October 10, 2025 ยท View on GitHub

This guide explains how to use Lua scripting with the Lobby Service. The scripting backend uses the luau flavor of Lua. The entry point is a main.lua file, such as the example in scripts/draft_lua/main.lua.

  1. Server Callbacks
  2. Lobby Callbacks
  3. Peer Callbacks
  4. Host Callbacks
  5. Lobby Package
  6. System Package
  7. Lobby Type
  8. Peer Type

1. Server Callbacks

function main._on_server_init()
    print("Server init")
end
function main._on_server_reload()
    print("Server reload")
end

2. Lobby Callbacks

function main._can_create_lobby()
    print("Can lobby create")
end
function main._on_lobby_created()
    print("On lobby create")
end
function main._on_lobby_tick(tickrate: int64)
    print("Lobby tick")
end

3. Peer Callbacks

function main._can_peer_join()
    print("Can peer join")
end
function main._on_peer_joined()
    print("Can peer join")
end
function main._can_peer_chat(message: string)
    print("Can peer chat " .. message)
end
function main._can_peer_ready(ready: boolean)
    print("Can peer set ready " .. tostring(ready))
end
function main._on_peer_leave()
    print("Peer left")
end
function main._on_peer_connected()
    print("Peer connected")
end
function main._on_peer_disconnected()
    print("Peer disconnected")
end

4. Host Callbacks

function main._can_host_set_tags(tags: any)
    print("Can host set tags")
end
function main._can_host_kick(peer_id: string)
    print("Can host kick " .. peer_id)
end
function main._can_host_seal(seal: boolean)
    print("Can host seal " .. tostring(seal))
end
function main._can_host_resize(max_players: number)
    print("Can host resize " .. tostring(max_players))
end
function main._can_host_set_title(title: string)
    print("Can host set title " .. title)
end

5. Lobby Pacakge

For lobby and peer type, check scripts/draft_lua/types.lua.

local lobby = require("lobby")

-- Get current lobby data
local l = lobby.get()
-- Lobby functions
-- Start a timer with a callback
l.start_timer("_timer_id_1", 10, "arg1")
-- Callback that will be called in 10s
function _timer_id_1(arg: string):
    print(arg)
end
-- Stop the timer
l.stop_timer("_timer_id_1")
-- Notify 1 peer with a message. The peer will receive on notify
l.notify("peer_id_1", {"message": "text"})
-- Notify all peers with a message. The peers will receive on notify
l.notify_all({"message": "text"})
-- Kick a peer by id
l.kick_peer("1234")
-- Send a chat message to all peers (with or without metadata)
l.broadcast_chat("message")
l.broadcast_chat("message", {"metadata_dict": "optional"})
-- Leaderboards (requires DB). Possible values for leaderboard type: set, best, inc, dec
l.set_leaderboard("peer_id", 123, "best", "leaderboard_id")
l.set_leaderboard("peer_id", 123)

6. System Package

local system = require("system")

-- Get time since epoch
print(system.get_time_since_epoch())
-- Http Request
system.http_request("GET", "https://google.com", {},  "")
-- Decode Json
local status, body = decode_json("{\"key\": \"value\"}")
-- Encode Json
local encoded_str = encode_json({ key = "value" })
-- Read file
local file_str = read_file_as_string("filename.txt")
-- Write file
write_file_as_string("filename.txt", "content_new")

7. Lobby Type

type Lobby = {
    calling_peer_id: string,  -- readonly
    id: string,               -- readonly
    tick_rate: number,        -- readonly
    name: string,             -- get/set
    host: string,             -- get/set
    max_players: number,      -- get/set
    create_time: number,      -- readonly (Unix timestamp)
    sealed: boolean,          -- get/set
    game_id: string,          -- readonly
    peers_count: number,      -- readonly
    public_data: any,         -- get/set
    private_data: any,        -- get/set
    tags: any,                -- get/set
    peers: any,               -- table of LobbyPeer
    peers_ordered: any,       -- array of LobbyPeer, sorted by order_id

    start_timer: function(id: string, duration: number, arguments ...)
    stop_timer: function(id: string)
    notify: function (peer_id: string, data: table)
    notify_all: function (data: table)
    kick_peer: function(peer_id: string)
    broadcast_chat: function(message: string)
    broadcast_chat: function(message: string, metadata: table)
}

-- Lobby type
local l = lobby.get()
-- ID of peer who called the current function
print(l.calling_peer_id)
-- Get calling peer object
local calling_peer = l.peers[l.calling_peer_id]
-- ...
-- Rename lobby
l.name = "new lobby name"
-- Set a new host by peer id
l.host = "1234"
-- Make the lobby be hostless
l.host = ""
-- Resize lobby
l.max_players = 15
-- Seal/unseal the lobby
l.sealed = true
print(l.public_data["some_data"])
-- Set public data, visible to all peers in the lobby
l.public_data["some_data"] = 123
-- Set private data, visible only to the server
l.private_data["private_data"] = 234
-- Set lobby tag, visible when finding lobby
l.tags["map_name"] = "my_name"
-- Iterate peers array
for peerID, peer in pairs(l.peers) do
    print(peerID, peer.ready)

8. Peer Type

type LobbyPeer = {
    user_data: any,            -- get/set
    public_data: any,          -- get/set
    private_data: any,         -- get/set
    platform: string,          -- readonly
    platform_id: string,       -- readonly
    id: string,                -- readonly (peer ID)
    order_id: number,          -- readonly
    ready: boolean,            -- readonly
    disconnected: boolean,     -- readonly
}

-- Get calling peer object
local peer = l.peers[l.calling_peer_id]

-- Set user data, this is freely settable by users also
peer.user_data["mask"] = "green"
-- Set private data, only the server sees this
peer.private_data["private"] = "secret"
-- Set public data, every peer sees this
peer.private_data["player_pos"] = 123
-- Print the platform (eg. steam/discord) and id of it (keep these private to server)
print(peer.platform .. peer.platform_id)

Notes

  • All callback functions are optional, but if present, will be called by the lobby service at the appropriate time.
  • You can return an error table (e.g., return { error = "reason" }) from any _can_* function to block the action.
  • Use the lobby and system packages for advanced logic (see comments in the script for usage examples from scripts/draft_lua/main.lua).
  • When writting to data, getters return data that need to be set afterwards:
-- This won't work
public_data["test"]["abc"] = 123

-- This will work
local test = public_data["test"]
test["abc"] = 123
public_data["test"] = test