Nakama Matchmaking Cheatsheet (historical)

Lifecycle: HISTORICAL SUPPORTING
Recovered from legacy wiki path content/archive/architecture/nakama-matchmaking.md (attic copy retained; not deleted).
Prefer current SSOT pages for production decisions. Last promoted: 2026-07-20.

Funday's live query does not follow the T/F examples below

The upstream +label.open:T boolean form shown on this page is not what Funday runs. The production browse query is label.game:<id> label.open:true -label.queueType:ranked for casual — no + prefixes, literal true. A +-prefixed term silently misses authoritative lobbies in the live runtime. For the current contract see Lobby, Matchmaking & Nakama.

🎮 Nakama Matchmaking

Updated: 2025-12-22

A practical cheat sheet for:

  • Matchmaker (find opponents → start a new match)
  • Match listing (browse/join existing matches)
  • Parties (keep a group together)

🧭 0) Pick the right feature

Matchmaker

  • Use when: you want the server to form groups from a pool of active players
  • Result: a matchmaker result event containing either a match ID (authoritative) or a token (relayed)

Match listing

  • Use when: you want a “server browser” / lobby list
  • Result: you list running matches and join one immediately

Parties

  • Use when: a pre-made group must enter matchmaking as a unit

Offline matchmaking

  • Use when: players are not online at the same time (server-side, storage-index based)

🧩 1) Core primitives (what you must understand)

Ticket

  • A unique ID representing a matchmaking request
  • A user can have multiple tickets at the same time
  • A match found for one ticket does not automatically cancel the other tickets

Properties

  • Describe the user submitting the ticket
  • Two buckets:
    • string properties (region, mode, platform)
    • numeric properties (rank, mmr, level)

Query

  • Describes what opponents the user wants
  • Query terms match against other users’ ticket properties via the properties. prefix

Matchmaker result

  • Delivered asynchronously via realtime socket event
  • Includes:
    • users (matched presences + their properties)
    • either:
      • match_id (authoritative matches)
      • token (relayed matches)

Counts

  • minCount / maxCount are inclusive of the user submitting the ticket
  • the matchmaker will try to form a match at maxCount first, then smaller sizes down to minCount

🔌 2) Client quickstart (nakama-js)

2.1 Create client + socket

Key calls

  • Create client: new Client(serverKey, host, port, useSSL)
  • Create socket: client.createSocket(useSSL, trace)
  • Connect: await socket.connect(session)

2.2 Join matchmaker pool

Core call

  • await socket.addMatchmaker(query, minCount, maxCount, stringProperties?, numericProperties?)

Required events to handle

  • socket.onmatchmakerticket (store ticket so you can cancel)
  • socket.onmatchmakermatched (match found)

2.3 Join the match you were assigned to

Rules

  • Matched users do not automatically join
  • For relayed matches, the token is short-lived; join immediately

Join patterns

  • Relayed: await socket.joinMatch(null, matched.token)
  • Authoritative: await socket.joinMatch(matched.match_id, null)

2.4 Cancel matchmaking

  • await socket.removeMatchmaker(ticket)
  • Party: await socket.removeMatchmakerParty(partyId, ticket)

🔎 3) Query syntax essentials (matchmaker + match listing)

Query term structure

  • field:value (a space-separated AND)

Operators

  • SHOULD (default): region:europe
  • MUST: +region:europe
  • MUST NOT: -level:>10

Numeric ranges

  • rank:>=5 rank:<=10

Regular expressions

  • mode:/(freeforall|ctf)/

Boosting (ordering)

  • region:europe^3 region:asia^2 region:africa

Escaping

  • Escape these characters in query terms: +-=&|><!(){}[]^"~*?:\\/
  • Space must be escaped too

Boolean fields

  • When querying boolean values, use T / F (true/false)

🎯 4) Matchmaker patterns that actually work

4.1 Skill-based matchmaking (range)

Concept

  • Put the user’s skill in numericProperties
  • Query for a narrow band of opponent skill

Example query

  • +properties.skill:>=1450 +properties.skill:<=1550

4.2 Region preference (soft)

Concept

  • Prefer same region, but allow fallback

Example query

  • properties.region:europe^3 properties.region:asia^2 properties.region:africa

4.3 Expanding criteria (don’t spam identical tickets)

Facts

  • Repeatedly submitting identical requests won’t magically produce different results
  • Prefer widening criteria over time

