Skip to main content
Backfill routes a newly-queued lobby into an already-running match that has open slots instead of always spinning up a fresh server. Players skip the launch wait and drop straight into a game in progress — ideal for battle royale late-join, persistent arenas, drop-in co-op, and any mode where a half-full match is better filled than a new one is started.
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}/roster and 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}/backfill and POST /v3/servers/{instance_id}/backfill/lock.
You get real backfill for free by flipping one config flag. You reach for the server-authoritative controls only when you need them.

Enabling backfill

Backfill is configured per matchmaking mode in your lobby config. Add a backfill object to the mode:

backfill fields

Backfill only runs for modes where enabled is true. Modes without a backfill object behave exactly as before — no backfill path executes for them.

Deprecated: allowBackfill

The old boolean flag still works and is treated as a shorthand:
is equivalent to:
The structured 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.
When a match forms with backfill enabled, it opens to joiners for 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

Authenticate with your project server API key (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 their playerId is in roster:
  • In the roster → admit them, and place them on their assigned team.
  • Not in the roster → reject the connection.
Because the roster is authoritative and refreshed on every change, a player who was admitted, dropped, and reconnects is still on the roster — so your server admits them instead of denying them at the proxy. Backfill and reconnect resilience are the same mechanism.
Pull is primary. The engine also best-effort mirrors the roster onto your instance so the endpoint is fast, but your server should treat GET /roster as the source of truth and poll it — don’t rely on a push you might miss.

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.
These endpoints require a server API key (pf_...). Client keys (pfclient_...) are allowed unless the project’s client_key_server_control setting is false, in which case they are rejected with 403. The read-only GET /roster endpoint accepts either key.

Open or close backfill (and re-scope skill)

Response (200):

Close backfill (shorthand)

Close the match to further joiners — a convenience shorthand for PATCH ... { "accepting": false }:
Returns the same 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.