what

Configuration

Complete reference for what.toml. All settings, defaults, and options in one place.

[server]

Server binding and request handling.

KeyTypeDefaultDescription
portinteger8085Port to listen on
hoststring127.0.0.1Host to bind to
max_body_sizestring10mbMaximum request body size (kb, mb, gb)
fetch_timeoutinteger10External fetch timeout in seconds
cssstringfullFramework stylesheet: full, minimal, or none
[server]
port = 3000
host = "0.0.0.0"
max_body_size = "25mb"
fetch_timeout = 15

Framework CSS modes

css controls how much of what.css your app serves:

  • full (default) — the whole design system: reset, theme variables, utilities, and component styles. About 14 KB gzipped.
  • minimal — reset, theme variables, and utility classes only. Component styles (buttons, cards, forms, navigation) are cut — roughly half the file. For when you style components yourself but want the utility layer.
  • none — what.css is not injected at all. Bring your own CSS. The file stays available at /static/what.css for manual linking.
Tip: what.css uses CSS cascade layers, so your own unlayered CSS always wins over framework styles regardless of mode. minimal and none are about shipping fewer bytes, not escaping specificity fights.

[session]

Server-side session storage and cookie settings.

KeyTypeDefaultDescription
enabledbooleantrueEnable sessions
storestringsqliteStorage backend: sqlite or cloudflare-kv
cookie_namestringw_sessionSession cookie name
max_ageinteger604800Session lifetime in seconds (7 days)
securebooleantrueSet Secure flag on cookie
databasestringsessions.dbSQLite database file for sessions
[session]
enabled = true
cookie_name = "my_session"
max_age = 86400        # 1 day
secure = false         # set false for local HTTP development
database = "data/sessions.db"

[cache]

In-memory page and data cache.

KeyTypeDefaultDescription
enabledbooleantrueEnable caching
ttlinteger300Default TTL in seconds (5 minutes)
[cache]
enabled = true
ttl = 60

[auth]

JWT-based authentication and route protection.

KeyTypeDefaultDescription
enabledbooleanfalseEnable auth middleware
login_endpointstringBackend API URL for login
logout_endpointstringBackend API URL for logout
jwt_cookie_namestringw_tokenCookie name for JWT
jwt_secretstringauto-generatedSecret for JWT validation
login_pathstring/loginRedirect path for unauthenticated users
after_loginstring/Redirect path after login
protected_pathsarray[]Glob patterns requiring auth
jwt_claimsarray["id", "user_id", "email", "full_name"]Claims to extract from JWT
[auth]
enabled = true
login_endpoint = "https://api.example.com/auth/login"
logout_endpoint = "https://api.example.com/auth/logout"
jwt_secret = "${JWT_SECRET}"
login_path = "/login"
after_login = "/dashboard"
protected_paths = ["/dashboard/*", "/admin/**"]
jwt_claims = ["id", "email", "full_name", "role"]

See Authentication for usage details.

[database]

Database backend selection. When absent, SQLite at data/app.db is used automatically. Tables are created on first write — no schema definition needed.

KeyTypeDefaultDescription
typestringsqliteDatabase type: sqlite, d1, or supabase
pathstringdata/app.dbSQLite database file path (SQLite only)
[database]
type = "sqlite"   # or "d1", "supabase"
path = "data/app.db"  # SQLite only

See Database for backend-specific setup (D1, Supabase).

[datasources.*]

Named datasources connect multiple external backends simultaneously. Each is accessed via the dsn: fetch prefix. See Data Sources for full usage.

