Skip to main content

Matchmaking

5Stack’s matchmaking system automatically pairs players for competitive matches based on skill rating, region latency, and preferences.

Getting Started

Navigate to the Play page to access matchmaking features. You’ll see options for:
  • Automated matchmaking queues
  • Custom match creation
  • Open matches to join
  • Tournament registration
You must be authenticated with Steam and not have any active bans or cooldowns to use matchmaking.

Match Types

5Stack supports multiple competitive formats:

Competitive

Standard 5v5 matches on competitive maps with full rules

Wingman

Fast-paced 2v2 matches on smaller maps

Duel

Intense 1v1 matches for individual skill testing

Match Type Configuration

// Available match types from Matchmaking.vue:262-278
e_match_types: {
  query: generateQuery({
    e_match_types: [
      {
        where: {
          value: {
            _in: [
              e_match_types_enum.Competitive,
              e_match_types_enum.Wingman,
              e_match_types_enum.Duel,
            ]
          }
        }
      },
      { value: true, description: true }
    ]
  })
}
Match types may be enabled or disabled by server administrators. Check the Play page to see currently available queues.

Region Selection and Latency

5Stack uses region-based matchmaking to ensure low-latency matches.

How Latency Testing Works

1

Automatic Testing

When you visit the Play page, 5Stack automatically tests your latency to all available regions
2

WebRTC Measurement

Uses WebRTC data channels to measure actual round-trip time to game servers
3

Result Storage

Latency results are cached in your browser’s local storage
4

Preference Selection

Regions below your acceptable latency threshold are automatically preferred

Latency Testing Implementation

// From MatchmakingStore.ts:359-390
async function getLatency(region: string) {
  return new Promise(async (resolve) => {
    setTimeout(() => {
      resolve(undefined);
    }, 5000);
    try {
      const buffer = new Uint8Array([0x01]).buffer;
      
      const datachannel = await webrtc.connect(region, (data) => {
        if (data === "") {
          datachannel.send(buffer);
          return;
        }
        
        const event = JSON.parse(data) as {
          type: string;
          data: Record<string, unknown>;
        };
        
        if (event.type === "latency-results") {
          datachannel.close();
          latencies.value.set(region, event.data);
        }
      });
      
      datachannel.send("latency-test");
    } catch (error) {
      console.error(`Failed to get latency for ${region}`, error);
      resolve(undefined);
    }
  });
}

Managing Region Preferences

By default, 5Stack queues you in all regions where your latency is below the acceptable threshold (default: 75ms).
// Preferred regions calculation from MatchmakingStore.ts:433-495
const preferredRegions = computed(() => {
  const availableRegions = useApplicationSettingsStore()
    .availableRegions.filter((region) => {
      const regionLatency = getRegionlatencyResult(region.value);
      
      if (regionLatency && region.is_lan && regionLatency.isLan) {
        return true;
      }
      
      if (regionLatency && 
          parseFloat(regionLatency?.latency) > 
          parseFloat(maxAcceptableLatency || "75")) {
        return false;
      }
      
      return true;
    });
  
  // Sort by latency
  return availableRegions.sort((a, b) => {
    if (a.is_lan && !b.is_lan) return -1;
    if (!a.is_lan && b.is_lan) return 1;
    return Number(getRegionlatencyResult(a.value)?.latency) -
           Number(getRegionlatencyResult(b.value)?.latency);
  });
});
LAN regions (local network servers) are automatically prioritized when detected, regardless of latency thresholds.

Joining a Queue

Queue Selection Process

1

Choose Match Type

Click on a match type card (Competitive, Wingman, or Duel)
2

Confirm Selection

Click the same card again within 5 seconds to confirm your choice
3

Enter Queue

