Skip to content

Steam Lobbies and P2P

Multiplayer features with Steam networking.

Lobbies

requestLobbyList(distanceFilter?)

Search for available lobbies. Returns an array of lobby ID strings.

javascript
const lobbies = await steam.requestLobbyList();
// ['109775241234567890', '109775241234567891', ...]

Parameters:

  • distanceFilter (optional) — 0 Close, 1 Default (~1500km), 2 Far, 3 Worldwide. Defaults to Worldwide so players in different regions can find each other.

Returns: string[] — array of lobby Steam IDs

Speed vs. reach

Worldwide search finds lobbies in any region but the underlying Steam matchmaking query takes longer (several seconds is normal). If you need a fast regional refresh — e.g. for an auto-refreshing browser list — pass 0 or 1 instead:

javascript
const lobbies = await steam.requestLobbyList(0); // fast, same-region only

Lobby search filters

Narrow the next requestLobbyList() call. Steam applies filters to the next search only and clears them afterwards, so set them immediately before searching.

javascript
await steam.addRequestLobbyListStringFilter('mode', 'deathmatch');
await steam.addRequestLobbyListNumericalFilter('level', 5, 2); // level >= 5
await steam.addRequestLobbyListFilterSlotsAvailable(1);        // at least 1 free slot
await steam.addRequestLobbyListResultCountFilter(20);          // cap results

const lobbies = await steam.requestLobbyList();

Filters match against values the host published with setLobbyData().

MethodPurpose
addRequestLobbyListStringFilter(key, value, comparison?)Match a string lobby-data value
addRequestLobbyListNumericalFilter(key, value, comparison?)Match a numeric lobby-data value
addRequestLobbyListFilterSlotsAvailable(slots?)Only lobbies with at least this many free slots
addRequestLobbyListResultCountFilter(maxResults?)Cap how many lobbies come back (fewer = faster)
addRequestLobbyListDistanceFilter(distance?)Geographic reach for this one search

Comparison values: -2 ≤, -1 <, 0 = (default), 1 >, 2 ≥, 3

Which distance API to use

requestLobbyList(distanceFilter) is the simple way. addRequestLobbyListDistanceFilter() does the same for one search and takes precedence over the default — useful when you set several filters together. Note 0 (Close) is a valid value, not "unset".

createLobby(type, maxMembers)

Create a new lobby.

javascript
const lobbyId = await steam.createLobby(2, 4); // Public, 4 players

Lobby Types:

  • 0 - Private
  • 1 - Friends Only
  • 2 - Public
  • 3 - Invisible

joinLobby(lobbyId)

Join an existing lobby.

javascript
await steam.joinLobby(lobbyId);

leaveLobby()

Leave current lobby.

javascript
await steam.leaveLobby();

getLobbyData(lobbyId, key)

Get lobby metadata. Works on any lobby ID — no membership required. Use this to show lobby details (map, mode, etc.) in a browser list before joining, or to read data from your own joined lobby.

javascript
const lobbies = await steam.requestLobbyList();
const mapName = await steam.getLobbyData(lobbies[0], 'map');

TIP

Steam's lobby search results carry a data snapshot, so this works immediately after requestLobbyList() without joining first.

setLobbyData(key, value)

Set lobby metadata (owner only).

javascript
await steam.setLobbyData('map', 'forest');
await steam.setLobbyData('mode', 'deathmatch');

getLobbyMembers()

Get list of members in current lobby.

javascript
const members = await steam.getLobbyMembers();
// Array of Steam IDs

getLobbyOwner()

Get the lobby owner's Steam ID (uses current lobby).

javascript
const owner = await steam.getLobbyOwner();
// '76561198012345678'

Returns: Steam ID string, or empty string if no current lobby.

getLobbyMemberCount(lobbyId)

Get the number of members currently in a lobby.

javascript
const count = await steam.getLobbyMemberCount(lobbyId);

Parameters:

  • lobbyId - Steam lobby ID string

Returns: number — member count (0 if lobby not found or not yet joined)

getLobbyMemberByIndex(lobbyId, index)

Get the Steam ID of a specific lobby member by index.

javascript
const memberId = await steam.getLobbyMemberByIndex(lobbyId, 0); // first member

Parameters:

  • lobbyId - Steam lobby ID string
  • index - Zero-based member index

Returns: Steam ID string, or empty string if index out of range.

getLobbyMemberLimit()

Get maximum number of members allowed.

javascript
const max = await steam.getLobbyMemberLimit();

setLobbyJoinable(joinable)

