Configuration#
Bialet stores configuration in the SQLite database — every setting is a row
in a simple key/value table. There are no .env files, no YAML, no
environment variables to set.
Reading and Writing#
Config.set("app_name", "My App")
var name = Config.get("app_name") // "My App"
Typed Access#
All values are stored as strings. Use typed getters for numbers, booleans, and JSON:
Config.set("items_per_page", "20")
var limit = Config.num("items_per_page") // 20 as a number
Config.set("maintenance_mode", "1")
var on = Config.bool("maintenance_mode") // true
Config.json("features", {"darkMode": true, "notifications": false})
var features = Config.json("features") // { "darkMode": true, ... }
Config.bool returns true for "1" and "true" (case-insensitive),
false for everything else including missing keys.
Enable / Disable#
For on/off flags, use the enable and disable convenience methods
instead of hand-writing the "1"/"0" strings:
Config.enable("beta_features") // stores "1"
Config.disable("beta_features") // stores "0"
Config.bool("beta_features") // false
if (Config.bool("live_reload")) {
// feature is on
}
enable sets the value to "1", disable sets it to "0". Both are
equivalent to Config.set("key", "1") and Config.set("key", "0").
Delete#
Config.delete("old_setting")
⚠️ Pitfall:
Config.get()returnsnullfor missing keys. Always provide a fallback:Config.get("title") || "Default Title".
How It Works#
Under the hood, Config reads and writes to the BIALET_CONFIG table, a
two-column table created automatically when the database initializes:
CREATE TABLE IF NOT EXISTS BIALET_CONFIG (
key TEXT PRIMARY KEY,
value TEXT
)
Config.set("key", "value") runs an INSERT OR REPLACE. Config.get("key")
does a SELECT value FROM BIALET_CONFIG WHERE key = ?. .num, .bool, and
.json convert the string result into the right type in Wren — the database
still stores everything as TEXT.
Because configuration is just rows in SQLite, changes persist across server restarts with no extra work. There’s no file to parse, no cache to invalidate, and no separate config server to run.
Seeding Configuration#
With -r at startup#
The -r flag runs Wren code before the server starts and then exits. Use
it in deployment scripts to seed secrets and settings:
bialet -r 'Config.set("stripe_key", "sk_live_...")' /www/myapp
bialet -r 'Config.set("mail_host", "smtp.example.com")' /www/myapp
-r can run any Wren code, so you can set multiple values in one call or
even build a setup script:
// setup.wren — run with: bialet -r "$(cat setup.wren)" /www/myapp
Config.set("title", "My App")
Config.set("items_per_page", "20")
Config.json("features", {"darkMode": true, "notifications": false})
Use
-rfor one-off provisioning — secrets, API keys, and environment-specific overrides that shouldn’t live in migrations.
With migrations for defaults#
For defaults that ship with the application and should exist in every environment, write them in a migration:
// _migration.wren
Db.migrate("Add default config", [
`INSERT OR IGNORE INTO BIALET_CONFIG VALUES ('title', 'My App')`,
`INSERT OR IGNORE INTO BIALET_CONFIG VALUES ('items_per_page', '20')`,
`INSERT OR IGNORE INTO BIALET_CONFIG VALUES ('admin_email', 'admin@example.com')`
])
INSERT OR IGNORE sets a fallback value — it only inserts if the key
doesn’t already exist. This lets -r overrides from deployment scripts
take precedence over migration defaults.
Working with Environments#
Bialet has no development, staging, or production mode. Each
environment is a different database file with its own BIALET_CONFIG
table. The database is the environment.
dev/
├── _db.sqlite3 # Dev config
├── _migration.wren
└── index.wren
prod/
├── _db.sqlite3 # Prod config
├── _migration.wren
└── index.wren
Migrations and application code are identical across environments — only
the database file differs, and therefore only the configuration and data
differ. Pointing Bialet at a different _db.sqlite3 switches environments.
Comparison with Other Frameworks#
Most frameworks layer configuration from multiple sources: .env files,
environment variables, YAML configs, command-line flags, and sometimes a
configuration database. They merge them with precedence rules (env vars
beat .env, flags beat env vars, etc.), and you need to know which source
wins in which context.
Bialet removes that entire stack. There is one source: the database.
Bialet |
Rails / Django / Laravel |
Express / Next.js |
|
|---|---|---|---|
Config storage |
SQLite rows |
|
|
Precedence rules |
None |
Multi-layer (env beats file, etc.) |
Multi-layer |
Persists across restarts |
Yes (it’s a DB row) |
No (env vars are per-process) |
No |
Programmatic write |
|
Usually read-only from code |
Usually read-only |
Environment switching |
Point to a different |
|
|
Configuration UI |
Possible — just writes to SQLite |
Needs a backing store |
Needs a backing store |
Secrets at deploy time |
|
|
|
The trade-off: you can’t configure Bialet through environment variables or
.env files. If your deployment pipeline relies on those, you’ll need to
translate them into -r calls or migration defaults. The benefit: no
precedence bugs, no runtime parsing overhead, and a single table to query
for every setting in the system.
Common Patterns#
Feature Flags#
if (Config.bool("beta_features")) {
// Show beta UI
}
Toggle flags with Config.enable() / Config.disable():
Config.enable("beta_features") // turn on
Config.disable("beta_features") // turn off
Maintenance Mode#
if (Config.bool("maintenance_mode")) {
Response.status(503)
return <main><h1>We'll be right back</h1></main>
}
Pagination Settings#
var limit = Config.num("items_per_page") || 20
var page = Num.fromString(Request.get("page") || "1")
var offset = (page - 1) * limit
Site-Wide Title#
var title = Config.get("site_title") || "My Site"
Pitfalls#
Config.get()returnsnullfor missing keys — always provide a fallback:Config.get("key") || "default".Config values are strings — use
Config.num()andConfig.bool()for typed access.Config.get("port")returns"7001", not7001.No
.envfiles or environment variables — everything lives inBIALET_CONFIG. If you need inject values from outside, use-r.-rruns once and exits — it’s for provisioning, not for setting values that the server reads on every request. Those belong in migrations or are set once via-rand then read viaConfig.get()at runtime.Keep your production database out of version control — it contains your config, user data, and sessions.
Next Steps#
Learn about Database — the Query object, migrations, and how
BIALET_CONFIGsits alongside your application tablesSet up a production environment with Deployment
See the Security guide for CSRF, headers, and secure configuration
Check the Reference for the full
ConfigAPI