Skip to main content
Follow this guide if you want to keep the Unity Game Services (UGS) Matchmaker and use PlayFlow as your server host. When the matchmaker assembles a match, a small Cloud Code allocator you deploy calls PlayFlow to start a server and returns the connection details to the matched players.
Prefer an all-in-one path? PlayFlow has native matchmaking with Unity SDK support — no UGS required. Use this guide when you specifically want to keep the Unity Matchmaker.
Before getting started, we expect that:
Unlike Multiplay, there is no pre-built “PlayFlow allocator” in Unity’s hosting-providers repo — you author a small Cloud Code allocator (below). It’s just an HTTP call to PlayFlow, so this stays a lightweight, fully custom integration.

How it works

1

Matchmaker assembles a match

UGS Matchmaker groups players and invokes your allocator Cloud Code function with the matched player list.
2

Allocator starts a PlayFlow server

Your allocator calls POST /api/v3/servers/start. PlayFlow reserves the ports immediately and returns the instance_id and network_ports.
3

Allocator publishes the assignment

The allocator waits for the server to reach running, then stores the host:port for the match (in UGS Cloud Save / your game data) keyed so the matched players can find it.
4

Clients connect

Each client retrieves its assignment and connects.

Step 1 — Store your PlayFlow key as a UGS secret

In the Unity Cloud dashboard, under Administration → Secrets, add:
  • PLAYFLOW_API_KEY — a PlayFlow server key (pf_...) from your project’s API Keys.
The allocator runs in Cloud Code (server-side), so the key is never exposed to clients.

Step 2 — Region selection

Edgegap places servers from player IP addresses; PlayFlow takes an explicit region instead (one of 13) — there is no IP-based placement. Have each player attach a coarse location or preferred region as matchmaking ticket custom data, then pick a region in the allocator:
Attach region hint to the ticket
var ticketOptions = new CreateTicketOptions(
    queueName: "my-queue",
    attributes: new Dictionary<string, object>
    {
        { "region_hint", "us-east" } // or a lat/long your allocator maps to a region
    }
);
If you send lat/long instead of a region name, reuse the nearestRegion() helper from the PlayFab Bridge guide to map the match’s average location to one of PlayFlow’s regions.

Step 3 — The allocator

Deploy a Cloud Code function that the matchmaker calls when a match is assembled. Its PlayFlow responsibilities are:
  1. Start a server with the matched players and chosen region.
  2. Wait until the server is running.
  3. Store the connection details so clients can look them up.
The PlayFlow calls are identical to any REST client:
Allocator (Cloud Code) — PlayFlow calls
const PLAYFLOW_API = "https://api.computeflow.cloud/api";

async function startPlayFlowServer({ apiKey, region, buildName, matchId, playerIds }) {
  // 1. Start the server
  const start = await fetch(`${PLAYFLOW_API}/v3/servers/start`, {
    method: "POST",
    headers: { "api-key": apiKey, "Content-Type": "application/json" },
    body: JSON.stringify({
      name: `match-${matchId}`,
      region,
      version_tag: buildName,          // the build NAME you uploaded
      ttl: 3600,                       // safety net
      environment_variables: {
        UGS_MATCH_ID: matchId,
        UGS_PLAYER_IDS: playerIds.join(","),
      },
    }),
  }).then((r) => r.json());

  // 2. Wait for the server to be ready
  let instance = start;
  while (instance.status !== "running") {
    await new Promise((res) => setTimeout(res, 2000));
    instance = await fetch(`${PLAYFLOW_API}/v3/servers/${start.instance_id}`, {
      headers: { "api-key": apiKey },
    }).then((r) => r.json());
    if (instance.status === "stopped") throw new Error("Server failed to start");
  }

  // 3. Return the public address of the "game" port
  const game = instance.network_ports.find((p) => p.name === "game");
  return {
    instanceId: instance.instance_id,
    host: game.host,
    port: game.external_port,
  };
}
Because PlayFlow returns network_ports synchronously from the start call, you don’t need Edgegap’s deployment-id-plus-webhook dance. The only wait is for status to flip to running. For very high match rates, kick off the poll asynchronously and store the assignment when it completes rather than blocking the allocator.
Store { host, port, instanceId } against the match (UGS Cloud Save, a database, or the matchmaker’s assignment results) so the matched players — looked up by UGS_PLAYER_IDS — can retrieve it.
For the UGS-side contract — how your allocator is registered, invoked by the matchmaker, and how assignment results are returned to clients — follow Unity’s official hosting-providers allocator template. This guide only covers the PlayFlow calls that go inside it.

Step 4 — Retrieve the assignment on the client

Clients fetch their assignment (via the matchmaker’s standard assignment flow or by reading the game data your allocator wrote), then connect their transport to host:port:
Connect once assigned
InstanceFinder.TransportManager.Transport.SetClientAddress(host);
InstanceFinder.TransportManager.Transport.SetPort(port);
InstanceFinder.ClientManager.StartConnection();

Step 5 — Clean up

When the match ends, terminate the server so you stop paying for it:
Terminate the server
curl -X DELETE "https://api.computeflow.cloud/api/v3/servers/<instance_id>?shutdown_reason=MATCH_ENDED" \
  -H "api-key: pf_your_server_key"
The server can also stop itself — PlayFlow injects PLAYFLOW_API_KEY, PLAYFLOW_API_URL, and PLAYFLOW_INSTANCE_ID into the container, so your headless build can call DELETE /v3/servers/$PLAYFLOW_INSTANCE_ID when the last player disconnects. Setting a ttl at start time guarantees cleanup even if nothing calls delete.