Set whether the lobby can be joined.

javascript
await steam.setLobbyJoinable(false); // Lock lobby when game starts
await steam.setLobbyJoinable(true);  // Reopen

setLobbyType(type)

Change lobby visibility at runtime.

javascript
await steam.setLobbyType(0); // Make private
await steam.setLobbyType(2); // Make public again

Types: 0 Private, 1 FriendsOnly, 2 Public, 3 Invisible

inviteUserToLobby(steamId)

Invite a user to the lobby. Opens Steam's invite overlay for them.

javascript
await steam.inviteUserToLobby('76561198012345678');

Lobby discovery

requestLobbyList() now defaults to worldwide distance filter. If your previous lobbies weren't showing up for players in different regions, updating GemShell will fix it.

P2P Networking

sendP2PPacket(steamId, data, type)

Send data to another player.

javascript
const packet = JSON.stringify({ type: 'move', x: 100, y: 200 });
await steam.sendP2PPacket(targetSteamId, packet, 2);

Channel Types:

  • 0 - Unreliable
  • 1 - Unreliable, no delay
  • 2 - Reliable
  • 3 - Reliable, buffered

readP2PPacket()

Read incoming packet.

javascript
const packet = await steam.readP2PPacket();
if (packet) {
  const data = JSON.parse(packet.data);
  const sender = packet.steamId;
}

acceptP2PSessionWithUser(steamId)

Accept incoming connection.

javascript
await steam.acceptP2PSessionWithUser(steamId);

closeP2PSessionWithUser(steamId)

Close connection.

javascript
await steam.closeP2PSessionWithUser(steamId);

Examples

Host Game

javascript
async function hostGame(maxPlayers = 4) {
  // 0 = Private, 1 = FriendsOnly, 2 = Public, 3 = Invisible
  const lobbyId = await steam.createLobby(2, maxPlayers);
  
  if (lobbyId) {
    console.log('Lobby created:', lobbyId);
    const members = await steam.getLobbyMembers();
    console.log('Members:', members);
  }
  
  return lobbyId ?? null;
}

Browse Lobbies

javascript
async function findLobbies() {
  const lobbies = await steam.requestLobbyList();
  // lobbies is an array of lobby ID strings
  
  const details = [];
  for (const lobbyId of lobbies) {
    const memberCount = await steam.getLobbyMemberCount(lobbyId);
    details.push({ lobbyId, memberCount });
  }
  
  return details;
}

Full Lobby Flow

javascript
async function lobbyDemo() {
  // Create a public lobby for up to 4 players
  const lobbyId = await steam.createLobby(2, 4);
  if (!lobbyId) return;

  // Check who joined
  const members = await steam.getLobbyMembers();
  const owner = await steam.getLobbyOwner();
  console.log(`Owner: ${owner}, Members: ${members.join(', ')}`);

  // Get count using explicit ID (useful when browsing lobbies)
  const count = await steam.getLobbyMemberCount(lobbyId);
  const first = await steam.getLobbyMemberByIndex(lobbyId, 0);

  // Leave when done
  steam.leaveLobby();
}

Network Loop

javascript
function networkUpdate() {
  // Process incoming packets
  let packet;
  while ((packet = steam.readP2PPacket())) {
    handlePacket(packet.steamId, JSON.parse(packet.data));
  }
}

function handlePacket(sender, data) {
  switch (data.type) {
    case 'move':
      updatePlayerPosition(sender, data.x, data.y);
      break;
    case 'chat':
      addChatMessage(sender, data.message);
      break;
    case 'action':
      handlePlayerAction(sender, data.action);
      break;
  }
}

// Send position update
function sendPosition(x, y) {
  const packet = JSON.stringify({ type: 'move', x, y });
  for (const playerId of connectedPlayers) {
    steam.sendP2PPacket(playerId, packet, 1); // Unreliable, no delay
  }
}

Reliable Messages

javascript
async function sendReliableMessage(steamId, data) {
  const packet = JSON.stringify(data);
  await steam.sendP2PPacket(steamId, packet, 2); // Reliable
}

// Use for important events
async function onPlayerScored(steamId, points) {
  await sendReliableMessage(steamId, {
    type: 'score',
    points,
    timestamp: Date.now()
  });
}

Best Practices

  1. Use reliable for important data - Score updates, game state changes
  2. Use unreliable for frequent updates - Position, rotation
  3. Validate incoming data - Never trust client data
  4. Handle disconnections - Clean up player state
  5. Rate limit sends - Don't flood the network