Local First Software

SQLite in the browser changes the default

4 min read

SQLite in the browser changes the default for small tools.

The old default was simple: if the app needs real state, put the state on a server. The browser can keep preferences, cache, maybe a few IndexedDB records if the team is willing to live with the shape of that API. The source of truth lives elsewhere.

That is still right for many products.

But once SQLite becomes practical to ship in the browser through WebAssembly-backed runtimes and local persistence layers, a different class of tool gets easier to imagine.

local state can be structured

Browser storage often starts as a key-value drawer.

That works until the tool grows. Then notes, tasks, runs, artifacts, settings, indexes, drafts, imports, exports, and sync status all end up as blobs with conventions nobody wrote down.

SQLite changes the feel because the client gets a real relational shape:

create table runs (
  id text primary key,
  created_at text not null,
  status text not null,
  input_hash text not null,
  output text
);

create table artifacts (
  id text primary key,
  run_id text not null references runs(id),
  kind text not null,
  path text,
  body text
);

That schema is a design artifact. It says what the tool remembers.

offline becomes a real path

Local databases make offline behavior less fake.

The app can create records, update drafts, store imported data, queue changes, and let the user inspect history without waiting on the network. The product can say “saved locally” and mean something more durable than an in-memory object.

That does not make sync easy. It does make the offline side honest.

The app still has to decide:

  • what is stored locally
  • what syncs
  • what stays private
  • how conflicts resolve
  • how migrations run
  • how backups and exports work
  • what happens when storage is cleared

Those are product decisions. The database gives them a place to live.

migrations arrive in the client

Once the browser has a schema, the browser has migrations.

That is the part people underestimate. A client database can be old. The user may not open the app for months. They may have unsynced local state. They may be on another browser. They may have a partial migration from a crashed tab.

The app needs a migration story:

open database
  -> read schema version
  -> run migrations in order
  -> verify expected tables
  -> preserve user data
  -> fail with recovery path

For small tools, that can stay simple. But it cannot be skipped.

export keeps the user in control

One reason I like local databases for small tools is export.

If the data lives near the user, the product should give the user a way to take it with them. SQLite is nice here because the database can be an understandable artifact. Depending on the app, export might mean a .sqlite file, CSV files, JSON, Markdown, or a project bundle.

Export is not an afterthought. It is part of trust.

ai tools need run history

Local AI tools often need more than preferences.

They need prompts, runs, source references, generated artifacts, review notes, eval examples, and retry history. A browser SQLite database can keep that run history close to the user, which is useful when the workflow is private or experimental.

The model output is only one artifact. The local record around it is what makes the work inspectable later.

browser storage still has limits

This is not magic.

Browser storage can be cleared. Quotas exist. Private browsing behaves differently. Persistence APIs vary. Large databases can make backup and migration awkward. WebAssembly startup cost matters. Multi-tab access needs care. Security boundaries still depend on the origin.

That means a serious local tool should make the limits visible:

  • local save state
  • export or backup
  • schema versioning
  • migration tests
  • storage quota handling
  • clear reset behavior
  • sync status if sync exists
  • user-facing recovery instructions

SQLite in the browser is useful because it makes serious local state feel ordinary. It lets some small apps start local instead of starting as a backend project with a browser attached.

Jeremy London

About Jeremy London

Engineering leader and builder in Denver. I write about AI platforms, agents, security, reliability, homelab infrastructure, and the parts of engineering work that have to survive production.