> ## Documentation Index
> Fetch the complete documentation index at: https://docs.playflowcloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Matchmaking Guide

> Integrate traditional and role-based matchmaking with the lobby system.

PlayFlow's matchmaking system supports both **traditional symmetric matchmaking** (like Fortnite, Counter-Strike) and **advanced role-based asymmetric matchmaking** (like League of Legends, Overwatch, Dead by Daylight). The system seamlessly integrates with lobbies to provide automated matching based on skill, region, roles, and custom criteria.

## Prerequisites

Matchmaking always runs on top of the lobby and player-identity systems, so before starting a search you need:

* **A lobby.** Every match request comes from a lobby (even a solo one). See the [Lobby Quick Start](https://docs.playflowcloud.com/unity/lobbies.md) to create and manage lobbies.
* **A player identity.** Lobby and matchmaking calls require an authenticated player. With a client key and an auth provider configured, pass a valid player token; otherwise a trusted player id is used. See [Player Authentication](https://docs.playflowcloud.com/guides/player-authentication).
* **A matchmaking mode.** Configure at least one mode in your lobby configuration (below) whose name you pass to `FindMatch`.

## Matchmaking Types

### Traditional/Symmetric Matchmaking

Used for games where all players have equal capabilities and teams are symmetrical. Examples include:

* **Battle Royale** (Fortnite, PUBG): 100 players, last one standing
* **Team Deathmatch** (Counter-Strike, Valorant): 5v5 competitive matches
* **Arena Duels**: 1v1 or 2v2 ranked matches
* **Large-scale Battles**: 32v32 or 64v64 team warfare

### Role-Based/Asymmetric Matchmaking

Used for games where players have distinct roles with unique abilities and responsibilities. Examples include:

* **MOBA Games** (League of Legends, Dota 2): Support, Tank, DPS roles with team composition requirements
* **Hero Shooters** (Overwatch, Paladins): Role queue with tanks, healers, and damage dealers
* **Asymmetric Horror** (Dead by Daylight): 4 survivors vs 1 killer
* **Social Deduction** (Among Us, Werewolf): Different roles with hidden information

## How Matchmaking Works

In PlayFlow, matchmaking always works through lobbies:

1. **A player creates a lobby** (for just themself, or for a party of friends)
2. Players set their matchmaking data (skill, region, roles)
3. The **lobby host starts the search**
4. The system finds compatible lobbies based on your configuration
5. When a match is found, a game server automatically starts for all players

This lobby-based approach naturally supports party matchmaking (friends queuing together) while maintaining team cohesion.

## Dashboard Configuration

Before writing code, you must configure your matchmaking rules in the PlayFlow Dashboard.

1. Go to your project's **Configuration** → **Lobbies** tab
2. Create or edit a lobby configuration
3. Click the **Matchmaking** tab
4. Add and configure matchmaking modes

### Traditional Configuration Examples

> [tab] 5v5 Competitive
>
> For tactical shooters like Counter-Strike or Valorant:
>
> ```json theme={null}
> {
>   "Competitive5v5": {
>     "teams": 2,
>     "playersPerTeam": 5,
>     "timeout": 120,
>     "backfill": { "enabled": false },
>     "rules": [
>       {
>         "type": "difference",
>         "field": "mmr",
>         "maxDifference": 200
>       },
>       {
>         "type": "region"
>       }
>     ]
>   }
> }
> ```

> [tab] Battle Royale
>
> For games like Fortnite or PUBG:
>
> ```json theme={null}
> {
>   "BattleRoyaleSolo": {
>     "teams": 100,
>     "playersPerTeam": 1,
>     "minPlayersPerTeam": 1,
>     "timeout": 60,
>     "backfill": { "enabled": true },
>     "rules": [
>       {
>         "type": "difference",
>         "field": "mmr",
>         "maxDifference": 500
>       }
>     ]
>   }
> }
> ```

> [tab] Team Deathmatch
>
> For casual multiplayer modes:
>
> ```json theme={null}
> {
>   "TeamDeathmatch": {
>     "teams": 2,
>     "playersPerTeam": 8,
>     "minPlayersPerTeam": 4,
>     "timeout": 45,
>     "backfill": { "enabled": true },
>     "maxPartySize": 4,
>     "rules": [
>       {
>         "type": "difference",
>         "field": "mmr",
>         "maxDifference": 300
>       }
>     ]
>   }
> }
> ```

> [tab] 1v1 Ranked
>
> For fighting games or RTS duels:
>
> ```json theme={null}
> {
>   "RankedDuel": {
>     "teams": 2,
>     "playersPerTeam": 1,
>     "timeout": 180,
>     "backfill": { "enabled": false },
>     "rules": [
>       {
>         "type": "difference",
>         "field": "mmr",
>         "maxDifference": 100
>       },
>       {
>         "type": "equals",
>         "field": "gameMode"
>       }
>     ]
>   }
> }
> ```

### Role-Based Configuration Examples

> [tab] MOBA (LoL/Dota)
>
> For games like League of Legends with strict role requirements:
>
> ```json theme={null}
> {
>   "RankedRoleQueue": {
>     "teams": 2,
>     "playersPerTeam": 5,
>     "minPlayersPerTeam": 5,
>     "maxPartySize": 2,
>     "excludedFromQueue": ["Spectator", "Coach"],
>     "teamComposition": {
>       "team1": {
>         "top": 1,
>         "jungle": 1,
>         "mid": 1,
>         "adc": 1,
>         "support": 1
>       },
>       "team2": {
>         "top": 1,
>         "jungle": 1,
>         "mid": 1,
>         "adc": 1,
>         "support": 1
>       }
>     },
>     "timeout": 90,
>     "rules": [
>       {
>         "type": "difference",
>         "field": "mmr",
>         "maxDifference": 150
>       }
>     ]
>   }
> }
> ```

> [tab] Hero Shooter (Overwatch)
>
> For role-locked competitive modes:
>
> ```json theme={null}
> {
>   "CompetitiveRoleQueue": {
>     "teams": 2,
>     "playersPerTeam": 6,
>     "minPlayersPerTeam": 6,
>     "maxPartySize": 3,
>     "excludedFromQueue": ["Spectator"],
>     "teamComposition": {
>       "team1": {
>         "tank": 2,
>         "damage": 2,
>         "support": 2
>       },
>       "team2": {
>         "tank": 2,
>         "damage": 2,
>         "support": 2
>       }
>     },
>     "timeout": 120,
>     "backfill": { "enabled": true },
>     "rules": [
>       {
>         "type": "difference",
>         "field": "sr",
>         "maxDifference": 250
>       }
>     ]
>   }
> }
> ```

> [tab] Asymmetric Horror
>
> For games like Dead by Daylight:
>
> ```json theme={null}
> {
>   "AsymmetricHorror": {
>     "teams": 2,
>     "playersPerTeam": 4,
>     "minPlayersPerTeam": 1,
>     "maxPartySize": 4,
>     "excludedFromQueue": ["Spectator"],
>     "teamComposition": {
>       "survivors": {
>         "survivor": 4
>       },
>       "killer": {
>         "killer": 1
>       }
>     },
>     "timeout": 60,
>     "backfill": { "enabled": false },
>     "rules": [
>       {
>         "type": "difference",
>         "field": "rank",
>         "maxDifference": 6
>       }
>     ]
>   }
> }
> ```

> [tab] Social Deduction
>
> For games like Among Us or Town of Salem:
>
> ```json theme={null}
> {
>   "SocialDeduction": {
>     "teams": 2,
>     "playersPerTeam": 8,
>     "minPlayersPerTeam": 6,
>     "maxPartySize": 3,
>     "excludedFromQueue": ["Spectator", "Host"],
>     "teamComposition": {
>       "crew": {
>         "crewmate": 6,
>         "detective": 1,
>         "engineer": 1
>       },
>       "impostors": {
>         "impostor": 2
>       }
>     },
>     "timeout": 45,
>     "backfill": { "enabled": true },
>     "rules": [
>       {
>         "type": "difference",
>         "field": "trustScore",
>         "maxDifference": 200
>       }
>     ]
>   }
> }
> ```

> [tab] MMO Dungeon
>
> For dungeon finder systems:
>
> ```json theme={null}
> {
>   "DungeonFinder": {
>     "teams": 1,
>     "playersPerTeam": 5,
>     "minPlayersPerTeam": 5,
>     "maxPartySize": 5,
>     "excludedFromQueue": ["Offline"],
>     "teamComposition": {
>       "party": {
>         "tank": 1,
>         "healer": 1,
>         "dps": 3
>       }
>     },
>     "timeout": 300,
>     "backfill": { "enabled": true },
>     "rules": [
>       {
>         "type": "difference",
>         "field": "itemLevel",
>         "maxDifference": 40
>       }
>     ]
>   }
> }
> ```

### Backfill Configuration

Backfill routes newly-queued players into an already-running match that still has open slots, instead of always spinning up a fresh server. Configure it with the `backfill` object on a mode:

```json theme={null}
{
  "TeamDeathmatch": {
    "teams": 2,
    "playersPerTeam": 8,
    "minPlayersPerTeam": 4,
    "timeout": 45,
    "backfill": {
      "enabled": true,
      "windowSeconds": 300,
      "closeWhenFull": true,
      "skill": { "field": "mmr", "maxDifference": 300 }
    }
  }
}
```

| Field | Default | Purpose |
| - | - | - |
| `enabled` | `false` | Master switch. Omit or set `false` to disable backfill. |
| `windowSeconds` | `300` | How long after a match forms it stays open to joiners. |
| `closeWhenFull` | `true` | Stop accepting backfill once the match reaches capacity. |
| `skill` | none | Optional skill gate — a joining lobby's average `field` must be within `maxDifference` of the match's band. |

> [!NOTE]
>
> `allowBackfill: true` is still accepted as a deprecated alias for `backfill: { "enabled": true }`, but prefer the object form so you can tune the window and skill gate.

## Implementation Guide

Here are the steps to implement matchmaking in your game.

### Step 1: Set Player Matchmaking Data

Players must set their matchmaking attributes before searching. This includes MMR, regions, and roles (for role-based modes).

> [tab] Unity
>
> #### Traditional Matchmaking Data
>
> For traditional modes, set MMR and acceptable regions:
>
> ```csharp theme={null}
> using System.Collections.Generic;
> using PlayFlow;
> using UnityEngine;
>
> public class TraditionalMatchmaking : MonoBehaviour
> {
>     public void SetTraditionalMatchmakingData(int playerMMR)
>     {
>         var matchmakingData = new Dictionary<string, object>
>         {
>             { "mmr", playerMMR },
>             { "regions", new List<string> { "us-west", "us-east", "eu-west" } }
>         };
>
>         PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(matchmakingData,
>             onSuccess: (lobby) => {
>                 Debug.Log("Player matchmaking data has been set.");
>             },
>             onError: (error) => {
>                 Debug.LogError($"Could not set matchmaking data: {error}");
>             }
>         );
>     }
> }
> ```
>
> #### Role-Based Matchmaking Data
>
> For role-based modes, also specify your role preferences:
>
> ```csharp theme={null}
> public class RoleBasedMatchmaking : MonoBehaviour
> {
>     // Single specific role (MOBA player who only plays one lane)
>     public void SetSpecificRole(string role, int playerMMR)
>     {
>         var matchmakingData = new Dictionary<string, object>
>         {
>             { "role", role }, // e.g., "jungle", "mid", "support"
>             { "mmr", playerMMR },
>             { "regions", new List<string> { "us-east", "us-west" } }
>         };
>
>         PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(matchmakingData);
>     }
>
>     // Priority array of roles (flexible fill player)
>     public void SetFlexibleRoles(int playerMMR)
>     {
>         var matchmakingData = new Dictionary<string, object>
>         {
>             { "role", new List<string> { "support", "adc", "mid" } }, // Preference order
>             { "mmr", playerMMR },
>             { "regions", new List<string> { "us-east", "eu-west" } }
>         };
>
>         PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(matchmakingData);
>     }
>
>     // Spectator role (excluded from matchmaking)
>     public void SetSpectatorRole()
>     {
>         var matchmakingData = new Dictionary<string, object>
>         {
>             { "role", "Spectator" }, // Will be excluded from team requirements
>             { "regions", new List<string> { "us-east" } }
>         };
>
>         PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(matchmakingData);
>     }
> }
> ```

> [tab] Unreal Engine
>
> Coming soon...

> [tab] Godot
>
> Coming soon...

### Step 2: Start the Search

Once the player's data is set, the lobby host can start the search by calling `FindMatch`. The `mode` parameter must match the name of the matchmaking mode you created in the dashboard.

> [tab] Unity
>
> ```csharp theme={null}
> public class MatchmakingStarter : MonoBehaviour
> {
>     // Traditional matchmaking (Competitive5v5, Duels, BattleRoyale, etc.)
>     public void StartTraditionalSearch(string mode = "Competitive5v5")
>     {
>         var manager = PlayFlowLobbyManagerV2.Instance;
>
>         if (!manager.IsInLobby || !manager.IsHost)
>         {
>             Debug.LogWarning("Must be the host of a lobby to start matchmaking.");
>             return;
>         }
>
>         manager.FindMatch(mode,
>             onSuccess: (lobby) => {
>                 Debug.Log($"Successfully entered the '{lobby.matchmaking.mode}' queue.");
>                 // Update your UI to show a "searching..." state
>             },
>             onError: (error) => {
>                 Debug.LogError($"Failed to start matchmaking: {error}");
>             }
>         );
>     }
>
>     // Role-based matchmaking (RankedRoleQueue, AsymmetricHorror, etc.)
>     public void StartRoleBasedSearch(string mode = "RankedRoleQueue")
>     {
>         var manager = PlayFlowLobbyManagerV2.Instance;
>
>         if (!manager.IsInLobby || !manager.IsHost)
>         {
>             Debug.LogWarning("Must be the host of a lobby to start matchmaking.");
>             return;
>         }
>
>         // Ensure all players have set their roles
>         var currentLobby = manager.CurrentLobby;
>         bool allPlayersHaveRoles = true;
>
>         foreach (var playerId in currentLobby.players)
>         {
>             if (currentLobby.lobbyStateRealTime.TryGetValue(playerId, out var playerState))
>             {
>                 if (!playerState.ContainsKey("role"))
>                 {
>                     Debug.LogWarning($"Player {playerId} has not set their role!");
>                     allPlayersHaveRoles = false;
>                 }
>             }
>         }
>
>         if (!allPlayersHaveRoles)
>         {
>             Debug.LogError("All players must set their role before matchmaking!");
>             return;
>         }
>
>         manager.FindMatch(mode,
>             onSuccess: (lobby) => {
>                 Debug.Log($"Entered role-based queue: {lobby.matchmaking.mode}");
>                 // Show searching UI with role information
>             },
>             onError: (error) => {
>                 Debug.LogError($"Failed to start role-based matchmaking: {error}");
>             }
>         );
>     }
> }
> ```

> [tab] Unreal Engine
>
> Coming soon...

> [tab] Godot
>
> Coming soon...

### Step 3: Handle Matchmaking Events

After starting the search, subscribe to events to drive your UI.

| Event | When it fires |
| - | - |
| `OnMatchmakingStarted` | Lobby entered the queue (status → `in_queue`) |
| `OnQueueStats` | Every \~10s while queueing — live wait-time telemetry |
| `OnMatchAwaitingConfirmation` | Match proposal pending (only if `matchConfirmation.enabled`) |
| `OnMatchConfirmed` | This lobby accepted; waiting on others |
| `OnMatchDeclined` | Someone declined or timed out — back to `waiting` |
| `OnMatchFound` | Match committed, server launching |
| `OnMatchRunning` | Server is ready to accept connections |
| `OnMatchServerDetailsReady` | Full port list (for multi-port games) |
| `OnMatchmakingCancelled` | Host called `CancelMatchmaking()` |
| `OnMatchmakingTimeout` | Queue timed out without finding a match |

> [tab] Unity
>
> ```csharp theme={null}
> void Start()
> {
>     var events = PlayFlowLobbyManagerV2.Instance.Events;
>
>     events.OnMatchmakingStarted.AddListener(HandleMatchmakingStarted);
>     events.OnQueueStats.AddListener(HandleQueueStats);
>     events.OnMatchFound.AddListener(HandleMatchFound);
>     events.OnMatchRunning.AddListener(HandleMatchRunning);
>     events.OnMatchServerDetailsReady.AddListener(HandleServerDetailsReady);
>     events.OnMatchmakingCancelled.AddListener(HandleMatchmakingCancelled);
>     events.OnMatchmakingTimeout.AddListener(HandleMatchmakingTimeout);
> }
>
> private void HandleMatchmakingStarted(Lobby lobby)
> {
>     Debug.Log($"In queue for mode: {lobby.matchmaking.mode}");
>     // Show "Searching for match…" UI
> }
>
> private void HandleQueueStats(QueueStats stats)
> {
>     // Update your UI: "42 players searching · ~12s avg wait"
>     Debug.Log($"{stats.playersSearching} searching, {stats.avgWaitSeconds:F0}s avg wait");
> }
>
> private void HandleMatchFound(Lobby lobby)
> {
>     Debug.Log("Match committed! Server launching…");
>     // Update UI to "Connecting…"
> }
>
> private void HandleMatchRunning(ConnectionInfo connectionInfo)
> {
>     // Quick-access for single-port games
>     MyNetworkManager.Connect(connectionInfo.Ip, connectionInfo.Port);
> }
>
> private void HandleServerDetailsReady(List<PortMappingInfo> portMappings)
> {
>     // Preferred: look up by the port name you defined in the dashboard
>     var lobby = PlayFlowLobbyManagerV2.Instance.CurrentLobby;
>     if (lobby.TryGetPort("game_udp", out var port))
>     {
>         Debug.Log($"Connecting to {port.host}:{port.external_port}");
>         MyNetworkManager.Connect(port.host, port.external_port);
>     }
> }
>
> private void HandleMatchmakingCancelled(Lobby lobby) { /* back to lobby */ }
> private void HandleMatchmakingTimeout(Lobby lobby)   { /* show "no match found" */ }
> ```

> [tab] Unreal Engine
>
> Coming soon...

> [tab] Godot
>
> Coming soon...

### Step 3.5: CS2-Style "Accept Match" flow (optional)

If the matchmaking mode has `matchConfirmation.enabled: true` in the dashboard, matches pause at the `match_found` status waiting for every lobby to explicitly accept. Opt in by subscribing to three extra events and exposing accept/decline buttons.

```csharp theme={null}
void Start()
{
    var events = PlayFlowLobbyManagerV2.Instance.Events;
    events.OnMatchAwaitingConfirmation.AddListener(HandleAwaitingConfirmation);
    events.OnMatchConfirmed.AddListener(_  => Debug.Log("You accepted. Waiting on other players…"));
    events.OnMatchDeclined.AddListener(_   => Debug.Log("Match cancelled — re-queue when ready."));
}

void HandleAwaitingConfirmation(Lobby lobby)
{
    // Show your "Accept Match" dialog
    var deadlineIso = lobby.matchmaking.confirmation.deadline;
    Debug.Log($"Match found — accept before {deadlineIso}");
}

public void OnAcceptClicked()  => PlayFlowLobbyManagerV2.Instance.ConfirmMatch();
public void OnDeclineClicked() => PlayFlowLobbyManagerV2.Instance.DeclineMatch();
```

If **any** player declines or the deadline expires, every participating lobby returns to `waiting` (not back to `in_queue`) — players re-queue manually when ready. This matches how CS2 and League of Legends handle match acceptance.

### Step 4: Cancel the Search

The host can cancel the matchmaking search at any time by calling `CancelMatchmaking`.

> [tab] Unity
>
> ```csharp theme={null}
> public void CancelSearch()
> {
>     var manager = PlayFlowLobbyManagerV2.Instance;
>
>     if (!manager.IsHost || manager.CurrentLobby?.status != "in_queue")
>     {
>         return; // Can only cancel if you are the host and in the queue
>     }
>
>     manager.CancelMatchmaking(
>         onSuccess: (lobby) => {
>             Debug.Log("Successfully cancelled matchmaking.");
>         },
>         onError: (error) => {
>             Debug.LogError($"Failed to cancel matchmaking: {error}");
>         }
>     );
> }
> ```

> [tab] Unreal Engine
>
> Coming soon...

> [tab] Godot
>
> Coming soon...

## Advanced Features

### Role Assignment Algorithm

The role-based matchmaking system uses an intelligent priority-based algorithm to assign players to roles. Roles can be provided as either a single string or a string array for flexibility.

> [!NOTE]
>
> **Key Principle**: Players with fewer role options get priority assignment to ensure everyone gets a suitable role. This is NOT random - it's deterministic and fair.

#### How Priority Assignment Works

**1. Specificity First**
Players with fewer role options are assigned before flexible players:

```csharp theme={null}
// Assignment priority (highest to lowest):
Player A: ["tank"]                    // 1 option - HIGHEST PRIORITY
Player B: ["tank", "healer"]          // 2 options - medium priority
Player C: ["tank", "healer", "dps"]   // 3 options - lowest priority

// Player A gets first choice, then B, then C
```

**2. Preference Order Matters**
When assigned, the system tries roles left-to-right in your array:

```csharp theme={null}
// Player's preference array:
["support", "adc", "mid"]

// System tries:
// 1st: Can they be support? → If yes, assigned!
// 2nd: If not, can they be adc? → If yes, assigned!
// 3rd: If not, can they be mid? → If yes, assigned!
```

**3. Tie-Breaking**
When players have identical preferences, first-come-first-served applies:

```csharp theme={null}
// Both want the same roles:
Player1: ["support", "adc"]  // Joined lobby first
Player2: ["support", "adc"]  // Joined lobby second

// Result:
Player1 → Gets "support" (first preference)
Player2 → Gets "adc" (support taken, gets second choice)
```

#### Real-World Example

For a MOBA team needing specific roles:

```csharp theme={null}
// Team needs: 1 top, 1 jungle, 1 mid, 1 adc, 1 support

// Players with their preferences:
Player1: ["mid", "top", "support"]     // Flexible
Player2: ["jungle"]                    // One-trick (gets priority!)
Player3: ["support", "adc"]            // Support main
Player4: ["mid"]                       // Mid only
Player5: ["top", "mid", "jungle"]      // Fill player

// Assignment result:
Player2 → jungle (most specific, only wants jungle)
Player4 → mid (most specific for mid)
Player3 → support (first preference available)
Player5 → top (mid/jungle taken, gets top)
Player1 → adc (forced to fill, but match succeeds!)
```

#### Setting Role Preferences in Code

```csharp theme={null}
public class RolePreferenceManager : MonoBehaviour
{
    // Specific player - highest priority but risky
    public void SetOneRoleOnly(string role)
    {
        var data = new Dictionary<string, object> {
            { "role", role }  // Single string
        };
        PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(data);
    }

    // Flexible player - lower priority but guarantees match
    public void SetMultipleRoles(List<string> roles)
    {
        var data = new Dictionary<string, object> {
            { "role", roles }  // String array in preference order
        };
        PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(data);
    }

    // Ultra-flexible - can play anything
    public void SetFillPlayer()
    {
        var allRoles = new List<string> {
            "top", "jungle", "mid", "adc", "support"
        };
        var data = new Dictionary<string, object> {
            { "role", allRoles }
        };
        PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(data);
    }
}
```

> [!WARNING]
>
> **Edge Case**: If too many players are inflexible (single role only) and want the same role, the match will fail. Always encourage some flexibility in your player base!

### Spectator Support

Spectators are excluded from team requirements but still receive game server access for observation.

```csharp theme={null}
public class SpectatorManager : MonoBehaviour
{
    public void JoinAsSpectator()
    {
        // Set spectator role
        var spectatorData = new Dictionary<string, object>
        {
            { "role", "Spectator" },
            { "regions", new List<string> { "us-east" } }
        };

        PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(spectatorData);
    }

    private void OnMatchFound(Lobby lobby)
    {
        // Spectators still get notified and can connect
        if (IsSpectator())
        {
            Debug.Log("Joined match as spectator!");
            // Connect to server in spectator mode
        }
    }
}
```

### Regional Matchmaking

Players can specify multiple acceptable regions. The system finds matches where players have overlapping regions.

```csharp theme={null}
// Specify regions in order of preference
var regionList = new List<string> { "us-east", "us-west", "eu-west" };
UpdatePlayerState(new Dictionary<string, object> {
    { "regions", regionList }
});
```

### Party Support

The lobby-based approach naturally supports parties. Friends can join the same lobby and queue together:

```csharp theme={null}
public class PartyManager : MonoBehaviour
{
    public void CreatePartyLobby()
    {
        // Create lobby for your party
        PlayFlowLobbyManagerV2.Instance.CreateLobby(
            name: "My Party",
            maxPlayers: 4,  // Your party size
            isPrivate: true,
            onSuccess: (lobby) => {
                Debug.Log($"Party lobby created! Share code: {lobby.code}");
            }
        );
    }

    public void QueueWithParty(string mode)
    {
        // Everyone in the lobby queues together
        PlayFlowLobbyManagerV2.Instance.FindMatch(mode);
    }
}
```

## Troubleshooting

### Common Issues

> [accordion] Players not matching despite being in queue
>
> * Verify all players have set required matchmaking data (MMR, regions, roles)
> * Check that players have overlapping regions in their `regions` field
> * For role-based modes, ensure you have the exact roles needed for team composition
> * Check dashboard configuration timeout settings

> [accordion] Spectators being counted in matchmaking
>
> Ensure spectator roles are listed in the `excludedFromQueue` array in your dashboard configuration:
>
> ```json theme={null}
> "excludedFromQueue": ["Spectator", "Observer"]
> ```

> [accordion] Role assignment not working correctly
>
> * Players with single specific roles are prioritized over flexible players
> * Ensure role names match exactly with dashboard configuration
> * Check that total players match team requirements

## Next Steps

> [card] [Lobby Quick Start](https://docs.playflowcloud.com/unity/lobbies.md)
>
> Review the basics of creating and managing lobbies.

> [card] [Lobby Events Reference](https://docs.playflowcloud.com/unity/lobby-events.md)
>
> See all events for building responsive matchmaking UI.

> [card] [Game Servers](https://docs.playflowcloud.com/unity/game-servers.md)
>
> Learn about server lifecycle and configuration.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.