CLI Reference
CLI Reference — all runtime flags
FraiseQL uses a single fraiseql.toml file for all project configuration. Secrets (database passwords, JWT keys) stay in environment variables — the TOML file references them by name.
| Section | Status |
|---|---|
[project] | Stable |
[database] | Stable |
[server] | Stable |
[server.cors] | Stable |
[server.tls] | Stable |
[fraiseql] | Stable |
[fraiseql.apq] | Stable |
[fraiseql.caching] | Stable |
[fraiseql.mcp] | Stable |
[query_defaults] | Stable |
[security] | Stable |
[security.enterprise] | Stable |
[security.error_sanitization] | Stable |
[security.rate_limiting] | Stable |
[security.state_encryption] | Stable |
[security.pkce] | Stable |
[security.api_keys] | Stable |
[security.token_revocation] | Stable |
[security.trusted_documents] | Stable |
[validation] | Stable |
[subscriptions] | Stable |
[debug] | Stable |
[federation] | Stable |
[observers] | Stable |
my-project/├── fraiseql.toml├── schema.json└── db/Generated by fraiseql init. The only required fields are the database URL reference and schema path.
[project]name = "my-api"version = "0.1.0"database_target = "postgresql"
[database]url = "${DATABASE_URL}"
[fraiseql]schema_file = "schema.json"output_file = "schema.compiled.json"Run with:
DATABASE_URL="postgresql://localhost:5432/mydb" fraiseql runEvery TOML value can be overridden at runtime with a CLI flag or environment variable. CLI flags take precedence over environment variables, which take precedence over TOML.
# Override port at runtime without changing fraiseql.tomlfraiseql run --port 9000
# Environment variable override (FRAISEQL_ prefix, __ for nesting)FRAISEQL_SERVER__PORT=9000 fraiseql run| TOML key | Environment variable |
|---|---|
server.port | FRAISEQL_SERVER__PORT |
server.host | FRAISEQL_SERVER__HOST |
database.url | DATABASE_URL or FRAISEQL_DATABASE__URL |
database.pool_max | FRAISEQL_DATABASE__POOL_MAX |
Project metadata used during compilation.
[project]name = "my-api"version = "1.0.0"description = "My GraphQL API"database_target = "postgresql"| Field | Type | Default | Description |
|---|---|---|---|
name | string | "my-fraiseql-app" | Project identifier |
version | string | "1.0.0" | Semantic version |
description | string | — | Human-readable description |
database_target | string | — | postgresql, mysql, sqlite, sqlserver |
Database connection and connection pool settings. Never put credentials directly in this file — use ${ENV_VAR} references.
[database]url = "${DATABASE_URL}"pool_min = 2pool_max = 20connect_timeout_ms = 5000idle_timeout_ms = 600000ssl_mode = "prefer"| Field | Type | Default | Description |
|---|---|---|---|
url | string | — | Connection URL or env var reference |
pool_min | integer | 2 | Minimum pool connections |
pool_max | integer | 20 | Maximum pool connections |
connect_timeout_ms | integer | 5000 | Connection acquisition timeout in ms |
idle_timeout_ms | integer | 600000 | Idle connection lifetime in ms |
ssl_mode | string | "prefer" | disable, allow, prefer, or require |
Env var reference syntax:
url = "${DATABASE_URL}" # Required env varurl = "${DATABASE_URL:-postgresql://localhost/mydb}" # With fallbackHTTP server binding and request handling.
[server]host = "0.0.0.0"port = 8080request_timeout_ms = 30000keep_alive_secs = 75| Field | Type | Default | Description |
|---|---|---|---|
host | string | "0.0.0.0" | Bind address |
port | integer | 8080 | Listen port |
request_timeout_ms | integer | 30000 | Request timeout in ms |
keep_alive_secs | integer | 75 | TCP keep-alive in seconds |
Cross-Origin Resource Sharing. Required when your frontend is served from a different origin.
[server.cors]origins = ["https://app.example.com", "http://localhost:3000"]credentials = true| Field | Type | Default | Description |
|---|---|---|---|
origins | array | [] | Allowed origins (empty = all origins) |
credentials | bool | false | Allow cookies and auth headers |
TLS termination. For most deployments, terminate TLS at the load balancer and run fraiseql on plain HTTP internally.
[server.tls]enabled = truecert_file = "/etc/ssl/certs/server.pem"key_file = "/etc/ssl/private/server.key"min_version = "1.2"| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable TLS |
cert_file | string | — | Path to certificate file (PEM) |
key_file | string | — | Path to private key file (PEM) |
min_version | string | "1.2" | Minimum TLS version: "1.2" or "1.3" |
Compilation settings.
[fraiseql]schema_file = "schema.json"output_file = "schema.compiled.json"| Field | Type | Default | Description |
|---|---|---|---|
schema_file | string | "schema.json" | Path to the schema definition |
output_file | string | "schema.compiled.json" | Path for the compiled output |
Automatic Persisted Queries — cache query documents by hash on the server, reducing payload size for repeated operations.
[fraiseql.apq]enabled = truecache_size = 1000 # Maximum number of persisted queries to cache (LRU)| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable APQ support |
cache_size | integer | 1000 | LRU cache capacity for persisted query documents |
Response caching for query results. Reduces database load for read-heavy workloads.
[fraiseql.caching]enabled = truebackend = "redis" # "memory" or "redis"redis_url = "${REDIS_URL}" # required when backend = "redis"max_memory_entries = 1000 # capacity when backend = "memory"| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable response caching |
backend | string | "memory" | Cache backend: "memory" (single-instance) or "redis" (distributed) |
redis_url | string | — | Redis connection URL (required when backend = "redis") |
max_memory_entries | integer | 1000 | LRU capacity for the in-memory backend |
Per-query TTL is set via cache_ttl_seconds= in the @fraiseql.query decorator. See Caching.
Alias for [security.trusted_documents] — both namespaces are accepted. Prefer [security.trusted_documents] for consistency. See Security.
Model Context Protocol server — exposes FraiseQL queries and mutations as MCP tools for AI agents (Claude Desktop, etc.).
[fraiseql.mcp]enabled = truetransport = "http" # "http" | "stdio" | "both"path = "/mcp" # HTTP endpoint pathrequire_auth = true # Require JWT/API key for MCP requests
# Restrict which operations are exposedinclude = [] # Whitelist by operation name (empty = all)exclude = [] # Blacklist by operation name| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable the MCP server |
transport | string | "http" | Transport: "http" (in-process endpoint), "stdio" (for CLI agents), or "both" |
path | string | "/mcp" | HTTP path for the MCP endpoint |
require_auth | bool | true | Require the same JWT/API key as the GraphQL endpoint |
include | array | [] | Whitelist of operation names to expose (empty = all) |
exclude | array | [] | Blacklist of operation names to hide from MCP clients |
See MCP Server for setup with Claude Desktop.
Project-wide defaults for which auto-parameters (where, order_by, limit, offset) are enabled on list queries. Per-query auto_params= overrides remain possible and are now partial — only specify the flags that differ from the project default.
Priority (lowest → highest):
[query_defaults] in fraiseql.tomlauto_params={"limit": True} in Python decorator[query_defaults]where = true # default: trueorder_by = true # default: truelimit = true # default: trueoffset = true # default: true| Field | Type | Default | Description |
|---|---|---|---|
where | bool | true | Enable where filter argument on list queries |
order_by | bool | true | Enable orderBy sort argument |
limit | bool | true | Enable limit argument |
offset | bool | true | Enable offset argument |
Example — Relay-first project (disable offset pagination globally):
[query_defaults]limit = falseoffset = false# Only this admin query re-enables limit/offset@fraiseql.query(sql_source="v_admin_log", auto_params={"limit": True, "offset": True})def admin_logs() -> list[AdminLog]: ...Compile-time warnings:
limit = false on a non-relay list query → unbounded table scan warninglimit = true + order_by = false → non-deterministic pagination warningAuthorization policies enforced at runtime. JWT verification is configured via environment variables — there is no [auth] TOML section.
[security]default_policy = "authenticated" # or "public"
[[security.rules]]name = "owner_only"rule = "user.id == object.owner_id"description = "User can only access their own data"cacheable = truecache_ttl_seconds = 300
[[security.policies]]name = "admin_only"type = "rbac" # rbac | abac | custom | hybridroles = ["admin"]strategy = "any" # any | all | exactlycache_ttl_seconds = 600
[[security.field_auth]]type_name = "User"field_name = "email"policy = "admin_only"| Field | Type | Default | Description |
|---|---|---|---|
default_policy | string | "authenticated" | Default access: "authenticated" or "public" |
Enterprise feature flags for audit logging and legacy rate limiting.
[security.enterprise]rate_limiting_enabled = falseauth_endpoint_max_requests = 100auth_endpoint_window_seconds = 60audit_logging_enabled = falseaudit_log_level = "info"| Field | Type | Default | Description |
|---|---|---|---|
rate_limiting_enabled | bool | false | Enable built-in rate limiting |
auth_endpoint_max_requests | integer | 100 | Max auth endpoint requests per window |
auth_endpoint_window_seconds | integer | 60 | Auth rate limit window in seconds |
audit_logging_enabled | bool | false | Enable audit logging |
audit_log_level | string | "info" | Audit log verbosity: "debug", "info", "warn" |
Controls what error detail is returned to clients. Disabled by default — enable in production to prevent SQL errors and stack traces from reaching clients.
[security.error_sanitization]enabled = false # opt-inhide_implementation_details = true # maximally safe when enabledsanitize_database_errors = truecustom_error_message = "An internal error occurred"| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable error sanitization (opt-in) |
hide_implementation_details | bool | true | Strip stack traces and implementation paths |
sanitize_database_errors | bool | true | Replace DB error messages with generic text |
custom_error_message | string | "An internal error occurred" | Message sent to clients when sanitizing |
ValidationError, Forbidden, and NotFound codes pass through unchanged — clients need these to act on errors. Only InternalServerError and DatabaseError codes are sanitized.
Token bucket rate limiting applied globally, plus per-endpoint limits for auth routes. In-memory per instance (no cross-instance coordination).
[security.rate_limiting]enabled = false # opt-inrequests_per_second = 100requests_per_second_per_user = 1000 # default: 10× requests_per_secondburst_size = 200trust_proxy_headers = false # set true only when behind a trusted reverse proxyauth_start_max_requests = 5auth_start_window_secs = 60auth_callback_max_requests = 10auth_callback_window_secs = 60auth_refresh_max_requests = 20auth_refresh_window_secs = 300auth_logout_max_requests = 30auth_logout_window_secs = 60failed_login_max_attempts = 10failed_login_lockout_secs = 900# redis_url = "${REDIS_URL}" # requires redis-rate-limiting Cargo feature| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable rate limiting (opt-in) |
requests_per_second | integer | 100 | Global token bucket refill rate |
requests_per_second_per_user | integer | requests_per_second × 10 | Per-authenticated-user limit |
burst_size | integer | 200 | Maximum burst above steady-state rate |
trust_proxy_headers | bool | false | Read X-Real-IP / X-Forwarded-For for client IP. Enable only when behind a trusted reverse proxy — spoofable otherwise |
redis_url | string | — | Redis connection URL for distributed rate limiting (requires redis-rate-limiting feature) |
auth_start_max_requests | integer | 5 | Max /auth/start requests per window |
auth_start_window_secs | integer | 60 | Window for /auth/start limit |
auth_callback_max_requests | integer | 10 | Max /auth/callback requests per window |
auth_callback_window_secs | integer | 60 | Window for /auth/callback limit |
auth_refresh_max_requests | integer | 20 | Max /auth/refresh requests per window |
auth_refresh_window_secs | integer | 300 | Window for /auth/refresh limit |
auth_logout_max_requests | integer | 30 | Max /auth/logout requests per window |
auth_logout_window_secs | integer | 60 | Window for /auth/logout limit |
failed_login_max_attempts | integer | 10 | Failed auth attempts before lockout |
failed_login_lockout_secs | integer | 900 | Lockout duration in seconds |
AEAD encryption for OAuth state blobs stored between /auth/start and /auth/callback. Requires a 32-byte key as a 64-character hex string.
[security.state_encryption]enabled = false # opt-inalgorithm = "chacha20-poly1305" # or "aes-256-gcm"key_source = "env"key_env = "STATE_ENCRYPTION_KEY"Generate a key: openssl rand -hex 32
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable state encryption (opt-in) |
algorithm | string | "chacha20-poly1305" | AEAD cipher: "chacha20-poly1305" or "aes-256-gcm" |
key_source | string | "env" | Key source — only "env" supported |
key_env | string | "STATE_ENCRYPTION_KEY" | Environment variable with the 64-char hex key |
PKCE (Proof Key for Code Exchange) for the OAuth /auth/start → /auth/callback flow.
[security.pkce]enabled = false # opt-in; requires state_encryption.enabled = truecode_challenge_method = "S256" # or "plain" (warns at startup in non-dev environments)state_ttl_secs = 600# redis_url = "${REDIS_URL}" # requires redis-pkce Cargo feature| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable PKCE (opt-in) |
code_challenge_method | string | "S256" | Challenge method: "S256" (recommended) or "plain" |
state_ttl_secs | integer | 600 | OAuth state token lifetime in seconds |
redis_url | string | — | Redis connection URL for distributed PKCE state (requires redis-pkce feature) |
API key authentication for service-to-service and CI/CD access. Keys are stored hashed — the plaintext is returned once at creation and never stored.
[security.api_keys]enabled = trueheader = "X-API-Key" # HTTP header namehash_algorithm = "sha256" # "sha256" (fast) or "argon2" (production)storage = "postgres" # "postgres" or "env" (static keys, CI only)
# Static keys — testing and CI only. Never in production.[[security.api_keys.static]]key_hash = "sha256:abc123..." # echo -n "secret" | sha256sumscopes = ["read:*"]name = "ci-readonly"| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable API key authentication |
header | string | "X-API-Key" | HTTP header to read the key from |
hash_algorithm | string | "sha256" | Hashing algorithm: "sha256" or "argon2" |
storage | string | "postgres" | Key storage backend: "postgres" (production) or "env" (static, CI only) |
For production, store hashed keys in the fraiseql_api_keys table. See Authentication for the table schema and mutation patterns.
Revoke JWTs before their exp claim expires — for logout, key rotation, or security incidents.
[security.token_revocation]enabled = truebackend = "redis" # "redis" or "postgres"require_jti = true # reject JWTs without a jti claimfail_open = false # if store unreachable, deny (not allow) the request| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable token revocation |
backend | string | — | Storage backend: "redis" (recommended) or "postgres" |
require_jti | bool | false | Reject tokens that have no jti claim |
fail_open | bool | false | When false (default), deny requests if the revocation store is unreachable. Set true to allow through on store failure |
When enabled, two endpoints become available:
POST /auth/revoke — revoke the caller’s own token (self-logout)POST /auth/revoke-all — revoke all tokens for a user (requires admin:revoke scope)Revoked JTIs are stored until the token’s exp expires — no manual cleanup needed.
Query allowlisting — only permit GraphQL operations present in a pre-approved manifest. Useful for hardening production APIs against arbitrary query execution.
[security.trusted_documents]enabled = truemode = "strict" # "strict" | "permissive"manifest_path = "./trusted-documents.json"reload_interval_secs = 300 # 0 = no hot-reload| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable trusted document enforcement |
mode | string | "permissive" | "strict" rejects unknown operations; "permissive" logs them but allows through |
manifest_path | string | — | Path to local JSON manifest (hash → query) |
manifest_url | string | — | URL to fetch the manifest from at startup |
reload_interval_secs | integer | 0 | How often to poll for manifest changes (0 = no reload) |
Query depth and complexity limits enforced at parse time, before execution. Queries exceeding either limit receive a validation error — no database round-trip occurs.
[validation]max_query_depth = 10max_query_complexity = 100| Field | Type | Default | Description |
|---|---|---|---|
max_query_depth | integer | 10 | Maximum allowed field nesting depth |
max_query_complexity | integer | 100 | Maximum allowed query complexity score |
Complexity is calculated by summing field weights (lists count more than scalars). Adjust thresholds based on your schema — a schema with many list fields may need a higher limit for legitimate queries.
WebSocket subscription settings. FraiseQL supports both graphql-transport-ws (modern) and graphql-ws (Apollo legacy) — protocol is negotiated from the Sec-WebSocket-Protocol header automatically.
[subscriptions]max_subscriptions_per_connection = 10
[subscriptions.hooks]on_connect = "https://auth.example.com/ws/connect"on_subscribe = "https://auth.example.com/ws/subscribe"on_disconnect = "https://auth.example.com/ws/disconnect"timeout_ms = 500| Field | Type | Default | Description |
|---|---|---|---|
max_subscriptions_per_connection | integer | unlimited | Maximum concurrent subscriptions per WebSocket connection |
[subscriptions.hooks] — webhook URLs invoked during connection lifecycle:
| Field | Type | Default | Description |
|---|---|---|---|
on_connect | string | — | URL to POST on connection_init. Fail-closed: connection rejected if hook returns non-2xx |
on_subscribe | string | — | URL to POST before a subscription is registered. Fail-closed |
on_disconnect | string | — | URL to POST on WebSocket close. Fire-and-forget (errors ignored) |
timeout_ms | integer | 500 | Timeout for fail-closed hooks (on_connect, on_subscribe) |
Development and diagnostics features. All debug capabilities are disabled by default and gated behind a master enabled switch. Never enable in production.
[debug]enabled = true # master switch — required for all other flagsdatabase_explain = true # include EXPLAIN output in /api/v1/query/explain responsesexpose_sql = true # include generated SQL in explain responses (default when enabled)| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Master switch. All debug features require this to be true |
database_explain | bool | false | Run EXPLAIN against the database and include the query plan in /api/v1/query/explain responses |
expose_sql | bool | true | Include generated SQL in explain responses |
Explain endpoint:
POST /api/v1/query/explainContent-Type: application/json
{"query": "{ users { id name } }"}Response includes the generated SQL and (when database_explain = true) the Postgres query plan. Use this to diagnose slow queries without enabling general query logging.
Apollo Federation v2 support and multi-database federation settings.
[federation]enabled = trueapollo_version = 2
[[federation.entities]]name = "User"key_fields = ["id"]Per-entity circuit breaker for Apollo Federation subgraph calls. Automatically opens after consecutive errors, returning 503 Service Unavailable with a Retry-After header instead of cascading to a gateway timeout.
[federation.circuit_breaker]enabled = truefailure_threshold = 5 # open after N consecutive errorsrecovery_timeout_secs = 30 # half-open probe after this many secondssuccess_threshold = 2 # successes in HalfOpen needed to close
# Per-entity override[[federation.circuit_breaker.per_database]]database = "Order"failure_threshold = 3recovery_timeout_secs = 60| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Enable the circuit breaker |
failure_threshold | integer | 5 | Consecutive errors before opening |
recovery_timeout_secs | integer | 30 | Seconds before attempting a recovery probe |
success_threshold | integer | 2 | Successes in HalfOpen state before closing |
[[federation.circuit_breaker.per_database]] accepts an array of per-entity overrides. Each entry requires a database key matching the federation entity name. All three threshold fields are optional (inherit from the global config if omitted).
Prometheus gauge: fraiseql_federation_circuit_breaker_state{entity="..."} (0 = Closed, 1 = Open, 2 = HalfOpen).
Event observer system. Supports in-memory, Redis, NATS, PostgreSQL, and MySQL backends.
[observers]enabled = truebackend = "nats" # "in-memory" | "redis" | "nats" | "postgresql" | "mysql"nats_url = "${FRAISEQL_NATS_URL}" # required when backend = "nats"# redis_url = "${REDIS_URL}" # required when backend = "redis"
[[observers.handlers]]event = "user.created"target = "v_notify_new_user"| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable the observer system |
backend | string | "in-memory" | Event backend: in-memory, redis, nats, postgresql, mysql |
nats_url | string | — | NATS server URL (required when backend = "nats"). Overridable via FRAISEQL_NATS_URL env var |
redis_url | string | — | Redis connection URL (required when backend = "redis") |
[project]name = "blog-api"version = "1.0.0"description = "Blog GraphQL API"database_target = "postgresql"
[database]url = "${DATABASE_URL}"pool_min = 5pool_max = 50connect_timeout_ms = 5000ssl_mode = "prefer"
[server]host = "0.0.0.0"port = 8080request_timeout_ms = 30000
[server.cors]origins = ["https://blog.example.com", "http://localhost:3000"]credentials = true
[fraiseql]schema_file = "schema.json"output_file = "schema.compiled.json"
[security]default_policy = "authenticated"
[[security.policies]]name = "admin_only"type = "rbac"roles = ["admin"]strategy = "any"
[security.enterprise]audit_logging_enabled = trueaudit_log_level = "info"
[security.error_sanitization]enabled = true
[security.rate_limiting]enabled = truerequests_per_second = 100burst_size = 200auth_start_max_requests = 5failed_login_max_attempts = 5failed_login_lockout_secs = 900
[security.api_keys]enabled = truehash_algorithm = "argon2"storage = "postgres"
[security.token_revocation]enabled = truebackend = "redis"require_jti = truefail_open = false
[security.trusted_documents]enabled = truemode = "strict"manifest_path = "./trusted-documents.json"
[validation]max_query_depth = 10max_query_complexity = 100
[subscriptions]max_subscriptions_per_connection = 10Run in production:
DATABASE_URL="postgresql://..." fraiseql runCLI Reference
CLI Reference — all runtime flags
Security
Security features — detailed security guide
Deployment
Deployment — production configuration