A match is a group of lobbies sharing one server. Backfill adds more lobbies to that same
matchId and the same running server, up to the match’s capacity (teams × playersPerTeam).The engine-authoritative hybrid model
PlayFlow runs backfill as an engine-authoritative hybrid:- The engine owns backfill by default. When a lobby queues for a backfill-enabled mode, the matcher first looks for a compatible open match. If it finds one, the lobby joins that running server; if not, it forms a fresh match as usual. The engine tracks capacity, per-team slots, the skill band, and the backfill window — you don’t have to.
- Your game server admits players from an authoritative roster. The engine maintains the single source of truth for who belongs in the match. Your server polls
GET /v3/servers/{instance_id}/rosterand admits connecting players against it. This is what fixes reconnect / “denied by proxy” edge cases — the roster, not the server’s local memory, decides admission. - Advanced servers can optionally take control. A server that knows better when it can accept joiners (e.g., only between rounds) can open, close, or re-scope backfill itself via
PATCH /v3/servers/{instance_id}/backfillandPOST /v3/servers/{instance_id}/backfill/lock.
Enabling backfill
Backfill is configured per matchmaking mode in your lobby config. Add abackfill object to the mode:
backfill fields
Deprecated: allowBackfill
The old boolean flag still works and is treated as a shorthand:
backfill object takes precedence if both are present. Prefer backfill for new configs — the dashboard drops allowBackfill on the next edit. Existing projects on allowBackfill: true keep working unchanged and now get real backfill (not just candidate discovery).
What happens when a lobby queues
For a backfill-enabled mode, the matcher tries to backfill before forming a fresh match:1
Search for an open match
The engine looks for a running match in the same project, lobby config, and mode with
status: in_game and backfill_open: true, whose backfill window hasn’t expired.2
Check compatibility
The candidate match must be region-compatible, have remaining capacity ≥ the joining lobby’s eligible players, and (if a skill gate is set) the lobby’s average skill must fall inside the match’s band.
3
Claim the slots
The engine atomically claims the open slots (assigning players to the teams with the largest deficit) and transitions the lobby to
in_game with the running match’s matchId and server connection info.4
Players connect to the running server
The joined lobby receives the same
server.network_ports[] as everyone already in the match — over the existing SSE stream — and connects immediately. No launch wait.5
No open match? Fresh formation.
If there’s no compatible open match, or another queue racer claims the last slots first, the lobby falls through to normal fresh match formation — a new server launches as usual.
windowSeconds (or until it fills, when closeWhenFull is true). Players who leave during a match free their slots, and the engine reopens backfill if the match is still under capacity and inside its window.
Server integration: admitting players
Your game server is the authority on gameplay, but the engine is the authority on the roster. Your server should admit connecting players against the roster it polls from the engine — not against whatever it happened to see at launch. This is the single change that makes backfill (and reliable reconnects) work.Poll the roster
pf_...) and use the server’s own instance ID. At launch the platform injects these environment variables into your server container — read them rather than hardcoding:
Poll on an interval — every few seconds is plenty.
Response (200):
status is one of in_game, locked, or completed. Keep admitting while it’s in_game; once it’s locked or completed, backfill is closed — stop admitting new joiners and treat the match as sealed.roster_version — cheap change detection
roster_version is a monotonically increasing counter bumped on every roster change (a backfill joiner admitted, a player released, a backfill state toggle). Store the last version you saw and only re-diff your admission list when it changes:
- Version unchanged → nothing to do.
- Version increased → a backfill joiner arrived (or a player left). Reconcile your local player list with
roster.
Admission rule
When a player connects (or reconnects), check whether theirplayerId is in roster:
- In the roster → admit them, and place them on their assigned
team. - Not in the roster → reject the connection.
- Unity (C#)
- Godot (GDScript)
Optional: server-authoritative control
Most games never need this — the engine opens and closes backfill for you based on the window and capacity. But if your server has better knowledge of when it can accept joiners (e.g., only between rounds, or once a boss phase starts), it can take control.Open or close backfill (and re-scope skill)
Response (200):
Close backfill (shorthand)
Close the match to further joiners — a convenience shorthand forPATCH ... { "accepting": false }:
ServerBackfillState shape as PATCH. Both bump roster_version, so a polling server sees the change on its next GET /roster.
Behavior reference
Matchmaking
Modes, teams, skill rules, and how matches form.
Real-time Events
Stream the
in_game transition and server connection info via SSE.