Players can join matches through several methods depending on the lobby access settings. This guide covers all ways to join and manage your participation.
Finding Matches
Discover available matches:
Open Matches
View all publicly available matches on the manage matches page:
// Subscription tracks open matches
const subscription = getGraphqlClient (). subscribe ({
query: generateSubscription ({
matches_aggregate: [
{
where: {
status: {
_eq: e_match_status_enum . PickingPlayers ,
},
options: {
lobby_access: {
_eq: e_lobby_access_enum . Open ,
},
},
},
},
{
aggregate: {
count: true ,
},
},
],
}),
});
subscription . subscribe ({
next : ({ data }) => {
openMatchesCount . value = data ?. matches_aggregate ?. aggregate ?. count || 0 ;
},
});
The match lobby store (stores/MatchLobbyStore.ts) maintains real-time counts of open matches, live matches, and tournaments.
Your Matches
Track matches you’re involved in:
// Subscribe to your matches
const subscribeToMyMatches = async () => {
const me = useAuthStore (). me ;
if ( ! me ?. steam_id ) return ;
const subscription = getGraphqlClient (). subscribe ({
query: generateSubscription ({
matches: [
{
where: {
_or: [
{
is_in_lineup: { _eq: true },
status: { _in: activeStatuses },
},
{
organizer_steam_id: { _eq: me . steam_id },
status: { _in: activeStatuses },
},
],
},
order_by: [{ created_at: order_by . desc }],
},
simpleMatchFields ,
],
}),
});
};
Lobby Access Modes
Matches have different access controls:
Open
Invite
Friends
Private
Anyone can join freely: < JoinLineupForm
v-if = " match . options . lobby_access === e_lobby_access_enum . Open "
: match = " match "
: lineup = " lineup "
/>
No invite code required
Instant join
Perfect for public games
Requires an invite code: < FormField name = "code" v-slot = " { componentField } " >
<FormItem>
<FormControl>
<Input
v-bind="componentField"
placeholder="Enter invite code"
/>
</FormControl>
</FormItem>
</ FormField >
Match organizer shares code
Code displayed on match page for organizers
Can be shared via link: ?invite=CODE
Only Steam friends can join:
Must be Steam friends with match organizer
Automatic verification
No code needed
Manual addition only:
Organizer or captain must add players
No self-join option
Full roster control
Joining a Lineup
The join process is handled by JoinLineupForm.vue:
< template >
< form @ submit . prevent = " joinLineup " class = "flex gap-4" >
< FormField
v-slot = " { componentField } "
name = "code"
v-if = " match . options . lobby_access === e_lobby_access_enum . Invite "
>
< FormItem >
< FormControl >
< Input v-bind = " componentField " ></ Input >
</ FormControl >
< FormMessage />
</ FormItem >
</ FormField >
< Button variant = "outline" class = "flex gap-4" >
< UserPlusIcon class = "h-4 w-4" />
{{ $t ( "match.overview.join" ) }}
</ Button >
</ form >
</ template >
< script lang = "ts" >
export default {
methods: {
async joinLineup () {
await this . $apollo . mutate ({
mutation: generateMutation ({
joinLineup: [
{
match_id: this . match . id ,
lineup_id: this . lineup . id ,
code: this . form . values . code || this . $route . query . invite || "" ,
},
{
__typename: true ,
},
],
}),
});
this . $emit ( "joined" );
},
} ,
} ;
</ script >
Join via Invite Link
Invite links automatically populate the code:
watch : {
$route : {
immediate : true ,
handler () {
if ( ! this . $route . query . invite ) return ;
this . form . setFieldValue ( "code" , this . $route . query . invite );
},
},
}
Example invite link:
https://5stack.gg/matches/abc-123?invite=XYZ789
After joining, the invite code is removed from the URL for security.
Lineup Management
Once in a match, you can see the lineup status:
Viewing Lineups
The LineupOverview.vue component displays team rosters:
< Table class = "min-w-[480px]" >
<TableHeader>
<TableRow>
<TableHead class="w-[220px] text-left">
<div class="flex items-center gap-4">
<!-- Ready indicator for check-in status -->
<div v-if="match.status === e_match_status_enum.WaitingForCheckIn">
<span
class="animate-ping rounded-full"
:class="{
'bg-red-600': !lineup.is_ready,
'bg-green-600': lineup.is_ready,
}"
/>
</div>
<span>{{ lineup.name }}</span>
</div>
</TableHead>
<!-- Stats columns -->
</TableRow>
</TableHeader>
<TableBody>
<LineupOverviewRow
v-for="member of lineup.lineup_players"
:member="member"
:lineup="lineup"
/>
<!-- Empty slots -->
<TableRow
v-for="slot of maxPlayers - lineup.lineup_players.length"
>
<TableCell>
<PlayerDisplay
:player="{
name: `Slot ${slot} ${slot > minPlayers ? '(Substitute)' : ''}`
}"
/>
</TableCell>
</TableRow>
</TableBody>
</ Table >
Player Slots
Matches have configurable player limits:
match . min_players_per_lineup // Required to start (usually 5)
match . max_players_per_lineup // Maximum allowed (5 + substitutes)
// Substitutes
match . options . number_of_substitutes // 0-5 additional players
Starting Players First min_players_per_lineup slots are starting players.
Substitutes Additional slots are substitutes who can swap in during the match.
Adding Players (Organizers/Captains)
Organizers and captains can manually add players:
< AssignPlayerToLineup
v-if = " lineup . can_update_lineup "
: lineup = " lineup "
: exclude = " excludePlayers "
: match-id = " match . id "
/>
The component prevents adding:
Players already in the match
Players assigned as coaches
Duplicate entries
computed : {
excludePlayers () {
const players = [];
players . push ( ... match . lineup_1 . lineup_players );
players . push ( ... match . lineup_2 . lineup_players );
if ( match . lineup_1 . coach ) players . push ( match . lineup_1 . coach );
if ( match . lineup_2 . coach ) players . push ( match . lineup_2 . coach );
return players ;
},
}
Updating Team Names
Lineup captains can customize team names:
async updateLineupName ( lineup_id : string , name : string ) {
await this . $apollo . mutate ({
mutation: generateMutation ({
update_match_lineups_by_pk: [
{
pk_columns: { id: lineup_id },
_set: { team_name: name },
},
{ __typename: true },
],
}),
variables: { name },
});
}
Team names default to the linked team’s name or “Team 1” / “Team 2” for PUGs.
Assigning Coaches
If coaches are enabled, they can be assigned:
< AssignCoachToLineup
v-if = " lineup . can_update_lineup && match . options . coaches "
: lineup = " lineup "
: exclude = " excludePlayers "
/>
Coaches:
Don’t count toward player limits
Can’t also be players in the match
Have tactical timeout permissions (if configured)
Are spawned and killed each round
Coach support requires server-side configuration. Not all servers support coaching features.
Check-in Process
Before the match starts, players may need to check in:
< template >
< div v-if = " isInMatch && match . can_check_in " >
< div v-if = " ! isCheckedIn " >
< Button @ click = " checkIn " >
{{ $t ( "match.check_in.check_in" ) }}
</ Button >
</ div >
< Badge v-else variant = "secondary" >
{{ $t ( "match.check_in.checked_in" ) }}
</ Badge >
< p class = "text-xs" >
{{ totalCheckedIn }} / {{ playersRequiredToStart }} checked in
</ p >
</ div >
</ template >
Check-in requirements vary by match settings:
All Players
Captains Only
Admin Only
Every starting player must check in: match . options . check_in_setting === e_check_in_settings_enum . Players
playersRequiredToStart = match . min_players_per_lineup * 2 // e.g., 10
Only team captains check in: match . options . check_in_setting === e_check_in_settings_enum . Captains
playersRequiredToStart = 2
Organizer controls check-in: match . options . check_in_setting === e_check_in_settings_enum . Admin
playersRequiredToStart = 1
Match Invites
Organizers can send direct invites:
// Invite system tracks pending invitations
match . invites : [{
steam_id: string
}]
// Players receive notifications
< MatchInviteNotification : invite = "invite" />
Leaving a Match
Players can leave before the match starts:
Once a match goes live, leaving may result in penalties or forfeit.
Next Steps
Match Lobbies Using chat and voice in match lobbies
Match Options Understanding match configuration options
Statistics Viewing your match performance