KeyTypeDescription
typestringRequired. One of: d1, supabase, sqlite, api
urlstringBase URL (API type only, must start with http:// or https://)
headerstableHTTP headers (API type only)
account_idstringCloudflare account ID (D1 type)
api_tokenstringCloudflare API token (D1 type)
d1_database_idstringD1 database ID (D1 type)
project_urlstringSupabase project URL (Supabase type)
api_keystringSupabase service role key (Supabase type)
pathstringSQLite file path (SQLite type)
[datasources.cloud]
type = "d1"
account_id = "${CF_ACCOUNT_ID}"
api_token = "${CF_API_TOKEN}"
d1_database_id = "${CF_D1_DATABASE_ID}"

[datasources.cms]
type = "supabase"
project_url = "${SUPABASE_URL}"
api_key = "${SUPABASE_KEY}"

[datasources.inventory]
type = "api"
url = "https://api.example.com"
headers = { Authorization = "Bearer ${API_TOKEN}" }
Important: Missing ${ENV_VAR} references in datasource config will cause the server to fail at startup. Make sure all referenced environment variables are set (or defined in .env).

[supabase]

Supabase configuration. Required when [database] type = "supabase".

KeyTypeDescription
project_urlstringSupabase project URL (e.g., https://xxx.supabase.co)
api_keystringSupabase service_role key (NOT the anon key)
[database]
type = "supabase"

[supabase]
project_url = "${SUPABASE_URL}"
api_key = "${SUPABASE_SERVICE_KEY}"

See Database for step-by-step Supabase setup.

[uploads]

File upload handling and storage.

KeyTypeDefaultDescription
enabledbooleanfalseEnable the upload endpoint
providerstringlocalStorage backend: local or r2
directorystringuploadsUpload directory (local only)
max_sizestring10mbMaximum file size
allowed_typesarray[] (all)Allowed MIME types/extensions
[uploads]
enabled = true
provider = "local"
directory = "uploads"
max_size = "25mb"
allowed_types = ["image/*", ".pdf", ".csv"]

See File Uploads for usage details.

[email]

Transactional email configuration. Choose SMTP or Resend API.

KeyTypeDefaultDescription
fromstringSender email address (required)
from_namestringSender display name
template_dirstringemailsEmail template directory

[email.smtp]

KeyTypeDefaultDescription
hoststringSMTP server hostname (required)
portinteger587SMTP port
usernamestringSMTP username (supports ${ENV_VAR})
passwordstringSMTP password (supports ${ENV_VAR})

[email.api]

KeyTypeDefaultDescription
providerstringAPI provider: resend
api_keystringAPI key (supports ${ENV_VAR})
[email]
from = "[email protected]"
from_name = "My App"

[email.smtp]
host = "smtp.mailgun.org"
port = 587
username = "${SMTP_USER}"
password = "${SMTP_PASS}"

See Email for template syntax and form integration.

[cloudflare]

Shared credentials for Cloudflare services (D1, R2, Turnstile).

KeyTypeDescription
account_idstringCloudflare account ID
api_tokenstringAPI token (default for all services)
d1_database_idstringD1 database ID
r2_bucketstringR2 bucket name
r2_public_urlstringR2 public URL prefix
turnstile_site_keystringTurnstile site key (public)
turnstile_secret_keystringTurnstile secret key (server-side)
[cloudflare]
account_id = "${CF_ACCOUNT_ID}"
api_token = "${CF_API_TOKEN}"
d1_database_id = "your-d1-database-id"
r2_bucket = "my-uploads"
r2_public_url = "https://pub-abc123.r2.dev"
turnstile_site_key = "0x..."
turnstile_secret_key = "${CF_TURNSTILE_SECRET}"

[rate_limit]

Rate limiting for sensitive endpoints. Format: "requests/seconds".

KeyTypeDefaultDescription
enabledbooleantrueEnable rate limiting
loginstring5/60Login endpoint (5 per 60s)
uploadstring10/60Upload endpoint (10 per 60s)
actionstring30/60Form action endpoints (30 per 60s)
[rate_limit]
enabled = true
login = "3/60"       # 3 login attempts per minute
upload = "20/60"     # 20 uploads per minute
action = "60/60"     # 60 form submissions per minute

[redirects]

URL redirect rules. Supports exact matches and wildcard prefixes.

[redirects]
"/old-page" = "/new-page"
"/blog/2023/*" = "/archive"
"/legacy/**" = "/modern"

Redirects are applied before route matching. Wildcard patterns follow the same rules as protected_paths: * matches one level, ** matches all depths.

[deploy]

Deployment configuration, saved automatically by run-what deploy.

[deploy.ssh]
host = "77.42.22.50"
user = "root"
remote_dir = "/opt/what"

[deploy.docker]
image = "my-app"
registry = "ghcr.io/username"

Full Example

A complete what.toml showing all sections:

what.toml
[server]
port = 8085
host = "127.0.0.1"
max_body_size = "10mb"
fetch_timeout = 10

[session]
enabled = true
cookie_name = "w_session"
max_age = 604800
secure = true
database = "sessions.db"

[cache]
enabled = true
ttl = 300

[auth]
enabled = true
login_endpoint = "https://api.example.com/auth/login"
logout_endpoint = "https://api.example.com/auth/logout"
jwt_secret = "${JWT_SECRET}"
jwt_cookie_name = "w_token"
login_path = "/login"
after_login = "/dashboard"
protected_paths = ["/dashboard/*", "/admin/**"]
jwt_claims = ["id", "email", "full_name", "role"]

[database]
type = "sqlite"
path = "data/app.db"

[uploads]
enabled = true
provider = "local"
directory = "uploads"
max_size = "10mb"
allowed_types = ["image/*", ".pdf"]

[email]
from = "[email protected]"
from_name = "My App"

[email.smtp]
host = "smtp.mailgun.org"
port = 587
username = "${SMTP_USER}"
password = "${SMTP_PASS}"

[cloudflare]
account_id = "${CF_ACCOUNT_ID}"
api_token = "${CF_API_TOKEN}"
d1_database_id = "your-d1-database-id"
r2_bucket = "my-uploads"
r2_public_url = "https://pub-abc123.r2.dev"
turnstile_site_key = "0x..."
turnstile_secret_key = "${CF_TURNSTILE_SECRET}"

# Supabase (alternative to D1)
# [supabase]
# project_url = "${SUPABASE_URL}"
# api_key = "${SUPABASE_SERVICE_KEY}"

# Named datasources (dsn: prefix in fetch directives)
# [datasources.external_api]
# type = "api"
# url = "https://api.example.com"
# headers = { Authorization = "Bearer ${API_TOKEN}" }

[rate_limit]
enabled = true
login = "5/60"
upload = "10/60"
action = "30/60"

[redirects]
"/old-blog" = "/blog"
"/legacy/*" = "/modern"

[deploy.ssh]
host = "77.42.22.50"
user = "root"
remote_dir = "/opt/what"
Tip: Most sections are optional. An empty what.toml (or no file at all) uses sensible defaults for everything. Add sections only when you need to override the defaults.