Permissions and write-gating

Two independent settings decide who can write what in your app: the glow's auth mode (a deploy/console setting — is this glow open to the world, restricted to an allowlist, or open self-serve?) and each signed-in user's permission (admin / user / viewer). This guide is about designing a UI around the combination of the two — which is where a static-page mental model tends to go wrong.

The reference documents each layer on its own: the auth-mode surface matrix says what every surface does for each mode-and-sign-in state, and backlit.auth plus auth.me() carry the permission field. This guide links to those rather than restating them, so it can't drift out of sync with the rules they define.

Plain text All guides SDK reference

Two dials, not one

The trap is treating "signed in" as "can write." It isn't: a signed-in viewer reaches the app but still can't write shared data. Auth mode gets the user in; permission decides what they can do.

The server is the gate — your UI just mirrors it

Every write surface enforces its own rule server-side, and a blocked write throws a specific code. You don't invent the gate; you read it off the reference and reflect it in the UI so the user isn't surprised by a rejection. The authoritative gate for each store lives with that store:

The two error codes you'll design against are the forbidden and unauthenticated rows in the error-code table — one means "signed in, but not allowed," the other "no user session at all." Consult the table for which call raises which; the point here is only that a good UI never lets the user trip them.

The recipe: pick the store by who's allowed to write

Instead of memorizing the matrix, run each thing your app writes through two questions — and a default:

  1. Can a signed-out visitor write it? If yes, that's a capture — a contact form, a telemetry ping, an anonymous submission the page can't read back. It's the only store whose create works with no session.
  2. Should each signed-in user own their own entries, viewers included? If yes, that's records — comments, posts, profiles. A freshly self-served Accounts viewer can post to records but not to shared data, which makes records the natural home for user-generated content on an open glow.
  3. Otherwise it's shared state everyone sees but only privileged users edit — that's data, gated to a signed-in non-viewer.

Choosing the right store is the permission design. If you find yourself wishing a viewer could write shared data, you probably wanted records.

Gate the control, not just the call

Because permission is readable before any write, branch your UI on it so a user never sees a control they'd be rejected for using. The canonical move is to disable the write affordance for a viewer:

const me = await backlit.auth.me();

// Anonymous visitor: no user session at all.
if (me === null) {
  saveBtn.hidden = true;
  // On an Accounts glow you can offer sign-in here; see below.
} else {
  // Signed in — but a viewer can't write shared data. Reflect that.
  const canWriteShared = me.permission === "user" || me.permission === "admin";
  saveBtn.disabled = !canWriteShared;
  adminPanel.hidden = me.permission !== "admin"; // capture review, user list, …
}

This is a mirror, not the enforcement — the server still rejects a viewer's shared-data write no matter what your buttons do (see auth.me() for why permission exists to branch ahead of the write). Reflecting it in the UI is purely so the user isn't handed a button that will fail.

Anonymous visitors and auth.login()

On a Private glow you never handle a signed-out state inside your page — a visitor without a session is redirected to sign in before your JavaScript runs. On a Public or Accounts glow your code does run for anonymous visitors, so auth.me() returning null is a real state to design for. What sign-in does from there differs by mode, and the auth-mode matrix is the authority on it — in short, an Accounts glow lets any visitor create an account, while a Public glow only admits allowlisted users (e.g. an admin unlocking privileged controls).

const me = await backlit.auth.me();
if (me === null) {
  // Anonymous on a Public/Accounts glow. Offer sign-in and route
  // anonymous input through capture in the meantime.
  loginBtn.onclick = () => backlit.auth.login(location.pathname);
}

Use auth.login() to send the visitor to sign-in; anything they need to submit before signing in belongs in capture, the one store an anonymous session can write.

A worked shape: an open glow with mixed access

Picture an Accounts glow — a community board. Anyone can read it; a signed-out visitor can leave a "contact the organizers" note; a signed-in member posts entries others can see but not edit; an admin curates a shared banner and reviews the contact notes. That maps cleanly onto the three stores and the two dials:

Each store's own reference section (data, records, capture) has the exact method signatures and the precise gate; this guide's job is only to help you pick the right one and reflect its gate in the UI.