Configuration
VEF reads configuration from application.toml through the config module, then injects strongly typed config structs into the rest of the runtime.
File lookup order
At startup, the framework config loader searches for application.toml in:
./configs$VEF_CONFIG_PATH.../configs
If the file cannot be read, startup fails immediately.
Core sections
These sections map directly to the public config package and internal module constructors. For the complete config public surface, including exported structs, fields, and methods, see Configuration Reference.
vef.app
Application-level settings:
[vef.app]
name = "my-app"
port = 8080
body_limit = "32mib"
Key fields:
name: used as the app name and as input to JWT audience generationport: HTTP port for the Fiber appbody_limit: maximum request body size, parsed as a human-readable byte size; defaults to32mibwhen omittedtrusted_proxies: proxy IPs/CIDRs whose forwarding headers Fiber may trust; empty meansX-Forwarded-Foris ignored
vef.api
Default per-operation rate limit, applied to every operation that does
not declare its own OperationSpec.RateLimit:
[vef.api.rate_limit]
max = 100 # default
period = "5m" # default
The limiter counts requests per operation × client (IP + principal) in a sliding window held in process memory, so in a multi-node deployment each node enforces the limit independently.
vef.data_sources
Database connection settings:
[vef.data_sources.primary]
type = "postgres"
host = "127.0.0.1"
port = 5432
user = "postgres"
password = "postgres"
database = "my_app"
schema = "public"
enable_sql_guard = true
The primary entry is mandatory and powers the framework-wide orm.DB
injection. Additional named data sources use the same shape:
[vef.data_sources.analytics]
type = "sqlite"
path = "./analytics.db"
Supported type values (the config.DBKind enum, drivers registered in the framework runtime):
postgresmysqlsqliteoraclesqlserver
Passing an unsupported kind at startup fails with an "unsupported database type" error.
For SQLite, path is optional. When omitted, the framework uses a shared in-memory database.
Network dialects also accept ssl_mode (disable | require | verify-ca
| verify-full, default disable) and ssl_root_cert; dialect notes for
SQL Server and Oracle live in Datasources.
vef.cors
CORS middleware settings:
[vef.cors]
enabled = true
allow_origins = ["http://localhost:3000", "https://my-app.com"]
Key fields:
enabled: enable CORS middlewareallow_origins: list of allowed origins
vef.event
Event bus, transport, routing, and consume middleware settings. A typical minimal setup enables the outbox transport and routes storage events to it:
[vef.event.transports.outbox]
enabled = true
[[vef.event.routing]]
pattern = "vef.storage.*"
transports = ["outbox", "memory"]
Key fields:
default_transport: fallback transport when no routing rule matches; defaults to"memory"async_queue_size: capacity of the async fan-in queue; defaults to4096async_workers: goroutines draining the async queue; defaults to4publish_timeout: cap on an individual transport publish call; defaults to5s
Per-transport blocks:
vef.event.transports.memory: in-process transport (queue_size,full_policy,publish_timeout)vef.event.transports.tx_memory: in-process transactional transport; disabled by default (enabled = false)vef.event.transports.outbox: persistent outbox (enabled,relay_interval,max_retries,batch_size,sink,cleanup_interval,completed_ttl)vef.event.transports.redis_stream: Redis Streams transport; disabled by default (enabled = false)
Middleware and inbox toggles:
vef.event.middleware:logging,tracing,tracing_strict,metrics,recover,inboxvef.event.inbox:retention,processing_lease,cleanup_interval
Routing rules are matched top-to-bottom; the first rule whose pattern
matches wins, and fan-out is expressed by listing multiple transports.
vef.security
Security-related runtime settings:
[vef.security]
secret = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
token_expires = "168h"
refresh_not_before = "15m"
login_rate_limit = 6
refresh_rate_limit = 1
Runtime notes:
secretis the hex-encoded JWT signing key. Leave it empty only for local development; the framework then generates an ephemeral per-process key, so tokens do not survive restart or work across nodes. Generate and set a stable private value for production.- access tokens issued by the built-in JWT token generator expire after
30m token_expirescontrols refresh-token lifetime and defaults to168hrefresh_not_beforedefaults to15m, half of the fixed access-token window- login and refresh rate limits default to
6and1when unset or non-positive
vef.storage
Object storage settings:
[vef.storage]
provider = "filesystem"
[vef.storage.filesystem]
root = "./data/files"
Supported providers:
memoryfilesystemminio
If provider is omitted, VEF uses memory storage.
For non-test deployments, set filesystem or minio; in-memory objects are lost on restart.
The filesystem provider defaults root to ./storage, and MinIO defaults its bucket to vef.app.name or vef-app when minio.bucket is empty.
vef.redis
The default boot graph includes the Redis module during vef.Run(...).
Redis is opt-in. When vef.redis.enabled is omitted or false, the framework injects a nil *redis.Client and skips startup PING; Redis-backed modules that depend on Redis must either stay dormant or require you to enable Redis explicitly.
When enabled = true and connection settings are omitted, the client defaults to:
- host:
127.0.0.1 - port:
6379 - network:
tcp
So in minimal examples, leave vef.redis out unless the application really depends on Redis. When it does, configure enabled = true intentionally.
vef.monitor
Monitoring configuration is injected into the monitor module. The module also applies its own defaults internally.
The default sampling interval is 10s, with a 2s sampling window.
vef.mcp
MCP support is present in the runtime, but the MCP server only activates when enabled in configuration.
The /mcp endpoint requires Bearer auth by default. If vef.mcp.require_auth is omitted or set to true, unauthenticated requests are rejected; set it to false only for deliberately anonymous MCP surfaces.
vef.approval
Approval workflow engine settings:
[vef.approval]
auto_migrate = true
timeout_scan_interval = "1m"
pre_warning_scan_interval = "5m"
cleanup_scan_interval = "24h"
delegation_max_depth = 10
form_snapshot_retention = "2160h" # 90 days
urge_record_retention = "720h" # 30 days
cc_record_retention = "2160h" # 90 days
Key fields:
auto_migrate: run the approval DDL migration on startuptimeout_scan_interval: cadence of the timeout scanner (default: 1m)pre_warning_scan_interval: cadence of the pre-warning scanner (default: 5m)cleanup_scan_interval: cadence of the retention cleanup job (default: 24h)delegation_max_depth: maximum delegation chain depth (default: 10)form_snapshot_retention/urge_record_retention/cc_record_retention: retention windows for the corresponding tables
config.ApprovalConfig.ApplyDefaults() fills the timing and retention defaults above but does not enable AutoMigrate; migrations run only when auto_migrate = true.
The outbox-related fields previously lived under
[vef.approval](outbox_relay_interval,outbox_max_retries,outbox_batch_size). They have moved to[vef.event.transports.outbox]so the framework-wide outbox transport can serve any module — see Event Bus.
vef.cron, vef.integration, and vef.push
Three optional feature sections, each off by default:
[vef.cron.store]
enabled = true # durable schedule store
auto_migrate = true
[vef.integration]
auto_migrate = true # read by vef.IntegrationModule
secret_key = "base64-key"
[vef.push]
enabled = true # WebSocket push endpoint at /ws
Full key lists live in the Configuration Reference and the module guides: Durable Schedules, Integration Engine, and Server Push.
Environment variables
Configuration keys are not overridable through environment variables — the generic prefix + dot-to-underscore override mechanism was removed. The framework reads exactly three standalone environment variables, none of which maps to a config key:
VEF_CONFIG_PATH— extra config search directoryVEF_LOG_LEVEL— log verbosityVEF_I18N_LANGUAGE— framework language
What configuration does not do
Configuration does not replace application composition. You still use code to:
- register resources
- provide services and modules
- register auth loaders and permission resolvers
- register CQRS behaviors
- register MCP providers
Think of configuration as runtime input, not application structure.
Next step
Once the config file is clear, move to Project Structure to organize a real project around modules.