Backend-only tables
By default every database table is app-visible: any credential that can reach your app — including the browser-safe publishable key — can read and write it. That’s the right default for data the client owns.
Some data shouldn’t be directly reachable from the browser at all: audit logs, internal scoring, other users’ records, anything you want to gate behind your own authorization logic. Mark those tables backend-only.
What “backend-only” means
Section titled “What “backend-only” means”Each table has an access class:
app(default) — reachable by any app credential, including a publishable key (pk). The normal client data plane.backend— a publishable key is rejected outright (403). The table is reachable only with a non-publishable credential: a scoped access token (at) or a secret key (sk).
Because the browser only ever holds a pk, a backend-only table is structurally
unreachable from client code. The only path to it is server-side — a
serverless function you write, which applies its own
authorization and acts as the gateway.
browser (pk) ──✗──▶ backend table (403: publishable key rejected)browser (pk) ──▶ function (at) ──▶ backend table (function gates access)Marking a table backend-only
Section titled “Marking a table backend-only”In the developer portal, open your app → Database → the table → set its Visibility to Backend-only. Existing tables default to app-visible, so nothing changes until you opt a table in.
Reading it from a function
Section titled “Reading it from a function”A function receives a scoped access token automatically as
env.MUHKOO_ACCESS_TOKEN (mirrored to env.MUHKOO_APP_KEY), so it can reach
backend tables with no manual key wiring:
import { Client } from "@muhkoo/connect";
export default { async fetch(request, env) { // 1) Apply YOUR authorization first. const user = await authenticate(request); if (!user) return new Response("Unauthorized", { status: 401 });
// 2) The injected token reaches backend-only tables the browser can't. const client = new Client({ apiKey: env.MUHKOO_APP_KEY, baseUrl: env.MUHKOO_API_URL, }); const rows = await client .db("audit_log") .query({ where: [{ column: "user", op: "eq", value: user.id }] });
return Response.json(rows); },};A browser calling the same table directly with the app’s publishable key gets a
403 — exactly the gate you want.
When to use it
Section titled “When to use it”- Sensitive or cross-user data — records a client must never read wholesale.
- Server-authored data — audit trails, computed scores, moderation state.
- Anything gated by custom rules — put the rules in a function and make the table backend-only so the function is the only way in.
For data the client legitimately owns and queries directly, leave the table app-visible — backend-only adds a round-trip through your function.