> For the complete documentation index, see [llms.txt](https://docs.edgegap.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.edgegap.com/docs/sample-projects/web-games/supabase-sumo-push.md).

# Supabase Sumo Push

supabase multiplayer game backend with edgegap: sign-in, profiles, leaderboard and matchmaking through supabase edge functions, one authoritative node.js server per match on edgegap.

Learn how to use Supabase as the backend of a multiplayer web game hosted on Edgegap.

![A match on an Edgegap server](https://raw.githubusercontent.com/edgegap/netcode-sample-threejs-supabase/main/docs/images/match.jpg)

This sample takes Sumo Push, a [three.js](https://threejs.org/) party game from the [Three.js Minigames](/docs/sample-projects/web-games/threejs-minigames.md) sample, and adds [Supabase](https://supabase.com/) as its backend. Players sign in, keep a profile, climb a live leaderboard, and click **Find match**. Edgegap's matchmaker starts an authoritative node.js server near them, which records the result in Supabase and stops itself when the match is over.

* **Supabase**: sign-in, profiles, wins, match history, leaderboard updates, and the gateway to the matchmaker.
* **Edgegap**: matchmaking, and one dedicated server per match, near its players.

This sample shows:

* **Anonymous sign-in**: every player gets a real Supabase user without an email or password, and keeps it on the next visit. Link an email or OAuth identity later to keep progress across devices.
* **Find match through a Supabase Edge Function**: the page never holds the matchmaker token. The function checks the player's session, joins the matchmaker, and remembers which ticket belongs to which player.
* **Players verified by the game server**: each player sends their Supabase access token when connecting. The server checks its signature against the project's public keys, so it knows who is playing without holding any Supabase key.
* **Results the server reports**: when a match ends, the server sends scores to a second Edge Function. Postgres only accepts players who came with their own, unused ticket for that match, so results can't be forged or counted twice.
* **Row Level Security**: players can read profiles and matches, and rename themselves, but can't change their wins.
* **Live leaderboard** with Supabase Realtime: a match ending anywhere updates every open page.

{% hint style="info" %}
See the [sample repository](https://github.com/edgegap/netcode-sample-threejs-supabase) for the database schema, both Edge Functions, the game server, and an end-to-end test of the whole flow.
{% endhint %}

## ✔️ Preparation

Before we start, you will need:

* [Supabase Sumo Push sample](https://github.com/edgegap/netcode-sample-threejs-supabase).
* [Node.js](https://nodejs.org/) 22 or newer, to run the server and pages locally, and the [Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started) through `npx`.
* [Docker](https://docs.docker.com/get-started/get-docker/), to build the server image.
* A [Supabase account](https://supabase.com/dashboard). The free plan is enough.

☑️ Try the game locally first:

{% code collapsedlinecount="10" %}

```bash
npm install
npm run dev
npm run web
```

{% endcode %}

Open `http://localhost:8080`, click **Local server**, and open a second tab to play against yourself. Without Supabase configured, the server lets anyone in and records nothing. `npm test` plays a full match with two headless players.

## 🗄️ Supabase Backend

### 1. Create the Project

☑️ Create a Supabase project and apply the sample's schema:

{% code collapsedlinecount="10" %}

```bash
npx supabase login
npx supabase projects create edgegap-sumo --org-id <org-id> --region us-east-1
npx supabase link --project-ref <project-ref>
npx supabase db push --remote
```

{% endcode %}

`npx supabase orgs list` shows your organization ID. `db push` creates the `profiles`, `tickets`, `matches` and `match_players` tables, their Row Level Security policies, a trigger giving every new user a profile, and the `record_match()` function.

☑️ In the Supabase dashboard, open **Authentication**, then **Sign In / Providers**, and enable **Allow anonymous sign-ins**.

☑️ Copy `client/config.js` to `client/config.local.js` and fill in your **Project URL** and **Publishable key**, from **Project Settings**, then **API Keys**.

{% hint style="success" %}
The **Supabase publishable key is safe to ship in web pages**: Row Level Security decides what it can read and change. Never put the secret key or the service role key in a page or in the server image.
{% endhint %}

☑️ Reload `http://localhost:8080`: the menu shows your generated player name, your record and the leaderboard. Rename yourself, and the name is saved to your profile.

✅ You may now proceed to the next step.

## ⚡ Deploy and Connect

### 2. Build the Server Image

☑️ To get started, you'll need to [create a free account with Edgegap](https://app.edgegap.com/auth/register). No credit card required.

☑️ Find your [Container Registry](/learn/advanced-features/edgegap-container-registry.md) project name and credentials.

☑️ Sign in to Edgegap's Container Registry with Docker, using your registry project name, username and token:

{% tabs %}
{% tab title="Bash (Linux)" %}

```bash
project='<REGISTRY_PROJECT>';
username='robot$<REGISTRY_ORGANIZATION_ID>+client-push';
token='<REGISTRY_TOKEN>';

echo "$token" | docker login registry.edgegap.com -u "$username" --password-stdin 2>&1
```

{% endtab %}

{% tab title="PowerShell (Windows)" %}

```powershell
$project = '<REGISTRY_PROJECT>'
$username = 'robot$<REGISTRY_ORGANIZATION_ID>+client-push'
$token = '<REGISTRY_TOKEN>'

echo "$token" | docker login registry.edgegap.com -u "$username" --password-stdin 2>&1
```

{% endtab %}
{% endtabs %}

☑️ Build and push the image containing the game server, tagged with the build time:

{% code collapsedlinecount="10" %}

```bash
docker build --platform linux/amd64 -t registry.edgegap.com/<project>/sumo-supabase:<tag> .
docker push registry.edgegap.com/<project>/sumo-supabase:<tag>
```

{% endcode %}

✅ You may now proceed to the next step.

### 3. Create the App Version

☑️ [Create an application](https://app.edgegap.com/application-management/applications/list) named `sumo-supabase`, with one version named after your tag:

* **Image**: `registry.edgegap.com/<project>/sumo-supabase`, tag `<tag>`,
* **Registry credentials**: your Container Registry username and token, so Edgegap can pull the image,
* **Resources**: 0.25 vCPU and 512 MB memory,
* **Port**: `7777`, name `gameport`, protocol **WS**, with **TLS Upgrade** enabled,
* **Environment variables**:
  * `SUPABASE_URL` - your project URL, `https://<project-ref>.supabase.co`,
  * `GAME_SERVER_SECRET` - a long random string, marked **hidden**, shared with Supabase in step 5,
  * `REQUIRE_TICKET` - `1`, to refuse players the matchmaker didn't send to this match,
* **Max duration**: 30 minutes, as a safety net: the server stops itself.

{% hint style="warning" %}
**TLS Upgrade** lets browsers on `https` pages connect to `wss://<id>.pr.edgegap.net:<port>`. Edgegap terminates TLS and forwards plain WebSocket traffic to port `7777`.
{% endhint %}

✅ You may now proceed to the next step.

### 4. Create the Matchmaker

☑️ Set `application.name` and `application.version` in the sample's `edgegap/matchmaker-config.json` to your app and version.

☑️ Open the [Matchmaker](https://app.edgegap.com/matchmaker-management-v2/matchmakers/list) page in the dashboard, click **Create**, upload the config, and click **Create and Start**.

The `sumo` profile matches 2 to 8 players. After 20 seconds, a player waiting alone gets a server, and bots fill the empty seats.

☑️ When the matchmaker shows **Online**, copy its **API URL** and **Auth Token**.

✅ You may now proceed to the next step.

### 5. Deploy the Edge Functions

☑️ Store the matchmaker details and the game server secret in Supabase, then deploy both functions:

{% code collapsedlinecount="10" %}

```bash
npx supabase secrets set EDGEGAP_MM_URL=<API URL> EDGEGAP_MM_TOKEN=<Auth Token> GAME_SERVER_SECRET=<same secret as step 3>
npx supabase functions deploy matchmaking
npx supabase functions deploy report-match
```

{% endcode %}

* `matchmaking` checks the player's session, then joins the matchmaker as a group of one, polls it, and returns the server's `wss://` address and the player's ticket ID. Its `cancel` action leaves the queue by deleting the player's group.
* `report-match` checks the game server's secret, then calls `record_match()`.

{% hint style="info" %}
**Players never call the matchmaker directly.** The Edge Function holds its token, so only signed-in players can queue, and every ticket is tied to a Supabase user. This also means the matchmaker's `allowed_cors_origins` doesn't need your page's origin.
{% endhint %}

✅ You may now proceed to the next step.

### 6. Find a Match

☑️ Open `http://localhost:8080` in two browsers (or a normal and a private window, since one browser profile shares one Supabase session). Click **Find match** in both within 20 seconds.

Each page:

1. calls the `matchmaking` Edge Function every 1.5 seconds until the matchmaker returns `HOST_ASSIGNED`,
2. connects to `wss://<fqdn>:<port>` and sends its ticket ID and Supabase access token,
3. plays: it sends inputs and draws the state the server sends back.

The game server:

1. verifies each player's access token against `https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json`, and their ticket against the match's `MM_TICKET_IDS`,
2. runs the match,
3. sends every player's ticket, user ID, score and place to `report-match`,
4. stops its own deployment after the results screen.

{% hint style="warning" %}
**Cancel searches players abandon**, or the matchmaker can still match them and start a server nobody joins. The sample cancels on the **Cancel** button, on a new **Find match**, and when the page closes mid-search, with a `keepalive` request the browser finishes after the page is gone. A player who leaves once their match is formed still gets a server: it stops itself after `FIRST_JOIN_TIMEOUT_S` (120 seconds) if nobody joins.
{% endhint %}

🙌 Both players are now in the same match, on a server placed near them, and the leaderboard updates on both pages when it ends.

☑️ To test the whole path without a browser, run the end-to-end test. It signs in headless players, finds them a match through the Edge Function, plays it, reads the result back from Supabase, and waits for the deployment to stop itself:

{% code collapsedlinecount="10" %}

```bash
SUPABASE_URL=https://<project-ref>.supabase.co SUPABASE_PUBLISHABLE_KEY=<publishable key> npm run e2e
```

{% endcode %}

## 📦 Ship Your Game

* Host the page anywhere: it's static files with no build step (`index.html`, `client/`, `game/`).
* Let players keep their account across devices by linking an email or OAuth identity to their anonymous user.
* Turn `inspect` off in the matchmaker config before launch.
* Lower `FIRST_JOIN_TIMEOUT_S` (for example to `60`), so servers whose players all left before joining stop sooner.
* Clean up old rows in the `tickets` table, for example with a daily `pg_cron` job.
* To ship a new server build, push a new image tag and point the app version at it. New matches use it, and running ones keep the old build.

{% hint style="info" %}
Using a coding agent? The `edgegap-supabase` skill in [Edgegap's agent skills](https://github.com/edgegap/llm-agent-skills) ships this sample's schema, Edge Functions, server and client changes as templates, and walks your agent through the same steps for your own game.
{% endhint %}
