Skip to main content
Match lobbies provide a central communication hub for players, organizers, and spectators before and during matches. The lobby system includes text chat, voice communication, and presence tracking.

Accessing the Lobby

Lobby access is determined by your role in the match:
computed: {
  canJoinLobby() {
    if (!this.match) return false;

    // Only active matches have lobbies
    if (![
      e_match_status_enum.Live,
      e_match_status_enum.PickingPlayers,
      e_match_status_enum.Scheduled,
      e_match_status_enum.Veto,
      e_match_status_enum.WaitingForCheckIn,
      e_match_status_enum.WaitingForServer,
    ].includes(this.match.status)) {
      return false;
    }

    return (
      this.match.is_in_lineup ||
      this.match.is_organizer ||
      this.match.is_coach
    );
  },
}
Lobbies are automatically shown on the match page for eligible users. No separate connection required.

Lobby Chat

The chat system is implemented via ChatLobby.vue:
<ChatLobby
  class="max-h-96"
  instance="matches/id"
  type="match"
  :lobby-id="match.id"
  :play-notification-sound="match.status !== e_match_status_enum.Live"
  v-if="canJoinLobby"
/>

Features

Real-time Messages

Instant message delivery using WebSocket subscriptions

User Presence

See who’s online and in-game in real-time

Notifications

Sound alerts for new messages (configurable)

Message History

Persistent chat history throughout match lifecycle

Presence Tracking

The match lobby store tracks user presence:
export const useMatchLobbyStore = defineStore("matchLobby", () => {
  const lobbyChat = ref<Record<string, Map<string, unknown>>>({});

  const add = (
    matchId: string,
    user: {
      name: string;
      steam_id: string;
      avatar_url: string;
      inGame: boolean;
    },
  ) => {
    if (!lobbyChat.value[matchId]) {
      lobbyChat.value[matchId] = new Map();
    }
    lobbyChat.value[matchId].set(user.steam_id, user);
  };

  const set = (
    matchId: string,
    users: Array<{ steam_id: string; name: string; avatar_url: string }>,
  ) => {
    if (!lobbyChat.value[matchId]) {
      lobbyChat.value[matchId] = new Map();
    }
    for (const user of users) {
      lobbyChat.value[matchId].set(user.steam_id, user);
    }
  };

  const remove = (matchId: string, user: { steam_id: string }) => {
    lobbyChat.value[matchId]?.delete(user.steam_id);
  };

  return { lobbyChat, add, set, remove };
});

User States

User is connected to the lobby:
  • Avatar displayed in online users list
  • Can send and receive messages
  • Presence indicator shown

Notification Settings

Control notification behavior:
<ChatLobby
  :play-notification-sound="match.status !== e_match_status_enum.Live"
/>
Notifications enabled:
  • Sound alerts for new messages
  • Important for coordination
  • Helps ensure players are ready
Notifications typically muted:
  • Players focused on game
  • Reduces distractions
  • Chat still accessible
Users can override notification settings in their personal preferences.

Chat Header

Match-specific header shows key information:
<ChatMatchHeader :match="match" />
Displays:
  • Match status
  • Team names and scores
  • Quick actions (connect to server, etc.)
  • Time remaining (for scheduled matches)

Lobby Access Control

Organizers can control lobby access:
<template>
  <Popover v-model:open="popoverOpen">
    <div v-if="match.invite_code">
      <ClipBoard :data="matchInviteLink" />
      <span>{{ match.invite_code }}</span>
    </div>

    <PopoverTrigger>
      <Button variant="outline" size="icon">
        <component :is="getIcon(match.options.lobby_access)" />
      </Button>
    </PopoverTrigger>

    <PopoverContent>
      <div class="flex justify-center">
        <Button
          v-for="access in [
            e_lobby_access_enum.Open,
            e_lobby_access_enum.Friends,
            e_lobby_access_enum.Invite,
            e_lobby_access_enum.Private,
          ]"
          @click="updateLobbyAccess(access)"
          :variant="match.options.lobby_access === access ? 'default' : 'outline'"
        >
          <component :is="getIcon(access)" />
          {{ access }}
        </Button>
      </div>
    </PopoverContent>
  </Popover>
