# Multiplayer with invites

> A two player room. Create it on your server, invite a friend into it with Lia's friend picker or a link, and land both players in the same room. From the Lia developer docs: https://docs.lialabs.org/guides/multiplayer-with-invites

The idea: a room has an id, and the id goes in the invite's **startParam**. Whoever opens the invite opens your app with that startParam, and your page joins them to the room. Lia's own Lia XO works this way.

## 1. Create a room on your server

```js title="server.js"
app.post("/api/rooms", async (req, res) => {
  const playerId = await playerFrom(req.body.session); // see Verify on your server
  if (!playerId) return res.status(401).end();
  const room = "room-" + crypto.randomUUID().replace(/-/g, "").slice(0, 10);
  await db.createRoom(room, { host: playerId, players: [playerId] });
  res.json({ room });
});

app.post("/api/rooms/join", async (req, res) => {
  const playerId = await playerFrom(req.body.session);
  if (!playerId) return res.status(401).end();
  const room = await db.room(req.body.room);
  if (!room) return res.status(404).json({ error: "no_room" });
  if (room.players.length >= 2 && !room.players.includes(playerId)) return res.status(409).json({ error: "full" });
  await db.addPlayer(req.body.room, playerId);
  res.json({ room: req.body.room });
});
```

Room ids must fit a startParam: 1 to 64 of letters, digits, `_` and `-`.

## 2. Invite a friend

```js title="game.js"
async function playWithAFriend() {
  const { room } = await api("/api/rooms", {});
  showWaiting(room);
  const { invited } = await Lia.inviteFriend({ startParam: room });
  if (!invited) Lia.toast("Share the link to invite someone");
}

// Or a link to send anywhere: opens your app in Lia, or the App Store without it.
async function shareRoom(room) {
  await Lia.share("Play with me: " + Lia.inviteLink(room));
}
```

Each friend picked gets a card in their Lia chat. Lia only lets people invite their friends, and only friends who can open your app (while it's in test, only your testers).

## 3. Land in the room

Read the startParam when your app opens, and listen for one arriving while it's open (the person taps a second invite):

```js
function route(startParam) {
  if (startParam && startParam.startsWith("room-")) joinRoom(startParam);
}

Lia.init().then(function () {
  route(Lia.startParam);
  Lia.ready();
});
Lia.on("startParam", function (data) { route(data.startParam); });

async function joinRoom(room) {
  try {
    await api("/api/rooms/join", { room });
    openTable(room);
  } catch (error) {
    Lia.toast(error.message === "full" ? "That game is full" : "That game has ended");
  }
}
```

A startParam is only a hint: anyone can make a link with any startParam, so your server decides whether the room exists and the player may join.

## 4. Play live

Connect both players to your server (a WebSocket is usual), and verify each player's session before joining them to the room. While a game is on:

```js
Lia.enableClosingConfirmation(); // "Leave the game?" if they close
Lia.disableVerticalSwipes();     // a swipe down won't close the sheet mid move
Lia.onPause(function () { socket.send(JSON.stringify({ type: "away" })); });
Lia.onResume(function () { socket.send(JSON.stringify({ type: "back" })); });
```

## 5. Your turn, by notification

When it's someone's move and they've left your app, a [notification](/guides/send-notifications) with the room as its startParam brings them straight back:

```json
{ "playerId": "p_3f9c0a6d1b2e4f5a6b7c", "text": "@kofi played. Your move.", "startParam": "room-a81c2e09f4" }
```

## Public tables

Invites are for friends. To let anyone on Lia join, also list the room in the [lobby](/concepts/lobby) while it waits:

```js title="server.js"
// When the room is made: list it (as the host, with one seat taken).
await lia("POST", "/lobby/tables", { tableId: room, hostPlayerId: playerId, seats: 2, taken: 1, title: "Quick game" });

// When someone joins from the lobby: seat them through Lia first (it refuses blocked players and full tables).
app.post("/api/rooms/sit", async (req, res) => {
  const playerId = await playerFrom(req.body.session);
  if (!playerId) return res.status(401).end();
  try {
    const { full } = await lia("POST", `/lobby/tables/${encodeURIComponent(req.body.room)}/sit`, { playerId });
    await db.addPlayer(req.body.room, playerId);
    if (full) startGame(req.body.room);
    res.json({ room: req.body.room });
  } catch (error) {
    res.status(409).json({ error: error.data?.error ?? "failed" }); // full, not_found, own_table
  }
});

// When the game starts or the room closes: take it out (safe to repeat).
await lia("DELETE", `/lobby/tables/${encodeURIComponent(room)}`);
```

In your page, show the open tables, and seat people who chose your table in Lia's own lobby (they arrive with the startParam `lobby-<table id>`):

```js
const { tables } = await Lia.lobby.tables();
renderTables(tables, (table) => api("/api/rooms/sit", { room: table.id }));

function route(startParam) {
  if (startParam?.startsWith("lobby-")) return api("/api/rooms/sit", { room: startParam.slice(6) }).then(openTable);
  if (startParam?.startsWith("room-")) return joinRoom(startParam);
}
```

Strangers can't chat through Lia, blocks are respected, and players can [report](/reference/sdk-notifications-and-invites#lia-lobby) each other.

## Limits to know

- One person can invite 20 friends into your app a day, so invites stay something people welcome in their chats.
- A host can have 3 open public tables in your app at a time, and a listing lasts at most an hour.
- `Lia.inviteFriend()` answers `{ invited }`, never who was invited: your server learns who joined when they join.