You’re added to the matchmaking queue for all your preferred regions
// Queue join handler from Matchmaking.vue:344-374
handleMatchTypeClick(matchType: e_match_types_enum): void {
  if (this.preferredRegions.length === 0) {
    this.showSettings = true;
    toast({
      title: this.$t("matchmaking.no_preferred_regions") as string,
      variant: "destructive",
    });
    return;
  }
  if (this.pendingMatchType === matchType) {
    // Second click - confirm
    if (this.confirmationTimeout) {
      clearTimeout(this.confirmationTimeout);
      this.confirmationTimeout = undefined;
    }
    this.joinMatchmaking(this.pendingMatchType);
    this.pendingMatchType = undefined;
    return;
  }
  
  // First click - show confirmation state
  if (this.confirmationTimeout) {
    clearTimeout(this.confirmationTimeout);
  }
  
  this.pendingMatchType = matchType;
  this.confirmationTimeout = setTimeout(() => {
    this.pendingMatchType = undefined;
    this.confirmationTimeout = undefined;
  }, 5000);
}

Queue Status Display

While in queue, you’ll see:

Queue Information

  • Match type you’re queuing for
  • Time spent in queue
  • Number of players in queue
  • Selected regions

Queue Actions

  • Cancel: Leave the queue
  • Queue automatically cancels if match found
  • Can’t join multiple queues simultaneously
// Queue state from MatchmakingStore.ts:23-42
const joinedMatchmakingQueues = ref<{
  details?: {
    totalInQueue: number;
    type: e_match_types_enum;
    regions: Array<string>;
  };
  confirmation?: {
    matchId: string;
    isReady: boolean;
    expiresAt: string;
    confirmed: number;
    confirmationId: string;
    type: e_match_types_enum;
    region: string;
    players: number;
  };
}>({});

Match Found

When a match is found:
1

Match Confirmation

You’ll receive a notification that a match has been found
2

Accept Prompt

Click Accept within the time limit (usually 30 seconds)
3

Waiting for Others

Wait for all players to accept
4

Match Ready

Once all players accept, you’re redirected to the match lobby
Important: If you fail to accept a match, you’ll receive a temporary matchmaking cooldown. Repeated failures may result in longer cooldowns.

Confirmation Display

The confirmation screen shows:
  • Match type and region
  • Number of confirmed players
  • Time remaining to accept
  • Large Accept button

Lobbies and Invites

Creating a Lobby

You can create a pre-made lobby before queuing:
// Lobby creation from MatchmakingStore.ts:269-283
const createLobby = async () => {
  const { data } = await getGraphqlClient().mutate({
    mutation: typedGql("mutation")({
      insert_lobbies_one: [
        {
          object: {},
        },
        {
          id: true,
        },
      ],
    }),
  });
  return data.insert_lobbies_one.id;
};

Inviting Friends

To invite friends to your lobby:
  1. View your friends list
  2. Click Invite next to a friend’s name
  3. They receive an invite notification
  4. Once they accept, they join your lobby
  5. Queue together as a party
// Invite to lobby from MatchmakingStore.ts:285-309
const inviteToLobby = async (steam_id: string) => {
  const me = useAuthStore().me;
  
  let lobby_id = me?.current_lobby_id;
  
  if (!lobby_id) {
    lobby_id = await createLobby();
  }
  
  await getGraphqlClient().mutate({
    mutation: typedGql("mutation")({
      insert_lobby_players_one: [
        {
          object: {
            steam_id,
            lobby_id,
          },
        },
        { __typename: true },
      ],
    }),
  });
};

Lobby Features

Lobby Settings

  • Set party captain
  • Configure privacy (Open, Friends, Private)
  • View all lobby members

Lobby Chat

  • Text chat with lobby members
  • Voice communication setup
  • Ready status indicators

Friends System

