Skip to content
staticbackend.

A database behind a static HTML site

Call Supabase from the browser with row level security on: 500 MB of Postgres, 50,000 monthly active users and 2 projects for $0, Pro from $25 a month. Turso and Cloudflare D1 need a Worker in front.

<script type="module">
  import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

  const supabase = createClient("https://YOUR-PROJECT.supabase.co", "YOUR_PUBLISHABLE_KEY");

  const { data, error } = await supabase
    .from("guestbook")
    .select("name, message, created_at")
    .order("created_at", { ascending: false })
    .limit(20);
</script>
alter table guestbook enable row level security;

create policy "Anyone can read the guestbook"
  on guestbook for select
  using (true);

create policy "Anyone can sign, up to 500 characters"
  on guestbook for insert
  with check (char_length(message) <= 500);

The publishable key in the page is public by design. What keeps the data safe is the policy SQL: with row level security enabled, the key can do exactly what the policies allow and nothing else. Supabase's own guidance is to "enable RLS on every table in an exposed schema" (RLS docs). Forget it on one table and anyone with the key from your HTML can read or rewrite that table.

Databases you can reach from a static site, compared

Database Free storage Free usage limits Paid entry Access from the page
Supabase 500 MB database, 2 active projects 50,000 monthly active users, paused after 1 week idle Pro from $25 a month Direct, guarded by row level security
Firebase Firestore 1 GiB stored 50,000 reads, 20,000 writes and 20,000 deletes a day Blaze, pay as you go Direct, guarded by Security Rules
Turso 5 GB, 100 databases 500 million rows read and 10 million written a month Developer, $4.99 a month Through a Worker or function
Cloudflare D1 5 GB per account, 500 MB per database, 10 databases 5 million rows read and 100,000 written a day Workers Paid, $5 a month Through a Worker binding

Supabase from a static page

Supabase is Postgres with an HTTP API in front, so the browser can query it directly with supabase-js and the publishable key. The free plan covers a 500 MB database, 1 GB of file storage, 50,000 monthly active users and 2 active projects. The limit that bites hobby sites is inactivity: free projects are paused after 1 week without activity, and a paused project rejects requests until you restore it in the dashboard. Pro starts at $25 a month and does not pause.

Firebase Firestore on the Spark plan

Firestore also talks to the browser directly, with Security Rules doing the job that RLS does in Supabase. The no-cost Spark plan allows 1 GiB of stored data, 50,000 document reads, 20,000 writes and 20,000 deletes a day (Firestore quotas). Those quotas are daily, so a page that reads 100 documents per visit runs out after 500 visits in a day. Blaze removes the caps and bills per operation (Firebase pricing).

Turso and Cloudflare D1 need a Worker in front

Turso gives 100 databases, 5 GB of storage, 500 million rows read and 10 million rows written a month for $0; Developer is $4.99 a month. Its auth tokens grant database access, so they cannot sit in HTML. Put a Worker or serverless function between the page and the database and expose only the queries the page needs.

Cloudflare D1 is SQLite reached through a Worker binding, so it has no browser key at all. The Workers Free plan includes 5 million rows read and 100,000 rows written a day, 5 GB of storage and 10 databases, with a 500 MB cap per database (D1 limits). Workers Paid is $5 a month (Workers pricing).

Security checklist for a browser-side database

  • Enable row level security, or write Security Rules, before the first row goes in. A table without policies plus a public key is an open database.
  • Never ship a Supabase service role key, a Turso token or a Firebase admin credential in the page. Those bypass every policy.
  • Cap what inserts can contain. The char_length check above stops a script from filling your 500 MB with one giant row.
  • Count reads per visit. A loop that refetches on every scroll can spend Firestore's 50,000 daily reads in minutes.
  • Rate limit write paths that go through a Worker, so one client cannot spend the 100,000 daily D1 writes.

Where to host the page

Any static host works because the browser talks to the database directly or through your Worker. For a quick demo link with no account, ship.page publishes a 10 MB HTML file for 7 days; for a permanent free site with your domain, Cloudflare Pages or GitHub Pages cost $0. See the free HTML hosting benchmark and the Netlify Drop alternatives test.

Frequently asked questions about databases for static sites

Can I connect to a database from plain HTML and JavaScript?

Yes, if the database is built for it. Supabase and Firestore accept requests straight from the browser and enforce access with row level security or Security Rules. Databases that use secret tokens, such as Turso, or bindings, such as Cloudflare D1, need a small Worker between the page and the data.

Is the Supabase publishable key safe in client code?

Yes, as long as row level security is on for every exposed table. The key identifies your project; the policies decide what it may read or write. The service role key is different: it bypasses all policies and must never appear in HTML or in a public repository.

Why did my Supabase free project stop responding?

Free Supabase projects are paused after 1 week of inactivity. A paused project rejects API calls, so the static page shows errors until you restore the project from the dashboard. Pro, from $25 a month, does not pause, and the free plan allows 2 active projects at a time.

Back to backend guides