# BrewPage Page data: build, try locally, publish
BrewPage Page data (API word `state`): a published page saves what visitors enter. No backend, no key in page code; not KV/JSON (your own data).
1) Build. Before your code (absolute URL, classic script, no async/import):
bp.state() -> s|null (guard it); c=s.col("name"); c.set(key,value) c.get(key) c.remove(key) c.add(value) c.list({order,limit}) c.stats({countBy})
async function start() { // no top-level await/return
const s = await bp.state(); // null -> reason in bp.state.reason, console
if (!s) return showNote("Saving is not available on this page right now."); // always guard
const book = s.col("guestbook");
await book.add({text: input.value}); // random key
const docs = await book.list({order: "-createdAt", limit: 20}); // [{key, id, value, mine, createdAt}], .next|null -> list({cursor})
for (const d of docs) ul.append(bp.text(d.value.text));
const v = s.col("votes");
await v.set("me", {choice: "a"}); // key A-Za-z0-9_.-, no leading _, <=128; "me" = one per visitor
await v.get("me"); // value or null; remove(key) -> true if it existed
await v.stats({countBy: "choice"}); // {count, by: {a, b, other}}
}
start();
bp.storage.ready.then(() => bp.storage.setItem("k", "v")); // localStorage-like, synced; works where s is null
2) state (in the publish body, not the page): {} = all lists private (visitor sees own entries). Name a list to change its rule:
{"collections":{"guestbook":{"mode":"shared","maxDocsPerVisitor":5}}} = all read, each writes own
"scores":{"mode":"shared","index":"score","maxDocsPerVisitor":1} -> list({order: "-index"})
"votes":{"choices":{"choice":["a","b"]}} -> stats, other values count as other
"menu":{"mode":"shared","write":"author"} = visitors read, author writes (Owner API, MCP)
List names a-z0-9_- (letter first, <=32 chars), <=32 lists. A private list never turns shared: new name.
3) Owner view: s.role is "owner" where the page shows its owner gear, else "visitor"; s.onRole(fn(role)) runs now and on change.
Owner: c.list({all: true}) = all entries + d.visitor label (paged like list); c.removeById(entry.id). No secrets in page.
4) Test locally: open from disk or http(s)://localhost, 127.0.0.1, [::1], *.localhost: bp.state() = local preview, s.local === true,
console "[BrewPage] Local preview". Kept in this browser, nothing sent; clear: localStorage.removeItem("__bp_state_local").
One visitor; checks and limits as published, e.status 0. Not applied: state rules, rate limits, human check, cookies.
?bp-local=owner = owner view, ?bp-local=off = off. Same file works unchanged once published with state.
5) Publish: POST https://brewpage.app/api/html, Content-Type: application/json, body {"content":"...","state":{...}}; sites: multipart field "state".
Raw HTML body cannot carry state (or PATCH /api/{ns}/{id}/state later). Own address https://