The matchmaking store tracks your friends and their online status:
// Friend subscription from MatchmakingStore.ts:78-148
const subscribeToFriends = async (mySteamId: bigint) => {
  const subscription = getGraphqlClient().subscribe({
    query: generateSubscription({
      my_friends: [
        {},
        {
          elo: true,
          name: true,
          role: true,
          country: true,
          steam_id: true,
          avatar_url: true,
          status: true,
          invited_by_steam_id: true,
          player: {
            is_in_lobby: true,
            is_in_another_match: true,
            lobby_players: [
              {
                limit: 1,
                where: {
                  lobby: {
                    access: {
                      _in: [e_lobby_access_enum.Friends, e_lobby_access_enum.Open]
                    }
                  }
                }
              },
              lobbyFields
            ]
          }
        }
      ]
    })
  });
};

Friend Status Indicators

  • Online: Friend is connected to 5Stack
  • In Lobby: Friend is in a pre-game lobby
  • In Match: Friend is currently playing
  • Offline: Friend is not connected

Matchmaking Restrictions

Cooldowns and Bans

You may be temporarily or permanently restricted from matchmaking for:
Common Restrictions
  • Abandonment: Leaving matches early
  • AFK Detection: Being inactive during matches
  • Toxicity: Reports from other players
  • Failed Accepts: Not accepting found matches
<!-- Ban check from Matchmaking.vue:18-24 -->
<template v-if="me.is_banned">
  <Alert class="my-3">
    <AlertDescription class="flex items-center gap-2">
      <AlertTriangle class="h-4 w-4" />
      {{ $t("matchmaking.banned") }}
    </AlertDescription>
  </Alert>
</template>

Temporary Cooldowns

Cooldowns are time-based restrictions:
<template v-else-if="me.matchmaking_cooldown">
  <Alert class="my-3">
    <AlertDescription class="flex items-center gap-2">
      <AlertTriangle class="h-4 w-4" />
      {{ $t("matchmaking.temp_banned", { time: me.matchmaking_cooldown }) }}
    </AlertDescription>
  </Alert>
</template>
Cooldown durations increase with repeated offenses:
  • 1st offense: 30 minutes
  • 2nd offense: 2 hours
  • 3rd offense: 24 hours
  • 4th+ offense: 7 days

Queue Statistics

The Play page shows real-time queue statistics:
// Queue stats display from Matchmaking.vue:413-435
inQueueStas() {
  const inQueue = {
    [e_match_types_enum.Duel]: 0,
    [e_match_types_enum.Wingman]: 0,
    [e_match_types_enum.Competitive]: 0,
  };
  const regions = this.preferredRegions as Region[];
  for (let i = 0; i < regions.length; i++) {
    const region: Region = regions[i];
    const regionStats = this.regionStats[region.value];
    if (!regionStats) {
      continue;
    }
    inQueue[e_match_types_enum.Duel] += 
      regionStats[e_match_types_enum.Duel] || 0;
    inQueue[e_match_types_enum.Wingman] += 
      regionStats[e_match_types_enum.Wingman] || 0;
    inQueue[e_match_types_enum.Competitive] += 
      regionStats[e_match_types_enum.Competitive] || 0;
  }
  return inQueue;
}
Each match type card displays the number of players currently in queue for your selected regions.

Custom Matches

If you don’t want to use automated matchmaking, you can create a custom match:
1

Click Custom Match

Scroll to the Custom Match section on the Play page
2

Configure Settings

Choose map, game mode, and match options
3

Invite Players

Invite specific players or make the match public
4

Start Match

Once all players are ready, start the match
Custom matches may not affect your ELO rating depending on server configuration.

Troubleshooting

  • Expand your region selection
  • Increase maximum acceptable latency
  • Try queueing during peak hours
  • Check that your selected match type is active
  • Ensure you have at least one region selected
  • Check for active bans or cooldowns
  • Verify your latency tests completed
  • Try refreshing region latencies
  • Check your internet connection
  • Ensure you clicked Accept in time
  • Verify you’re not in another match
  • Contact support if issue persists

Next Steps

Match Lobbies

Learn about match lobby features

Teams

Create a team for organized play

Statistics

Track your competitive performance

Build docs developers (and LLMs) love