The full-stack Python framework designed for humans and agents. Follow for development updates. Follow @davegaeddert for more background.

Kansas
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
18
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.
26
Starting to rework some of the postgres APIs
1
22
plain docs cache --api prints the real signatures for the version you have installed. Docs ship inside each Plain package — your agent reads them instead of guessing from training data.
1
18
plain.loginlink: link expiration is now a class attribute — link_expires_in = 60 * 15 on your form, no method override needed. Docs now spell out the real semantics: links stay valid until expiry, deliberately, so mail scanners can't burn them before your user clicks.
16
Dev secrets can now live in git. `plain env set` encrypts a value into your committed .env.dev, one key per project. A fresh clone — or a hosted agent sandbox — just needs DEV_ENV_KEY and every dev credential comes with the repo.
1
28
Response.status_code is read-only in Plain now. The status decides things everything downstream builds on — whether a body is allowed, how the response is framed on the wire — so it's fixed at construction. Renderers take it directly: self.render(form=form, status_code=422)
1
26
plain 0.160.0 makes that bug unrepresentable instead of just catching it: bodiless responses (204, 304, HEAD) refuse content at construction, and Response.status_code is read-only — so you can't build a valid response and mutate it into an invalid one.
31
We were seeing intermittent 503s behind Heroku's router that never showed up in our app logs. Root cause: a 204 response with a body. The body bytes go onto the keep-alive connection unframed, and the router reads them as the start of the next response.
1
1
328
Replaced Model.save() with create() and update() in Plain. The old version branched on whether a primary key was set, which meant reading a save() call told you nothing about whether it wrote a new row.
1
20
Two new packages in Plain: plain.mcp and plain.oauthserver. Together they mean an AI client can connect to your app as a real, authenticated user — PKCE, dynamic client registration, token rotation.
1
38
Plain doesn't auto-index foreign keys — you declare the indexes you want. Add a composite index starting with that FK and the single-column one is redundant anyway: Postgres uses the composite's leftmost prefix. Explicit indexes, no overlap by default.
1
1,270
Making a little progress on the admin UI... - pyproject.toml project name for branding - icons for sidebar nav - gravatar for logged in user
1
5
157
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
1
4
172
Plain now ships with `plain install` and `plain upgrade` commands. In some ways these are glorified AI prompt generators! But integrating with coding agents in this way leaves API keys and control completely in your hands. Use any provider or tool you want.
1
221
From seeing a problem to fixing it, with the new opentelemetry instrumentation, plain-observer, and `plain observer diagnose`. What else can we do if we embrace coding agents?!
582
Models in plain no longer support custom primary keys. Everything gets a bigint auto id field! Time will tell whether more is really needed here, but in most cases you shouldn't need to customize primary keys in new apps and this sets a clear expectation across all of Plain.
41
The admin toolbar shows exception tracebacks automatically when you break something. This appears on top of your regular error view, so you never forget what your users are seeing in these situations!
35
plain-code now uses @biomejs v2 for a no-config formatting and linting of JS files. Just run `plain fix`
3
142
Plain now supports a single DATABASE instead of multiple DATABASES. This drastically simplifies a few areas of the framework. Multiple db support can always come back if it proves to be a need, and by then it may take a different form then it has today!
1
68
Experimenting with a `plain help` command that lists out all available commands and options... one more step towards figuring out what makes a framework "AI-friendly"
55
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
1
1
3
589
The plain-importmap package is being removed for now. The implementation wasn't quite right, and it's not totally clear that it's actually needed. In practice, plain-esbuild (bundling) has been more useful, and plain-vendor can be used when creating importmaps manually.
62
First draft of the Plain principles
1
1
77
Elements are getting a syntax update! Now it looks more like jsx, with single braces to distinguish Python variables.
1
2
195
The `plain fix` command now uses @biomejs to lint and format javascript, json, and css files. Combined with @astral_sh ruff for python, it's an all-in-one preconfigured toolset for code formatting.
1
132
The updated admin toolbar - goes full width - shows request, url, and view info - tracks sql queries across requests - can show custom panels
1
69
More breaking changes! The request GET and POST have been replaced by query_params and data (which can now parse JSON).
1
1
172
The plain.api package is being reworked! Plain itself is even more JSON-friendly than before, and the new plain.api views bring it all together with API keys and (optional) OpenAPI/swagger.json generation
1
1
161
The first plainx community package is plainx-sentry (integration with @getsentry). We aren't planning to maintain official, first-party integrations for other commercial services... but we could provide some kind of vetted directory that includes them!
1
2
201
For anybody interested in helping out
1
1
2
87