Skip to main content
Data & Schema

Not translated yet — showing English.

Access Control

Last updated: 26 August 2026

Access control decides who is allowed to touch each table. It answers a different question from authentication: sign-in establishes who the caller is; access control establishes what that caller may do. The model is deliberately simple — Read and Write per table, Execute per function, for three kinds of caller — composed with per-player row ownership.

Info

By default a game client can read but not write, and a brand-new table is closed to the client entirely. You open access up deliberately, per caller and per table. If a client call is refused for lack of access, the runtime is the authority — a client-side check may refuse early, but it never grants.


The three subjects

Every runtime call resolves to one subject, taken from the credential it already carries.

SubjectWho it is
clientYour game client — the public pk_... key, with or without a signed-in player
serverYour own server — a secret sk_... key (game servers, scripts, CI)
backendYour server-side Functions — the cloud-function plane

The Backoffice (operator) plane is separate and is never limited by these table settings.


Two layers that compose

Access is decided in a fixed order: authority first, then row scope.

  1. Table authority — may this subject read or write this table at all? If not, the call is refused with 403 and nothing else is considered.
  2. Row scope — for a client on a table owned by a player, which rows are visible? A player sees its own rows; another player's row is invisible.
Call + credential→ subjectauthorityRead / Write flagno → 403 forbiddenrow scopeClient on owned tableown rows onlyForeign row404 — invisible

Table authority

Each table carries a Read and a Write flag for every subject. Write covers every mutation — create, update, and delete alike; there is no separate delete permission.

SubjectDefault ReadDefault Write
clientoffoff
serveronon
backendonon

A newly created table starts closed to the client on both axes and open to server and backend. This is why a client Update() on a fresh table is refused until you grant client.write — the write boundary is the safe default, not a bug.

Warning

Granting client.write on a table that is not owned by a player lets any client write any row of it — the shared/world-state case. Only tables owned by a player scope client writes to the caller (see below). Grant open client write deliberately.


Row ownership for clients

On a table marked owned by player, table authority still decides whether a client may touch it; row ownership then decides which rows.

  • Read owner — a client sees only its own rows.
  • Read public — a client sees every row (ownership does not scope reads).
  • Write — a client create stamps the acting player as owner; an update or delete only succeeds on the caller's own row.
  • server and backend are never scoped — they read and write any row.

A row owned by another player is treated as invisible, not as "forbidden": reading, updating, or deleting it returns 404 — the same result a row that does not exist returns. This is intentional, so the write path never reveals a row the read path hides.

Note

Because a foreign row and a missing row are indistinguishable by design, don't rely on the difference between them in client code — both come back as a plain not-found.


Function execute

Each Function carries an Execute flag per subject — client, server, backend — all on by default. A subject without Execute is refused with 403.

Execute is separate from the function's Auth requirement (none or player): Auth is whether an anonymous client may call, Execute is whether the subject is allowed at all. A client call to a player-auth function with no signed-in player is refused with 401 regardless of Execute.


When a call is refused

SituationResult
Subject lacks Read on the table403 table_read_forbidden
Subject lacks Write on the table403 table_write_forbidden
Subject lacks Execute on the function403 function_execute_forbidden
Anonymous client reads an owner-scoped table403 owner_read_requires_player
Anonymous client writes an owned table422 acting_player_required
Client calls a player-auth function with no player401 player_auth_required
Client touches a row owned by another player404 not_found (invisible)
Client reads another player's account403 player_read_forbidden

Tip

If a client write is unexpectedly refused, check the table's client.write flag first, then whether the table is owned by a player and you are acting as the row's owner. The two together explain almost every 403/404 on the data plane.


Next steps

Last updated on 26 Aug 2026