Guides

How to add a database to a static website — and when you don’t need one

A static site is just files: HTML, CSS and JavaScript. That’s why it’s fast, cheap and hard to break. It’s also why it can’t save anything a visitor does. Here’s how to add storage, starting with the options that keep your site simple.

Written by jstash, which makes some of the options compared here. Updated 4 October 2026.

Summary

  • A static host only serves files. There’s nowhere to keep a database password and nothing to check who may write. So anything visitors save has to go through a service that checks each person’s login, or a serverless function next to your site.
  • Often you don’t need a database. A JSON file in your repo, your host’s forms, a comments widget, a static search index or localStorage may cover it.
  • Public data you change without redeploying: a hosted JSON document, like a jstash bin.
  • Each signed-in person’s own data: jstash Apps, or Supabase or Firebase with per-user rules.
  • Queries, relationships, shared data or live updates: a real database. Supabase and Firebase work from the browser with rules you write. Cloudflare D1 needs a little server code; Turso suits a database per user or customer.

What a static host can’t do

GitHub Pages is a typical static host: it “takes HTML, CSS, and JavaScript files straight from a repository on GitHub, optionally runs the files through a build process, and publishes a website”. Nothing of yours runs on the server when someone visits. That leaves two gaps:

  • No place for a secret. A database password or secret key in your JavaScript can be read by every visitor. See how to save JSON without exposing an API key.
  • No one to check writes. Something the visitor can’t change has to decide who may save what.

So every option below does one of three things. It avoids writes from the browser altogether, or the service checks each person’s login itself, or your own code checks in a serverless function next to your site, like Netlify Functions. With functions, you still don’t run a server, but your site isn’t only files any more.

When you don’t need a database

Plenty of “I need a database” moments have a simpler answer.

Content you edit yourself

Products, team members, a list of links: put them in a JSON file in your repo. Fetch it from the page, or let your site generator read it at build time. Changes go through Git, so you get history and review for free. Each change means a commit and a redeploy.

const products = await fetch('/data/products.json').then((r) => r.json());

Contact forms

If you host on Netlify, Netlify Forms collects submissions without server code: turn on form detection, add data-netlify="true" to the form, and submissions appear in Netlify’s admin. On other hosts, a hosted form service does the same job.

Comments

giscus keeps comments in GitHub Discussions: “No database needed.” People need a GitHub account to comment, which suits a developer blog more than a shop.

Search

Pagefind is “a fully static search library”: it indexes your built pages and searches them in the browser, “without hosting any infrastructure.”

Preferences in one browser

localStorage keeps small values in the visitor’s browser, with no server at all. It stays in that one browser, and private windows clear it when the last private tab closes. For when that’s enough, see localStorage vs cloud storage.

Public data you change without redeploying

Opening hours, a price list, a status banner, settings for a widget: data everyone reads and only you change. If a redeploy for each change is a chore, or a script does the updating, keep the data in a hosted JSON document instead.

A jstash bin is one JSON document at its own URL. Create it in the dashboard, write it with your API key from your machine, a script or CI, and read it from your page with a plain fetch:

# From your machine or CI. The API key stays here.
curl -X PUT https://api.jstash.app/v1/bins/<bin-id> \
  -H "Authorization: Bearer $JSTASH_API_KEY" \
  --data-binary @hours.json
// In your page. No key needed.
const hours = await fetch('https://j.jstash.app/<bin-id>').then((r) => r.json());
  • Any website can read it, and public reads aren’t metered. After a change, browsers can see the old copy for up to 5 minutes.
  • Browser JavaScript on other websites can’t write it. That’s by design: the API key stays in your script or CI, never in a page.
  • Anyone with the URL can read it, so don’t put anything private in a bin.
  • Free accounts get 100 bins of up to 100 KB each, 10 MB in total, and 1,000 updates a day. To try it without an account, curl -d '{"hello":"world"}' https://api.jstash.app/v1/bins makes a trial bin that lasts 7 days.
  • No backups. jstash keeps no old versions, so keep your own copy. The JSON file in your repo is a good place for it.

If a redeploy for each change is fine, the JSON file in your repo is simpler, and you don’t need jstash.

Each signed-in person’s own data

Settings, progress, drafts, saved items: data that belongs to one person and should follow them from their laptop to their phone. If your site has a login, there are two good ways to keep it.

  • jstash Apps. Your page sends the person’s login token from Clerk, Supabase Auth, Firebase Auth or Sign In With Google, and each person reaches only their own JSON documents. No server, no key in the page, no rules to write. Free for 1 app with up to 100 users. There’s no sharing, no queries and no live sync, and you can’t look at your users’ data.
  • Supabase or Firebase with per-user rules. More power: queries, sharing and live updates. In return you write rules that keep each person to their own data, and test them, because a missing rule doesn’t show up as a bug.

Step by step: how to save user data in a static website, per-user storage with Clerk, and the jstash Apps guide.

When you need a real database

You need a real database when people share data, like a team’s task list. Or when you query across records: search, filter, sort, totals. Or when records relate to each other, changes should appear live on other screens, or you need to see and manage your users’ data. jstash doesn’t do any of these, on purpose. Use one of these instead.

Supabase

Postgres with sign-in, file storage and live updates. Your page talks to it directly with the publishable key, and Row Level Security policies decide who reaches what. The free plan has a 500 MB database, 50,000 monthly active users and 2 active projects. Free projects with low activity for 7 days are paused, and can be restored for up to a year.

Firebase (Cloud Firestore)

A document database with sign-in, where your page can listen for changes as they happen. Security Rules decide who reaches what. The no-cost plan includes 1 GiB stored, 50,000 reads and 20,000 writes a day.

Cloudflare D1

“Cloudflare’s managed, serverless database with SQLite’s SQL semantics.” Your code reaches it from a Worker or a Pages Function, and that code is where you check who’s signed in. The Workers Free plan includes 5 million rows read and 100,000 rows written a day, and 5 GB in total.

Turso

SQLite in the cloud, built to “spin up a database for every user, agent, and tenant”. Its access tokens can be limited to databases, tables and actions, and the usual way to keep each person’s data apart is to give them a database of their own. The free plan has 100 databases and 5 GB.

Free tiers were checked on 4 October 2026. They change, so check each pricing page before you choose.

Decision table

You needUse
Content only you edit
a redeploy per change is fine
A JSON file in your repo. Each change is a commit and a redeploy.
Contact form submissionsYour host’s forms, like Netlify Forms, or a form service.
Comments on postsgiscus. Commenters need a GitHub account.
Site searchPagefind. It searches your pages, not data people save.
A visitor’s preferences
in one browser
localStorage. It doesn’t follow them to other devices.
Public data you change
without redeploying
A jstash bin. Written from a script or CI; anyone with the URL can read it.
Each signed-in person’s own data
settings, progress, drafts
jstash Apps. No server or rules; no sharing, queries or live sync.
Shared data, queries, relationshipsSupabase or Firebase. Write the rules and test them.
Live updates across screensSupabase Realtime or Firestore listeners. The same rules apply.
SQL from your own functions
or a database per customer
Cloudflare D1 or Turso. Expect to write some server-side code.

More guides

Want to try jstash? Free includes 1 app with up to 100 users, and 100 public bins, no card. Read how jstash Apps works or the bins API docs.