</template>

Access Icons

getIcon(access: e_lobby_access_enum) {
  switch (access) {
    case e_lobby_access_enum.Private:
      return Lock;        // 🔒
    case e_lobby_access_enum.Invite:
      return Send;        // ✉️
    case e_lobby_access_enum.Open:
      return Unlock;      // 🔓
    case e_lobby_access_enum.Friends:
      return Handshake;   // 🤝
  }
}

Invite Code Sharing

For invite-only lobbies:
computed: {
  matchInviteLink() {
    const currentRoute = new URL(window.location.href);
    currentRoute.search = "";
    const baseUrl = currentRoute.toString();
    return `${baseUrl}?invite=${this.match.invite_code}`;
  },
}
const inviteLink = `https://5stack.gg/matches/${matchId}?invite=${inviteCode}`;

Tournament Lobbies

Tournament matches have additional lobby features:
const subscribeToChatTournaments = async () => {
  const subscription = getGraphqlClient().subscribe({
    query: generateSubscription({
      tournaments: [
        {
          where: {
            _or: [
              {
                status: { _eq: e_tournament_status_enum.Live },
                joined_tournament: { _eq: true },
              },
              {
                status: { _eq: e_tournament_status_enum.Live },
                is_organizer: { _eq: true },
              },
            ],
          },
        },
        {
          id: true,
          name: true,
          status: true,
        },
      ],
    }),
  });

  subscription.subscribe({
    next: ({ data }) => {
      chatTournaments.value = data?.tournaments || [];
    },
  });
};
Tournament participants automatically get access to lobbies for their matches and the tournament-wide chat.

Voice Communication

While not built into the web interface, lobbies coordinate voice:
1

Join Discord

Most teams use Discord for voice. Link your Discord in settings.
2

Auto-Create Channels

Some tournaments auto-create voice channels for matches.
3

In-Game Voice

CS2 in-game voice is available once connected to the server.

Match Coordination Features

Quick Connect

When match is live:
<QuickMatchConnect 
  :match="match" 
  v-if="match.status === e_match_status_enum.Live"
/>
Provides:
  • One-click connect to server
  • Copy connection string
  • GOTV link for spectators

Schedule Information

<ScheduleMatch :match="match" v-if="match.can_schedule" />
Shown in lobby when:
  • Match is in PickingPlayers status
  • Organizer has scheduling permissions
Displays:
  • Scheduled time
  • Countdown timer
  • Time zone conversions

Check-in Status

<CheckIntoMatch :match="match" v-if="showCheckInSection" />
Lobby shows check-in progress:
  • Who has checked in
  • Who is pending
  • Time remaining to check in

Live Match Lobby

During live matches, the lobby provides:

Server Status

Real-time server health and player count

Score Updates

Live score tracking per map

Pause Notifications

Alerts when match is paused

Admin Controls

Organizer tools for match management

Moderating Chat

Organizers have moderation tools:
Abusive behavior in match lobbies may result in platform-wide penalties.

Lobby Lifecycle

Lobby availability by match status:
const LOBBY_ACTIVE_STATUSES = [
  e_match_status_enum.PickingPlayers,
  e_match_status_enum.Scheduled,
  e_match_status_enum.WaitingForCheckIn,
  e_match_status_enum.Veto,
  e_match_status_enum.WaitingForServer,
  e_match_status_enum.Live,
];
1

Created

Lobby opens when match is created and players start joining.
2

Active

Lobby remains active through veto, check-in, and live play.
3

Closed

Lobby closes 10 minutes after match ends (for post-game discussion).
4

Archived

Chat history remains accessible in match details.

Next Steps

Creating Matches

Set up matches with the right lobby settings

Match Options

Configure lobby access and notifications

Statistics

View performance data from completed matches

Build docs developers (and LLMs) love