Project 01 — Digitale PSM

A database that
lives in the browser.

Digitale PSM is a free, open-source web app for horticulture and farming businesses: calculate, document and export plant-protection applications as a record. No account, no backend, no cloud. All data lives in one SQLite file on the user's machine — and the database itself runs as WebAssembly in the browser.

By Abbas Hoseiny· Since 04/2025· Version 2.0· MIT licence

Anyone applying plant-protection products professionally has to record every application: product, amount, area, crop, date, waiting period. In practice that ends up in paper binders or in cloud services with an account, a subscription and someone else's server. Digitale PSM takes the third route: a web app that behaves like a program on your own computer, keeps the data as a file with the user, and works without a network.

Start screen of Digitale PSM on the desktop: create new or open an existing file
Start screen of Digitale PSM on a phone
Fig. 1 — the startdigitale-psm.de, desktop and phone

No login, no sign-up. The app asks exactly one question: create a new database or open an existing file. The file is an ordinary SQLite database on the user's computer — easy to copy, back up and read with any SQLite tool.

What the app does

Calculate, document, prove.

Calculation
Application rate by area or quantity, conversion between different rate units, product profiles for frequent combinations, EPPO codes and BBCH stages with quick selection.
Documentation
Every application is recorded; calendar view with filters; export as PDF or CSV.
Official approval data
After import, the BVL approval database sits inside your own file: search by crop, pest and active substance, offline too.
As a desktop app, offline
Installable as a desktop app (Chrome, Edge), starts without a network, remembers the last database and opens .sqlite files by double-click. GPS locations for fields.
Tech

How the data storage works

The usual blueprint for an application like this is a server with a database and an account per user. I wanted the opposite: the data belongs to the business and lives as a single file on its own computer. For that, the database has to go where the data is — into the browser.

1. A file instead of a server

The app is nothing but static HTML, CSS and JavaScript, served from GitHub Pages. There is no server that ever sees the records. The database is SQLite compiled to a WebAssembly module (@sqlite.org/sqlite-wasm), bundled locally with the app, hashed and versioned at build time like every other file. Nothing is fetched from a third-party CDN at runtime — which is why the database works offline as well.

One detail that is easy to miss: the file on the user's disk is a real SQLite database, not a home-made export format. It opens in any SQLite tool, and it can be backed up, copied and taken to another machine.

2. Three layers

The database never runs in the browser's main thread. It lives in its own Web Worker so the interface stays responsive even during a large import. Between the two there are only messages.

Interface · main thread
Astro, TypeScript, Bootstrap
  • Forms, calendar, lists, export (PDF, CSV)
  • State and event bus
  • Talks to the database only through messages
  • Holds the file handle and writes the file
⇄postMessage
ID, timeout,
transfer of large buffers
Database · Web Worker
SQLite 3.54 as WebAssembly
  • Schema with migrations (user_version 1 to 11)
  • WAL, foreign keys, 20 MB page cache
  • All queries, paged lists, BVL import
  • Exports the whole database as bytes
⇄bytes
export · import
Storage · with the user
One .sqlite file
  • File System Access API, handle kept in IndexedDB for auto-start
  • OPFS mode when the browser allows it
  • Fallbacks: JSON file, localStorage, memory

3. Saving, step by step

  1. An input, say a new application, is sent to the worker as a message. Every message carries an ID and a timeout; the reply comes back asynchronously.
  2. The worker writes to its SQLite database. That database lives in the worker's memory or, if the browser allows it, in the Origin Private File System (OPFS).
  3. The worker then exports the complete database as a byte sequence (sqlite3_js_db_export) and hands the buffer to the main thread without copying it (Transferable).
  4. The main thread writes the bytes through the file handle into the user's .sqlite file (createWritable, write, close). The file on disk is therefore always a complete, consistent database.
  5. Changes in quick succession are batched: if a write is already running, the app only notes that it has to write once more afterwards. One write per burst of changes instead of one per keystroke.

4. Opening: content, not file extension

When opening, the app reads the file as bytes and checks the signature at the start: SQLite format 3. The content decides, not the extension. A file may be called .json and still be a binary database — earlier versions saved as JSON, and exactly this mix once made an intact database look “empty”. Since then the signature rules.

If it is SQLite, the worker turns the file into the active database via sqlite3_deserialize; in OPFS mode OpfsDb.importDb imports it into the persistent file. If it is JSON, the data is imported as a snapshot. Both routes end in the same database, and the migrations run either way.

5. The BVL data: a pipeline of its own

The official approval database of the BVL (Germany's Federal Office of Consumer Protection and Food Safety) is reachable through a public API, but not made for the browser: many endpoints, many requests, no offline use. So the preparation happens up front in a pipeline of its own, the public repository pflanzenschutz-db:

  1. Every two days a GitHub Actions run fetches the data from all BVL endpoints, validates it and enriches it — among other things with organic-use flags, active-substance names and hazard statements.
  2. The result is one SQLite file, compressed with Brotli and as ZIP, published through GitHub Pages together with a manifest listing size and SHA-256 checksum per file.
  3. The app loads the manifest, picks the smallest format the browser can decompress, shows progress and hands the bytes to the worker.
  4. The worker opens the download as a temporary in-memory database and copies its tables into the user's database inside one transaction — in dependency order, so the foreign keys hold.
  5. After that the complete catalogue sits inside the user's own .sqlite file and can be searched offline. A log inside the database records which release was imported.
As of 25 Oct 2026Value
Products in the catalogue2,165
Applications (product per crop and pest)30,836
Active substances482
Download, Brotli / ZIP6.8 MB / 12.5 MB
Unpacked (SQLite)113 MB

6. Offline and as an app

A service worker precaches all hashed build files on install, WebAssembly module included. After that the app starts without a network. Every build stamps a new cache version into the service worker; without that step, installed users would be stuck on old code, because a browser never reinstalls a byte-identical service worker.

As an installed desktop app, Digitale PSM registers through the File Handling API for .sqlite, .db and .json: a double-click in the file explorer opens the database straight in the app. The last file handle is kept in IndexedDB; on the next start the app only asks for permission and reopens the same file.

7. Performance and robustness

8. Limits, honestly

9. Fact sheet

Appdigitale-psm.de — free, no ads, no account
LanguageGerman (the app follows German approval law and the BVL database)
SinceApril 2025, currently version 2.0
Licence & codeMIT, source code on GitHub
StackAstro 5, TypeScript, Bootstrap 5 (bundled locally), SQLite 3.54 as WebAssembly, Web Worker, service worker, web app manifest
StorageSQLite file with the user via the File System Access API; OPFS when available; fallbacks JSON file and localStorage
Approval dataBVL API, prepared in pflanzenschutz-db (GitHub Actions, every two days), delivered as Brotli/ZIP with manifest and SHA-256
HostingStatic via GitHub Pages, build and deploy through GitHub Actions
Relatedst.digitale-psm.de: statistics dashboard for the BVL database, SQL queries right in the browser