How public visitors write to a sheet
A Database isn't a publishable surface on its own — it has no public URL. Visitor writes reach a sheet only through a published playground that links the Database (linked_database_id). The playground renders the UI; the SDK's flowfn.data.insert / update / delete calls land on the linked sheet. See Using Data Sheets from a Playground.
The gate
Every public write must clear all of these, or it's rejected before touching the data:
- Per-sheet opt-in. Each sheet has three flags —
allow_public_insert,allow_public_update,allow_public_delete— all off by default. Only the operations you turn on are callable from the public iframe; a disabled write fast-fails in the SDK witherr.code === 'sheet_write_disabled'. - Plan gate. Public sheet writes are plan-gated alongside the rest of Data Sheets (
allow_data_sheets). - Published playground. The linking playground must be published and publicly reachable; writes through a draft or private playground don't apply.
- Rate-limited. Public write endpoints are rate-limited per IP to blunt abuse.
When all three sheet flags are off, the sheet stays strictly read-only from the public page — reads still work, writes don't.
No per-row ownership
When updates or deletes are enabled, any visitor with the playground link can mutate any row — there's no per-visitor row ownership. If you need authenticated-per-user semantics, leave update / delete off and route mutations through a gated workflow trigger or server-side fetchData entry instead. A public delete is a soft delete: the row leaves all reads immediately but you can restore it from Recently deleted within 90 days. There is no public restore.
Row actions run the linked playground's server code
A sheet can carry up to 10 owner-defined row actions — "admin buttons" on each row that call an exported function in the Server tab of the playground that links the Database. Because server functions live on a playground, a database with no linked playground has no row actions to run. The function receives { id, row, row_id, action, sheet } (id is the primary-id column's cell, or the stable row_id when unset), plus an optional input when the action defines a prompt. Row actions are owner-only — they're never exposed to public or embed visitors.
Sheet-full errors
A public insert at the plan's row cap (max_data_sheet_rows) rejects with err.code === 'sheet_full'. Handle it in your playground JS to show a friendly message instead of retrying.