A self-hosted livediagram is configured through environment variables, secrets and each Worker's wrangler.toml, not application code. A few wrangler.toml values ship with livediagram.app's own settings (the database and KV ids, the allowed AI origins, the MCP issuer and consent URL), so a fork replaces those with its own. The most important thing to understand is that the optional features are genuinely optional: leave them unset and the editor still works perfectly, just in a simpler mode. This page covers how configuration is supplied and the main toggles you will care about.
How configuration is supplied
Secrets and settings never live in source code, since the repository is public. Instead, each Worker reads them at runtime.
- Local development uses a gitignored
.env.localfile per frontend app and a gitignored.dev.varsfile per Worker. - Workers receive secret values via
wrangler secret put, and non-secret settings as[vars]in the Worker'swrangler.toml. Set plain settings in the file rather than the Cloudflare dashboard: every deploy replaces them with what the file declares. - Static frontends only ever bundle values explicitly marked as publishable, prefixed
NEXT_PUBLIC_so it is obvious they are safe to ship to the browser, and baked in at build time. Server-only secrets stay on the Worker side and never reach client code.
Each app that needs configuration ships an example file documenting which variables it expects, so you can see at a glance what to set.
Optional sign-in
Sign-in runs inside your own api Worker, with its accounts in your own database, so it needs no outside service and no build-time key: the editor asks the api whether sign-in is on and which methods it offers. There are two modes.
- Sign-in on: set
BETTER_AUTH_SECRETand at least one sign-in method, so guests and signed-in people coexist. - Guest-only, the typical self-host: leave the secret unset. The app runs without an account system, sign-in pages say sign-in isn't enabled here and offer Continue as guest, and the editor is unchanged.
| Variable | Where | What it does |
|---|---|---|
BETTER_AUTH_SECRET | api Worker secret | Turns sign-in on. At least 32 random characters (openssl rand -base64 32). It also signs guest ids, so nobody can claim another guest's documents when they sign in. Rotate it with BETTER_AUTH_SECRETS beside it, never by replacing it |
APP_BASE_URL | api [vars] | The origin people open the editor on. Required for sign-in, and used for links in emails |
GOOGLE_AUTH_CLIENT_ID | api [vars] | Your Google OAuth client, for Continue with Google. Register https://<your-host>/api/auth/callback/google as its redirect URI |
GOOGLE_AUTH_CLIENT_SECRET | api Worker secret | The same client's secret; Google sign-in needs both |
EMAIL binding or RESEND_API_KEY | api Worker | An email sender, for sign-in with an email code: Cloudflare Email Service, or Resend. Set EMAIL_FROM to an address on your sending domain |
EMAIL_OUTBOX | api [vars] | 1 keeps codes in your database instead of sending them, for previews and testing only. Never on a real deployment |
Accounts unlock the features that need one: teams, API tokens, and, if you set them up, email, the Google Drive mirror and the MCP server.
Guest-only is what lets a self-hoster run with zero outbound runtime traffic beyond their own hosting if they want to.
Guest-only fallback
The API identifies the owner of a request in one of two equivalent ways: a verified sign-in token, or a per-browser guest id sent as a header. When sign-in is not configured, every request simply uses the guest path, and the editor stays fully usable, with persistence, sharing, and real-time collaboration all working.
Other optional features
Every other optional feature follows the same pattern: unset means off, and the editor hides what it cannot use.
| Feature | Turn it on with |
|---|---|
| Image uploads, thumbnails | Create the livediagram-images R2 bucket the api wrangler.toml already binds (or delete that binding to leave uploads off). Optional per-owner caps: IMAGE_MAX_PER_OWNER, IMAGE_MAX_BYTES_PER_OWNER |
| AI assistant, server-side photo reading (without it, photos are read in the browser) | One AI key on the api Worker: OPENAI_API_KEY, GOOGLE_AI_STUDIO_API_KEY, or AI_API_KEY with AI_BASE_URL and AI_MODEL. Also set AI_ALLOWED_ORIGINS to your own origin or remove it: it ships with livediagram.app's, and any other origin gets a 403 |
The EMAIL binding (Cloudflare Email Service) or RESEND_API_KEY on the api Worker, plus EMAIL_FROM on your sending domain and APP_BASE_URL set to your origin, since both default to docs.nu | |
| Google Drive mirror | GOOGLE_CLIENT_ID on the api Worker and NEXT_PUBLIC_GOOGLE_CLIENT_ID on the live build; GOOGLE_CLIENT_SECRET and DRIVE_TOKEN_KEY keep it syncing (needs sign-in). See Google Drive and MCP |
| MCP server for AI tools | Deploy the mcp Worker with its own KV namespace and hostname, then point CONSENT_BASE_URL (mcp Worker) at your editor's origin, and NEXT_PUBLIC_MCP_ORIGIN (live build) and OAUTH_ISSUER (api Worker) at your MCP host (needs sign-in). See Google Drive and MCP |
| Telemetry | TELEMETRY_ENABLED on the api Worker and NEXT_PUBLIC_TELEMETRY_ENABLED on every frontend build |
| Workbenches | Nothing extra beyond sign-in, but set APP_BASE_URL on the api Worker to the origin that serves the app, or pairing and frame links point at livediagram.app (needs sign-in) |
The NEXT_PUBLIC_* values above are baked in at build time. When you deploy through the bundled GitHub Actions workflow, add them to the workflow's build step (or set mcp_origin in deploy.yml); apart from the Google Picker key, they are not read from repository secrets. Sign-in needs none: the workflow syncs BETTER_AUTH_SECRET and GOOGLE_AUTH_CLIENT_SECRET to the api Worker from repository secrets of the same name.
The Community is the one feature that is on by default; set COMMUNITY_ENABLED to false on the api Worker to switch it off.
For every variable, where it lives and the steps around it (OAuth redirect URIs, KV namespaces,
the AI model knobs), follow the repository's docs/operations/self-hosting.md and each app's
.env.example file.
Was this article helpful?