Practical approach

  • Create a strict ticket
  • If still unmatched, create a looser ticket
  • Cancel old tickets once you commit to a match (or you can get matched twice)

4.4 Matchmaking around blocked users

Concept

  • Include blocked user IDs in a string property
  • Add a MUST NOT regex query term to exclude tickets where the other side blocked you

Pattern (high-level)

  • property: blocked = "id1 id2 id3"
  • query term: -properties.blocked:/.*<my_user_id>.*/

⚙️ 5) Server configuration (matchmaker)

Config keys

  • matchmaker.max_tickets
    • prevents users from submitting abusive numbers of tickets
  • matchmaker.interval_sec
    • how often the server attempts to form matches
    • lower = faster matching, higher server cost
  • matchmaker.max_intervals
    • how many intervals to try at maxCount before allowing minCount
  • matchmaker.rev_precision / matchmaker.rev_threshold
    • optionally enforce bidirectional matching for a short period

Observability

  • Matchmaker Stats API
    • GET /v2/matchmaker/stats (requires auth)
    • returns:
      • ticket_count
      • oldest_ticket_create_time
      • recent completions (create_time / complete_time)

🧠 6) Authoritative matchmaking: create the match on the server

If you want an authoritative match per matched group

  • Register registerMatchmakerMatched in runtime
  • Create a match via nk.matchCreate(...)
  • Return the match ID

High-level TypeScript runtime pattern

  • initializer.registerMatchmakerMatched((ctx, logger, nk, matches) => nk.matchCreate("lobby", {invited: matches}))

Client then

  • receives a matchmaker matched event that contains match_id
  • calls socket.joinMatch(match_id, null)

🏷️ 7) Match listing (server browser / lobbies)

Key idea

  • Match listing is for joining existing matches immediately
  • Matchmaker is for forming a new match from a pool

Label rules

  • To query label fields, the label must be JSON
  • Keep labels small (max 2KB)

Two ways to search

  • Filter (exact label match)
  • Query (flexible criteria via query syntax)

Boolean query reminder

  • +label.open:T (not true/false)

Find-or-create pattern

  • List matches for a label/query
  • If none exist, nk.matchCreate(...)

Client-side listing

  • client.listMatches(session, limit, authoritative, label, minSize, maxSize, query)

Server-side listing

  • nk.matchList(limit, authoritative, label, minSize, maxSize, query)

Label update best practice

  • update label only when a meaningful field changes (open/players/mode)
  • avoid updating every tick

🧑‍🤝‍🧑 8) Party matchmaking

What parties solve

  • ensures a pre-made group stays together during matchmaking

Core flow

  • party leader creates party
  • leader accepts join requests
  • leader calls addMatchmakerParty(...)
  • every party member receives matchmaker matched event
  • every party member joins the match (typically by token)

Cancellation

  • cancel with removeMatchmakerParty(partyId, ticket)

🧯 9) Common pitfalls (seen in real projects)

  • Joining too late

    • relayed match tokens are short-lived → join immediately
  • Forgetting to join at all

    • matchmaker assigns opponents, but you still must call joinMatch
  • Leaving old tickets around

    • a successful match on one ticket does not cancel your other tickets
  • Disconnect during matchmaking

    • pending tickets are cancelled when a user disconnects
  • Overly strict queries

    • use SHOULD terms or widen criteria over time
  • Bad label/query shape

    • label must be JSON to query label fields
    • boolean uses T/F

🧪 10) Debug checklist

Client

  • socket connected before calling addMatchmaker
  • you see onmatchmakerticket fire and you store the ticket
  • you see onmatchmakermatched fire
  • you call joinMatch immediately

Server

  • check /v2/matchmaker/stats to see if tickets exist and are completing
  • confirm server config matchmaker settings are sane
  • if using registerMatchmakerMatched, check logs for match creation errors

🧩 11) Funday notes (optional)

Current Funday production pattern

  • Lobby flow uses match listing + “find-or-create” RPC rather than pure matchmaker

Key integration invariant

  • after you receive a matchId from API/RPC, you must still join via socket:
    • await socket.joinMatch(matchId)

🔗 References

Upstream docs (Heroic Labs)

Local Funday docs

Ask Docs

AI assistant to help answer questions about the documentation. Answers are read-only and cite docs/source.

Hi! How can I help you with the documentation today? Answers are read-only and cite docs/source.

Ctrl+Enter to send