Skip to content

Configuration

Orimora is configured entirely through environment variables (and team-level preferences in the database). The source of truth is .env.example at the repository root; expand each section below for the full table.

Core application variables
VariableRequiredDescription
APP_URLYes (prod)Public base URL, e.g. https://wiki.example.com. Drives magic links and OAuth redirects. Dev: http://localhost:5173.
NODE_ENVdevelopment or production.
PORTHTTP listen port (default 3000 in Docker; Vite dev uses its own port).
Database and Redis
VariableRequiredDescription
DATABASE_URLYesPostgreSQL 16+ connection string.
REDIS_URLYesUsed for sessions, rate limiting, queues, and BullMQ workers.
Session and magic-link secrets
VariableRequiredDescription
SESSION_SECRETYes64 hex chars (32 bytes). Signs session cookies.
MAGIC_LINK_SECRETYes64 hex chars. Signs magic-link JWTs.

Generate both with:

Terminal window
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Optional dedicated HMAC secrets (recommended in production for independent rotation):

VariableDefault when unsetPurpose
API_KEY_SECRETMAGIC_LINK_SECRETHashes API-key tokens at rest
EMAIL_CHANGE_SECRETMAGIC_LINK_SECRETSigns email-change confirmation tokens
ACCOUNT_DELETION_SECRETSESSION_SECRETSigns account-deletion confirmation tokens
VariableDefaultDescription
SESSION_IDLE_TIMEOUT_DAYS7Invalidate sessions after this many idle days. Set 0 to disable idle expiry.
Registration, docs redirect, CORS
VariableDefaultDescription
ALLOW_REGISTRATIONfalseWhen false, only invited users can join (open sign-up disabled).
DOCS_URLhttps://docs.orimora.comWhere the app redirects /docs requests (Starlight site).
EXTRA_ALLOWED_ORIGINSComma-separated extra origins for WebSocket /collab and Origin checks.
SMTP settings

If SMTP is unset, magic-link email is not sent (use OAuth or another strategy in dev).

VariableDescription
SMTP_HOSTServer hostname
SMTP_PORTUsually 587 (STARTTLS) or 465
SMTP_USERUsername
SMTP_PASSWORDPassword (name in .env.example)
SMTP_FROMFrom address
Google, Microsoft, OIDC
VariableDescription
GOOGLE_CLIENT_IDOAuth client ID
GOOGLE_CLIENT_SECRETOAuth client secret

Redirect: {APP_URL}/auth/google/callback

VariableDescription
MICROSOFT_TENANT_IDTenant ID or common for multi-tenant
MICROSOFT_CLIENT_IDApplication (client) ID
MICROSOFT_CLIENT_SECRETClient secret

Redirect: {APP_URL}/auth/microsoft/callback

VariableDescription
OIDC_ISSUERIssuer URL
OIDC_CLIENT_IDClient ID
OIDC_CLIENT_SECRETClient secret
OIDC_SCOPESpace-separated scopes (default openid email profile)

Redirect: {APP_URL}/auth/oidc/callback

WebSocket /collab settings
VariableDescription
COLLAB_SECRETOptional shared secret for the collab endpoint
COLLAB_MAX_CONNECTIONSMax concurrent WebSocket connections
COLLAB_MAX_YJS_STATE_BYTESMax persisted Yjs state size per document (default 8 MB). Set 0 to disable the service-layer check.

MCP OAuth (Claude mobile / Custom Connectors)

Section titled “MCP OAuth (Claude mobile / Custom Connectors)”
OAuth 2.1 + Dynamic Client Registration for the MCP endpoint

Lets the Claude mobile app and claude.ai Custom Connectors authorize against /api/mcp via a browser OAuth flow, in addition to the existing kb_… API keys (which stay unchanged). See the MCP guide for the user-facing setup.

The OAuth issuer is APP_URL — set it to your public https origin before enabling in production, or discovery hands out unusable localhost URLs (the server logs a warning at startup).

VariableDefaultDescription
MCP_OAUTH_ENABLEDtrueMaster switch. When false, the .well-known/oauth-* routes 404 and the server presents as Bearer-only.
MCP_OAUTH_ALLOWED_APP_SCHEMESclaude://Comma-separated custom redirect schemes accepted at registration (besides https and http://localhost).
MCP_OAUTH_CORS_ORIGINShttps://claude.ai,https://www.claude.aiBrowser origins allowed to call the OAuth + MCP surface cross-origin.
MCP_OAUTH_ACCESS_TOKEN_TTL_SECONDS3600Access-token lifetime.
MCP_OAUTH_REFRESH_TOKEN_TTL_SECONDS2592000Refresh-token lifetime (30 days).
MCP_OAUTH_AUTH_CODE_TTL_SECONDS60Authorization-code lifetime.
OAUTH_REGISTER_RATE_LIMIT_PER_HOUR10Per-IP cap on Dynamic Client Registration (/oauth/register).
MCP_OAUTH_TOKEN_SECRETAPI_KEY_SECRETHMAC secret for hashing OAuth codes/tokens at rest. Set an independent 64-char value in production.

