Data and backends
Where a page can store data. The built-in blocks need no backend of your own; window.smp covers custom shapes; and a page can call your own API or a hosted database over HTTP.
A shared page is real HTML in a real browser, so a common question is where it keeps data. "Do I need a backend? Can I connect a database?"
Most of the time the answer is no, you do not need either. ShareMyPage stores responses for you. There are three levels, and you should reach for the first one that fits.
1. The built-in blocks
Drop a block into your page's HTML and storage is handled. No backend, no API key, no JavaScript of your own.
| Block | What it stores | Read it back |
|---|---|---|
<smp-poll> | one vote per person, tallied live on the page | live counts on the page, plus CSV export |
<smp-form> | free-text submissions, private to you | CSV export, one column per field |
<smp-reactions> | one row per person per emoji | the counts on the page |
<smp-checklist> | shared team state, synced across everyone, and with allow-add the items readers type too | the page itself |
This covers RSVPs, feedback, surveys, votes, guestbooks, launch checklists,
shared todo and shopping lists, and counters. Responses live in the page's own
storage, are tied to the block's id, and are read from the Responses panel
in page settings.
Note the split in that last column, because it decides which block you want. A
form's submissions are private to you: they never appear on the page, so a
visitor cannot see what anyone else wrote. A checklist is the opposite, it is
shared, and allow-add extends that to the items themselves. If you want
readers to add something the next reader can see, that is the checklist, not the
form.
2. window.smp for a custom shape
If your data does not fit a poll, a form, or a checklist, every page has a raw append-and-read stream available to any inline script:
// append a row to a named stream (one updatable row per visitor)
await window.smp.append("signups", { name: "Sam", plan: "team" });
// or spell out where it lands: a named bucket is what the built-in blocks
// count, so this is how your own script writes something they can read back
await window.smp.append("checklist-items", { text: "Ship it" }, {
blockId: "todo:items", // defaults to the stream name
bucket: "Ship it", // defaults to null, which only adds to the total
dedupeKey: "i1a2b3c", // one updatable row per key, per visitor
});
// the identity a checklist files an item under, so your own board UI can
// address the same item the block does
const id = window.smp.itemValue("Ship it"); // "i1a2b3c"
// read the running aggregate. `bucketOrder` holds the same keys as `buckets`,
// oldest first, for when the order matters.
const agg = await window.smp.read("signups");Both calls resolve to the same aggregate, or to null when the stream has
nothing yet or the write was rejected (closed, paused, over quota, rate-limited,
or a network failure). Always null-check before reading fields:
{
total: 12, // rows counted
buckets: { sushi: 7, pizza: 4 }, // per-key counts; {} for a bucketless stream
bucketOrder: ["sushi", "pizza"], // the same keys, oldest first
mine: ["sushi"], // the keys YOU hold, when you identify yourself
closed: false // true once the owner closes the block
}buckets is filled only when appends carry a bucket. A plain
window.smp.append with no bucket has buckets: {}, so use total.
You get the counts, not the individual rows. Individual responses stay private
to you and come out of the CSV export. That makes this a good fit for counters,
tallies, and intake with a bespoke UI, and a poor fit for anything where the
page itself must display what other people wrote. For that last case, reach for
<smp-checklist allow-add> before you reach for a
backend: it is the one built-in block whose contents readers write and every
other reader sees.
3. Your own API or a hosted database
When you need real reads and writes that the page renders back, call your own
endpoint over HTTP. A page is allowed to make outbound requests, so a plain
fetch() to your API, to Supabase, to a Neon Data API, to Firebase, Turso, or
Airtable works.
Three things decide whether it actually works, and all three trip people up.
You cannot connect to a database directly
A browser speaks HTTP and WebSocket, nothing else. There is no way to open a Postgres or MySQL connection from a page, on ShareMyPage or anywhere else. You always need an HTTP API in front of the database, either one you write or the one your database host provides.
Anything in the page is public
Your page's HTML is served to every visitor, so any key inside it can be read by every visitor. Treat it as published the moment you save.
That means a publishable or anon key only, paired with row-level security in the database so the key alone cannot read or change anything it should not. Never a service key, an admin token, or a database URL with a password in it.
Do not paste secrets into a page
A private page is private to readers you have not invited, not to the browser. Anyone who can open the page can read its source. If a credential must stay secret, keep it on your own server and have the page call a small endpoint that holds it.
CORS sees Origin: null
Page content renders inside a sandboxed frame with no origin of its own, which
is what keeps a page's scripts away from your account and everyone else's pages.
A side effect is that outbound requests carry Origin: null.
So an API that answers Access-Control-Allow-Origin: * works, and one whose
allowlist expects a specific domain will reject the request. If you control the
endpoint, allow the null origin explicitly. If a hosted service only offers a
domain allowlist, put your own small proxy in between.
There is no localStorage
For the same reason, the sandboxed page has no localStorage, no
sessionStorage, and no cookies of its own. Keep per-visitor state in memory
for the length of the visit, or push it to window.smp or your own API if it
has to survive a reload.
Which one should I use
- A poll, a form, a checklist, or a counter. Use the built-in block. It is the only option with no keys to leak and no service to run.
- A list readers add to and everyone then sees, like a todo list, a
shopping list, or an agenda. Use
<smp-checklist allow-add>. This is the one case that used to need your own backend, because a form's answers are private to you andwindow.smpreads back totals rather than rows. - The same kind of data, your own look. Style the block, or drive it from
your own script with
window.smp. - Reading back individual records, joining data, or showing a visitor the
full text of what other visitors submitted. Use your own API, with a
publishable key and row-level security, or a proxy endpoint that holds the
secret.
window.smp.readreturns per-answer totals, not the rows behind them, so anything that has to render one visitor's words to another needs either an appendable checklist or a backend of your own.
The built-in storage has fair-use caps: a per-page response cap plus per-visitor and per-IP rate limits. It is sized for pages that collect responses, not for high-frequency or high-volume data collection. Bring your own backend if you are past that.