Secrets in Plain are a type
SECRET_KEY: Secret[str]
`plain settings get` now masks them like `settings list` already did. Matters more with agents: everything a command prints lands in the transcript.
`plain settings get --reveal` is the explicit ask
ALT app/settings.py declares STRIPE_SECRET_KEY: Secret[str]. Below, labeled 'what the agent sees', an agent transcript shows Bash(plain settings get STRIPE_SECRET_KEY) returning ******** and a hint that it is a secret, pass --reveal to print its value.
Plain now requires Python 3.14. The first payoff is sql() in plain-postgres: the SQL is the literal parts of a t-string, every interpolated value binds as a parameter, and passing a plain str is a type error.
ALT Python code using Plain's new sql() API: a QueueStats dataclass, then JobRequest.query.sql() called with a t-string SELECT that interpolates {JobRequest.queue}, count(*), min({JobRequest.created_at}), FROM {JobRequest}, WHERE queue = ANY({queues}), GROUP BY 1, with result_type=QueueStats.
plain dev runs your whole local setup in one command: preflight checks, pending migrations, an auto-reloading server, Tailwind watch, and Postgres. It serves HTTPS by default with locally-trusted certs, so dev behaves like production from the first request.
Every constraint in Plain is now checked at the write that violates it — never at COMMIT. FKs are NOT DEFERRABLE (deferral was a 2007 Django fixtures thing; Plain has no fixtures), and a bad FK in create()/update() is a ValidationError on the field, not a commit-time traceback.
plain.dev now manages development databases so they track your worktrees automatically. A new checkout gets its own copy of your main database, data included, the first time you run the dev server there — nothing to configure, nothing to name.
Experimenting with a MARK comment in the Plain source code, which we can render as sections when displaying docs.
Inspired by # pragma mark in Objective-C and MARK in Swift
Plain 0.58.0 includes completely rewritten CSRF protection — no more {{ csrf_input }} needed!
This mostly relies on the modern Sec-Fetch-Site header and some great research by Filippo Valsorda (words.filippo.io/csrf/), and the new csrf middleware in Go 1.25.
plain.pytest 0.8.0 includes a new `testbrowser` fixture — this brings together playwright, pytest-playwright, and gunicorn to start running browser tests with no additional setup