{"version":"v1.0","client_state":{"DocsNavigation.default":{"fields":{"query":""},"persist":"memory","send_to_server":true},"ColorScheme.default":{"fields":{"mode":"system"},"persist":"local","send_to_server":true}},"endpoints":{"action":"/_bretzel/action","sse":"/_bretzel/sse","refetch":"/_bretzel/refetch"},"csrf":"7b1b7bf754c2ad3b82d2beba52e755b9","page_id":"9e375a7f027e4eaba95cb8a6fb9e6cfa","address":""}

Config & run

The `Bretzel(...)` instance carries the configuration; `run(...)` starts the dev server. The tables below are read from the code at render time — they follow the API with no editing.

The instance

from bretzel import Bretzel

app = Bretzel(
    title="My App",
    secret_key="…",   # required, ≥ 16 chars
    mode="dev",       # "dev" | "prod" (prod by default)
)
Bretzel(...)
ParameterTypeDefaultPassed as
titlestr'Bretzel App'keyword-only
descriptionstr | NoneNonekeyword-only
themeTheme | NoneNonekeyword-only
secret_keystr | NoneNonekeyword-only
redis_urlstr | NoneNonekeyword-only
workersint1keyword-only
session_max_age_daysint30keyword-only
action_max_ageint | NoneNonekeyword-only
mobile_breakpointint768keyword-only
nav_progressboolTruekeyword-only
pwaAnyNonekeyword-only
langstr'en'keyword-only
languagesSequence[str]()keyword-only
textsMapping[str, str] | NoneNonekeyword-only
static_dirstr | NoneNonekeyword-only
faviconstr | bool | NoneNonekeyword-only
cors_originslist[str] | NoneNonekeyword-only
trusted_hostslist[str] | NoneNonekeyword-only
security_headersboolTruekeyword-only
cspbool | Literal['report-only']Falsekeyword-only
csp_sourcesMapping[str, Sequence[str]] | NoneNonekeyword-only
api_docsboolFalsekeyword-only
modeLiteral['dev', 'prod'] | NoneNonekeyword-only
secure_cookiesbool | NoneNonekeyword-only
expose_errorsbool | NoneNonekeyword-only
debugbool | NoneNonekeyword-only
cssLiteral['auto', 'build', 'browser'] | NoneNonekeyword-only

Running

`mode` (dev/prod) and `reload` (hot reload) are independent: `mode` picks a profile, `reload` restarts the server when a file changes.app.run(...)
ParameterTypeDefaultPassed as
hoststr'127.0.0.1'positionnel
portint8000positionnel
log_levelstr'info'keyword-only
reloadboolFalsekeyword-only
targetstr | NoneNonekeyword-only
uvicorn_kwargsAny—**kwargs

What `mode` decides — and does not decide

`mode` is a PRESET: it sets the defaults of independent settings, which you can override one by one.
SettingThe question askeddevprod
expose_errors=does the detail of exceptions go into the response?TrueFalse
debug=should the framework be talkative (warnings, readable IDs)?TrueFalse
css=who compiles the CSS?browserbuild
—cache headersno-storeimmutable
The two set themselves independently: `mode="prod", debug=True` gives a talkative production that exposes nothing, and `mode="dev", expose_errors=False` lets you see your real error pages locally.

The CSS pipeline

`css="build"` compiles a sheet at startup and serves it as a `<link>`: the CSS is there at the first paint. `css="browser"` lets the Tailwind compiler work inside the page — no binary to install, but the CSS arrives after the first paint. `"auto"` (the default) picks `build` in production and `browser` in dev.To develop in production's exact conditions, ask for `css="build"` — it needs the compiler (`pip install 'bretzel[css]'`) and it adds a few seconds to every startup. With no binary, the application says so and falls back to the browser compiler.
app = Bretzel(secret_key="…", mode="dev", css="build")

Cookies and HTTPS

The cookies' `Secure` attribute does NOT depend on the mode: it follows the request's protocol. A `Secure` cookie is never sent back over an `http://` origin, so tying it to the mode would cut the session of every application deployed without TLS.
# Behind a proxy that terminates TLS: tell uvicorn,
# and it rewrites the protocol the app sees.
app.run(proxy_headers=True)

# Or force it, if the proxy sends no header.
app = Bretzel(secret_key="…", secure_cookies=True)

The security headers

Three headers are set BY DEFAULT, because they can break no app: `nosniff`, `Referrer-Policy`, `X-Frame-Options`. `security_headers=False` exists for the app that sets them itself upstream, behind its proxy.The CSP, for its part, is OPT-IN — and it is a split, not an oversight: Bretzel cannot guess your app's fonts, CDNs and iframes. It computes what it owes itself (the hashes of its inline scripts, the origins of its assets); you add your own. One widens, one never narrows.
# 1. The first rung: the browser EVALUATES the policy
#    and reports what would have been blocked, without
#    blocking anything.
app = Bretzel(secret_key="…", csp="report-only")

# 2. One looks at what comes back, fills the gaps, then
#    closes it.
app = Bretzel(secret_key="…", csp=True, csp_sources={
    "font-src": ["https://fonts.gstatic.com"],
    "frame-src": ["https://www.youtube.com"],
})
The mode and the sources are two separate axes on purpose: a dict meaning “enable AND extend” would make two ways of writing the same thing. And `report-only` exists precisely so one does not discover in production that an origin was missing.

State persistence

By default, server state lives in memory. For multi-worker or durability, pass `redis_url=` — there is no `state_backend=` param, the backend is wired at startup according to `redis_url`.
ParamEffect
secret_key=the HMAC key (action signatures) — required, ≥ 16 chars; in production through an env var
redis_url=a Redis backend if supplied; memory otherwise
session_max_age_days=session cookie lifetime (default 30)
workers=uvicorn processes (Redis required if > 1)