Go backend
WriteKit is a single executable. The HTTP server, rendering engine,
MCP endpoint, dashboards, database migrations, and static assets
are all compiled into one file via //go:embed. To run
it, you need the executable and a config.
Why Go
Go produces statically-linked executables and has an HTTP server in its standard library. That keeps the runtime dependency surface small — there's no separate language runtime to install alongside the server. Concurrency is handled with goroutines, so each request, background worker, and event subscriber runs independently without a thread pool to tune. The same source cross-compiles for Linux, macOS, and Windows, which is what makes the desktop build (below) feasible.
The dashboards are client-side JavaScript applications. They're built ahead of time into static bundles and embedded in the executable, so at runtime the server hands out static files.
SQLite per site
Each site gets its own SQLite file on disk. Pages, collections, versions, embeddings, and settings all live together in that one file. There's no shared content database and no row-level tenant column — tenancy is enforced by which file you open.
Why one file per site
- Isolation. A query in one site's file cannot reach another site's data, because the other data is literally a different file with a different handle.
-
Portability. Backup and migration are file
copies.
cp alice.dbmoves the whole site. - Transparency. The file is a standard SQLite database. You can open it in any SQLite browser to see what's stored, or copy it out to keep a local backup.
- Migrations. Schema changes run at startup from embedded SQL files, per-tenant. There are no shared content tables that need coordinated changes.
- Hosted data model. The hosted build also uses Postgres, but only for platform concerns — users, tenants, sessions, and Stripe records. Page content stays in the per-site SQLite file.
Desktop app
The desktop app uses the same codebase as the hosted server,
wrapped with Wails.
It imports the same internal/app package with
LOCAL=true and serves the user SPA over loopback
inside a native window.
internal/app · no platform shimWhat's different in local mode
- No Postgres — there is no platform database at all.
- No OAuth, Stripe, subdomains, or DNS.
- Identity is injected via request context from the loopback listener. The server treats requests as coming from the logged-in OS user rather than going through a sign-in flow.
- Content lives in a single SQLite file in the OS user data directory.
Open source
The codebase is at github.com/Macawls/writekit. That includes the server, desktop app, frontends, migrations, and the MCP tool definitions.
The repo is self-hostable. The docker compose file used
for local development (Postgres and Ollama) is the same one that
can run a self-hosted instance.
Data & privacy
This section describes what data WriteKit stores, where it stores it, and which data paths exist between your device and our servers.
Hosted mode — what's stored on our servers
- Account: email, OAuth provider, user ID.
- Tenant: the subdomain you chose and any custom domain you configured.
- Page content: in your per-tenant SQLite file, on disk in our hosting environment.
- Billing: a Stripe customer ID. Card details are held by Stripe, not by us.
What we don't collect
- No third-party analytics, pixels, or session-replay scripts are loaded anywhere on the site.
- Drafts are not used to train models.
- No advertising or data-broker integrations.
Desktop mode
The desktop app doesn't send data to writekit.dev. It writes a SQLite file to your user data directory, and the network is only touched if you configure an external service (such as a local Ollama instance for embeddings).