Object storage, AI, publishing, push, quota, cron

Section titled “Object storage, AI, publishing, push, quota, cron”
Optional services and encryption keys
AreaVariables
Upload storage (opt-in: STORAGE_DRIVER=s3; default local)STORAGE_DRIVER, S3_BUCKET, S3_REGION, S3_ACCESS_KEY, S3_SECRET_KEY, S3_ENDPOINT, S3_FORCE_PATH_STYLE, S3_PRESIGN_TTL_SECONDS — see below
AILLM_ENCRYPTION_KEY (required) — AI assistant
PublishingPUBLISHING_ENCRYPTION_KEY (required), GIT_MIRROR_ALLOWED_HOSTS
Web PushVAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT — enables the push channel. Step-by-step setup + key generation: Notifications → web push setup
Storage quotaDEFAULT_TEAM_STORAGE_QUOTA_BYTES, STORAGE_QUOTA_WARN_PERCENT
BackupsBACKUP_ENABLED, BACKUP_PATH, BACKUP_SCHEDULE, BACKUP_RETENTION_DAILY/WEEKLY, BACKUP_S3_BUCKET, BACKUP_PG_DUMP_TIMEOUT_MS
Off-site DRBACKUP_RCLONE_REMOTE, BACKUP_POST_HOOK — encrypted off-site copies, see Backups + Disaster Recovery
CronCRON_SECRET for POST /api/admin/cron.cleanuprequired in production (drives scheduled cleanup + invite reminders)
ObservabilityMETRICS_TOKEN (Prometheus /api/metrics); SENTRY_DSN, SENTRY_ENVIRONMENT, SENTRY_RELEASE (error tracking — see below)
MCP CLIORIMORA_API_KEY — see MCP guide

Webhook/publishing auto-pause thresholds (WEBHOOK_*, PUBLISHING_*) and SESSION_IDLE_TIMEOUT_DAYS are documented inline in .env.example.

Error tracking. Set SENTRY_DSN to forward unexpected 5xx server errors and SSO authentication failures to any Sentry-compatible backend — sentry.io, a self-hosted Sentry, or GlitchTip all work with only the DSN changing (no vendor lock-in). Routine 4xx is never sent, and email addresses and token-shaped strings are scrubbed from payloads. Leave it empty to disable. SENTRY_ENVIRONMENT defaults to NODE_ENV; SENTRY_RELEASE (e.g. a git SHA) groups issues by version. The metrics endpoint, health probes, error tracking, and correlation IDs are covered together in Observability.

Some defaults are stored per team in the database (document width, locale, revision retention). See the admin settings UI and REST API overview.

By default, uploads (document attachments, avatars, team logos) are stored on disk under ./uploads and served by the app. Set STORAGE_DRIVER=s3 to store them in an S3-compatible bucket instead — AWS S3, MinIO, Cloudflare R2 or Backblaze B2.

VariablePurpose
STORAGE_DRIVERlocal (default) or s3
S3_BUCKETBucket name (required when s3)
S3_ACCESS_KEY / S3_SECRET_KEYCredentials (required when s3)
S3_REGIONRegion (default us-east-1)
S3_ENDPOINTCustom endpoint for non-AWS S3 (MinIO/R2/B2); empty = real AWS S3
S3_FORCE_PATH_STYLEtrue for MinIO and some self-hosted gateways
S3_PRESIGN_TTL_SECONDSLifetime of presigned GET URLs (default 300)

The bucket stays private — files are served via short-lived presigned URLs that the upload routes redirect to, after the same permission check as the local path (so a restricted document’s attachments stay protected). In production, STORAGE_DRIVER=s3 with a missing bucket or credentials is a hard start-up failure rather than a silent fallback.

Orphaned objects (left behind by a rare failed delete) are swept by the scheduled cleanup cron, conservatively: only objects that no row references and that are older than 24 h are removed, so a just-uploaded file is never deleted.

Set the driver at deploy time, not on a running instance. Flipping local → s3 leaves existing on-disk files unreachable (every existing image 404s) until they are backfilled into the bucket; the driver is never auto-detected from S3_*.

Migrating an existing instance. With S3_* set (the driver can stay local), copy the existing uploads into the bucket first:

Terminal window
yarn storage:migrate-s3 # dry run — reports what would copy
yarn storage:migrate-s3 --apply # upload

It is idempotent (re-run to catch files added since) and non-destructive (local files are only read). Once it reports success, set STORAGE_DRIVER=s3 and redeploy. To roll back, set it to local again — the local files are untouched.