Configuration¶
wfm is configured with environment variables. Durations use Go syntax (15m, 24h); lists
are comma-separated. Invalid configuration stops the process at startup with every problem listed.
Core¶
| Variable | Default | |
|---|---|---|
WFM_MODE |
all |
web, hooks, worker or all (also wfm serve --mode=) |
WFM_ENV |
production |
anything else enables development conveniences and relaxes WFM_SECRET_KEY |
WFM_DATABASE_URL |
postgres://wfm:wfm@localhost:5432/wfm?sslmode=disable |
PostgreSQL 16+ (17 recommended) |
WFM_DATABASE_POOLER_HOST |
— | host[:port] of a transaction-mode PgBouncer (such as a CNPG Pooler). Queries go there with the credentials and database of WFM_DATABASE_URL, which stays the direct address for live-event listeners and migrations. See deployment.md |
WFM_DATABASE_DIRECT_URL |
— | the other way round: set when WFM_DATABASE_URL already points at the pooler, to give the direct address. Set one of the two, not both |
WFM_PUBLIC_URL |
http://localhost:8080 |
external URL of the UI; used for OIDC redirects and links |
WFM_HOOKS_PUBLIC_URL |
WFM_PUBLIC_URL |
external URL where /hooks and /scim are reachable |
WFM_SECRET_KEY |
— | required in production; 32+ random bytes; encrypts stored secrets (SCM keys, webhook secrets) and signs cookies. Keep it stable — changing it makes stored secrets unreadable |
WFM_HTTP_ADDR |
:8080 |
|
WFM_METRICS_ADDR |
:9090 |
Prometheus /metrics |
WFM_LOG_LEVEL |
info |
debug, info, warn, error |
WFM_LOG_FORMAT |
json |
json or text |
WFM_SESSION_TTL |
168h |
browser session lifetime |
WFM_DEV_LOGIN |
false |
passwordless sign-in for development; refused when WFM_ENV=production |
Request limits¶
Requests to the API, sign-in, MCP, SCIM and webhooks are limited per process (with several
replicas, multiply by the replica count). Over a limit the server answers 429 with Retry-After
and counts it in wfm_rate_limited_total. Limits are on by default when WFM_ENV=production and
off otherwise; each value is requests per minute, with bursts of half that, and 0 turns a
limit off.
| Variable | Default (production) | |
|---|---|---|
WFM_RATE_LIMIT_PER_MINUTE |
600 |
per access token or browser session |
WFM_RATE_LIMIT_ANONYMOUS_PER_MINUTE |
120 |
per client address, requests without credentials (sign-in pages, metadata) |
WFM_RATE_LIMIT_ADDRESS_PER_MINUTE |
3000 |
per client address, all requests (offices behind one address share it) |
WFM_RATE_LIMIT_HOOKS_PER_MINUTE |
1200 |
per webhook endpoint (/hooks/...) |
WFM_TRUSTED_PROXIES |
private and loopback ranges | CIDRs of proxies allowed to set X-Forwarded-For (the ingress controller, a load balancer). Requests from anywhere else are their own client, whatever the header says |
Sign-in and provisioning¶
See identity.md.
| Variable | Default | |
|---|---|---|
WFM_GOOGLE_CLIENT_ID, WFM_GOOGLE_CLIENT_SECRET |
— | enable Google sign-in |
WFM_OIDC_PROVIDERS |
google if a Google client id is set |
provider ids, e.g. google,okta |
WFM_OIDC_<ID>_ISSUER, _CLIENT_ID, _CLIENT_SECRET |
— | per provider (Google's issuer is preset) |
WFM_OIDC_<ID>_DISPLAY_NAME, _SCOPES, _ALLOWED_DOMAINS |
optional per provider | |
WFM_OIDC_<ID>_GROUPS_CLAIM |
— | claim paths whose values are the person's groups at each sign-in, e.g. groups,realm_access.roles (identity.md) |
WFM_OIDC_<ID>_GROUPS_PREFIX |
— | only claim groups starting with this |
WFM_OIDC_<ID>_SINGLE_LOGOUT |
false |
signing out also signs out of the provider (RP-initiated logout) |
WFM_OIDC_<ID>_API_AUDIENCE |
— | accept the provider's access tokens with this audience as API tokens; must differ from _CLIENT_ID |
WFM_OIDC_<ID>_API_CLIENTS |
— | clientid=serviceaccount,…: client-credentials tokens act as that service account |
WFM_ALLOWED_DOMAINS |
— | default allowed domains for every provider |
WFM_JIT_PROVISIONING |
auto |
auto, true, false |
WFM_BOOTSTRAP_ADMINS |
— | emails that are always organisation admins |
WFM_LOCAL_LOGIN |
true |
email and password sign-in for local accounts (identity.md); false hides the form and refuses it |
WFM_LOCAL_REQUIRE_2FA |
false |
local accounts must set up two-factor authentication at their next sign-in |
WFM_LOCAL_LOCKOUT_ATTEMPTS |
10 |
consecutive failed sign-ins that lock a local account (0 disables the lockout) |
WFM_LOCAL_LOCKOUT_DURATION |
15m |
how long a lockout lasts |
WFM_DELETED_USER_RETENTION |
2160h (90 days) |
then deleted users are anonymised |
WFM_GOOGLE_DIRECTORY_ENABLED |
false |
Google Workspace directory sync |
WFM_GOOGLE_SERVICE_ACCOUNT_KEY |
— | path, JSON or base64 JSON |
WFM_GOOGLE_ADMIN_SUBJECT |
— | admin to impersonate (domain-wide delegation) |
WFM_GOOGLE_CUSTOMER |
my_customer |
|
WFM_GOOGLE_SYNC_GROUP |
— | access group email |
WFM_GOOGLE_SYNC_ORG_UNIT |
— | org unit path, e.g. /Engineering |
WFM_GOOGLE_SYNC_INTERVAL |
15m |
|
WFM_GOOGLE_SYNC_GROUPS |
true |
mirror groups |
WFM_GOOGLE_GROUP_PREFIX |
— | only mirror groups whose email starts with this |
WFM_KEYCLOAK_ENABLED |
false |
Keycloak directory sync (identity.md); not together with Google's |
WFM_KEYCLOAK_URL |
— | Keycloak's base URL, e.g. https://sso.example.com (may be a private, in-cluster address) |
WFM_KEYCLOAK_REALM |
— | the realm to sync |
WFM_KEYCLOAK_CLIENT_ID, WFM_KEYCLOAK_CLIENT_SECRET |
— | a confidential client whose service account has the realm-management roles view-users and query-groups |
WFM_KEYCLOAK_PROVIDER |
keycloak |
the WFM_OIDC_PROVIDERS id people sign in with; their Keycloak user id is the subject there |
WFM_KEYCLOAK_SYNC_GROUP |
— (everyone) | access group path, e.g. /workflow-users; its subgroups' members count too |
WFM_KEYCLOAK_SYNC_GROUPS |
true |
mirror groups (named by path) and their direct members |
WFM_KEYCLOAK_GROUP_PREFIX |
— | only mirror groups whose name starts with this (case-insensitive) |
WFM_KEYCLOAK_SYNC_INTERVAL |
15m |
0 runs only on Run now |
AI assistants (OAuth)¶
Assistants connect to /mcp by URL and sign in through the browser; see mcp.md.
WFM_PUBLIC_URL is the OAuth issuer and <WFM_PUBLIC_URL>/mcp the token audience, so it must be
the address assistants use (https in production).
| Variable | Default | |
|---|---|---|
WFM_OAUTH_ENABLED |
true |
false: no OAuth; /mcp accepts personal access tokens only |
WFM_OAUTH_DYNAMIC_REGISTRATION |
true |
allow apps to register themselves at /oauth/register (RFC 7591); false leaves client ID metadata documents |
WFM_OAUTH_CLIENT_METADATA_DOCUMENTS |
true |
accept https client ids that point at a client ID metadata document (fetched under the outbound guard below) |
WFM_OAUTH_ACCESS_TOKEN_TTL |
1h |
access token lifetime (1m–24h) |
WFM_OAUTH_REFRESH_TOKEN_TTL |
720h (30 days) |
refresh token lifetime, renewed on every refresh (1h–8784h) |
Attachments¶
Without S3, attachments are stored in PostgreSQL.
| Variable | Default | |
|---|---|---|
WFM_S3_ENDPOINT |
— | e.g. s3.amazonaws.com, minio.storage:9000 |
WFM_S3_BUCKET |
— | |
WFM_S3_REGION |
us-east-1 |
|
WFM_S3_ACCESS_KEY, WFM_S3_SECRET_KEY |
— | omit to use the environment's credentials (e.g. IRSA) |
WFM_S3_USE_SSL |
true |
|
WFM_ATTACHMENT_MAX_MB |
25 |
Time tracking¶
Durations such as 1w 2d 3h count working days and weeks; see time-tracking.md.
| Variable | Default | |
|---|---|---|
WFM_TIME_HOURS_PER_DAY |
8 |
hours in a working day (1d); more than 0, at most 24 |
WFM_TIME_DAYS_PER_WEEK |
5 |
working days in a week (1w); more than 0, at most 7 |
Background work and integrations¶
| Variable | Default | |
|---|---|---|
WFM_WORKER_CONCURRENCY |
10 |
jobs per queue per worker process |
WFM_WORKER_POLL_INTERVAL |
1s |
how often workers look for new jobs. Without a pooler they are also woken by NOTIFY; behind one they only poll, so this is the pickup delay (minimum 100ms) |
WFM_SCM_RECONCILE_INTERVAL |
30m |
0 disables |
WFM_AUTOMATION_MAX_DEPTH |
5 |
maximum chain of rules triggering rules |
WFM_WEBHOOK_ALLOW_PRIVATE |
false |
allow outbound calls to any private address (prefer the allowlist below) |
WFM_OUTBOUND_ALLOWED_HOSTS |
— | host names (or .domain suffixes) that may resolve to private addresses, e.g. a self-hosted Mattermost |
Email¶
Without WFM_SMTP_HOST, email is not sent: messages are logged and kept in memory (in
development they can be read at /api/v1/dev/mailbox). Email is queued after the change that
causes it and retried on failure.
| Variable | Default | |
|---|---|---|
WFM_SMTP_HOST |
— | SMTP server, e.g. smtp.sendgrid.net, email-smtp.eu-west-1.amazonaws.com |
WFM_SMTP_PORT |
587 |
|
WFM_SMTP_TLS |
starttls |
starttls, tls (implicit, usually port 465) or none |
WFM_SMTP_USERNAME, WFM_SMTP_PASSWORD |
— | omit for an unauthenticated relay |
WFM_SMTP_FROM |
Workflow <workflow@localhost> |
sender address; its display name also signs notification emails |
WFM_EMAIL_BATCH_WINDOW |
2m |
immediate notification emails wait this long so several changes to one issue arrive as one email (Notifications) |
Slack and Mattermost¶
See chat.md. Channel notifications need no configuration; these enable the /wfm command.
| Variable | Default | |
|---|---|---|
WFM_SLACK_SIGNING_SECRET |
— | Slack app signing secret; enables /hooks/chat/slack |
WFM_MATTERMOST_COMMAND_TOKENS |
— | slash command token(s); enables /hooks/chat/mattermost |
WFM_CHAT_COMMAND_PROJECTS |
every open project | project keys the command may read |
Telemetry¶
Standard OpenTelemetry variables: OTEL_EXPORTER_OTLP_ENDPOINT (or
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) enables trace export; OTEL_EXPORTER_OTLP_HEADERS,
OTEL_TRACES_SAMPLER and friends apply.
CLI¶
wfm workflow … reads WFM_SERVER (base URL) and WFM_TOKEN (a token with the write scope).