Wellformed
Version 1.0.0. Private software, owned outright by Bram Luiken โ see
LICENSE. Running it requires a licence granted in writing;
possession of these files is not one, and neither is anything you can set in a
config file.
A licence, once granted, is broad: use it however you like, commercially, modified, on as many machines as you run. The one boundary is that Wellformed itself is never what you sell or host for others.
What it needs
- Node 20.11 or newer
build-essential(or equivalent).better-sqlite3is a native module. npm usually finds a prebuilt binary, and when it does not it compiles โ so install the toolchain before the firstnpm ci, not while debugging one.
Install
npm ci
npm run build
npm prune --omit=dev
Copy .env.example to .env and set at least:
NODE_ENV=production
HOST=127.0.0.1
PORT=4000
WELLFORMED_SECRET= # node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
WELLFORMED_DATA_DIR=/opt/wellformed/data
WELLFORMED_PUBLIC_URL=https://your.domain
WELLFORMED_LICENSE= # the signed licence line, from your licence package
WELLFORMED_SIGNUPS=closed
WELLFORMED_SECRET is mandatory in production; the server refuses to start
without it.
WELLFORMED_LICENSE holds the licence line issued with your licence โ one
signed line naming the party it was granted to, found in license.env in the
package you were handed. It is what makes this installation unmetered; leave it
empty and workspaces run into the hosted plan's limits. On start the server
prints whose licence it is running under:
[wellformed] licensed to Acme B.V. ยท WF-2026-001 ยท since 3 March 2026 ยท perpetual
WELLFORMED_SIGNUPS=closed means accounts are made by invitation instead of by
anyone who finds the address.
First account
node packages/server/dist/tenancy/provision-cli.js \
--email you@your.domain --workspace "Your workspace" --slug work
It prints a generated password once. Call the compiled file directly:
npm run provision needs a dev dependency that npm prune has removed.
Run
Behind nginx or another reverse proxy, with the service bound to
127.0.0.1 and TLS terminated in front of it. deploy/ holds a systemd unit
to start from.
Updating
Take a copy of the data directory first, every time. Database migrations run forward only: putting the old code back does not put the old database back, so that copy is the only way back.
cp -a /opt/wellformed/data /opt/wellformed/data.before-update
git pull
npm ci && npm run build && npm prune --omit=dev
systemctl restart wellformed-server
The databases migrate themselves when the server next opens them.
Languages
Wellformed speaks English and Dutch. The language is a property of the
workspace, like its currency: a workspace is full of names in one language,
and everyone who opens it reads the same page โ the same words, the same way
of writing numbers and dates. Admins change it in workspace settings; a new
workspace starts in the language of the browser that created it. Outside a
workspace โ the login page, the marketing page โ the browser decides.
Adding a language means adding a column to the message catalogue in
packages/web/src/i18n/messages/ and a value in packages/shared/src/locale.ts
โ the compiler then points at every message still missing.
Your data
One SQLite file per workspace, under WELLFORMED_DATA_DIR. Back that directory up
somewhere that is not this machine. Any workspace can also be exported whole
from inside the product, at any time, on any plan.