autumn-admin-plugin
autumn-admin-plugin adds a server-rendered admin panel to an autumn-web
application. Register one or more models, mount the plugin, and it serves a
CRUD UI with list/detail/edit screens, search, filtering, bulk actions, CSRF
integration, and HTMX-driven interactions with no frontend build step.
Features
- Mounts an admin UI under
/adminby default - Generates list, create, detail, edit, delete, and bulk-action flows
- Includes a built-in jobs dashboard for queued, running, completed, and failed work
- Uses Maud + HTMX and works under Autumn's default
Content-Security-Policy - Reads and writes model data through a small
AdminModeltrait - Requires an authenticated session with the
"admin"role by default
Installation
One command adds the dependency at a version compatible with your app's
autumn-web, mounts the plugin in your autumn_web::app() builder chain, and
prints any configuration still needed. It is safe to re-run, and it refuses —
before touching any file — to install into an app on an incompatible
autumn-web version. See docs/plugins.md.
Manual install
If you would rather wire it yourself (or autumn plugin add could not find your
builder chain and printed these lines for you):
[]
= { = "0.10", = ["db", "flash", "htmx", "maud"] }
= "0.10"
autumn-admin-plugin expects a configured Autumn database pool: model
operations receive the app's pool. The built-in jobs dashboard can render
without a database pool when no model route is accessed.
Quick Start
use ;
;
// Implement `AdminModel` for your type.
// Supply field metadata plus `list`, `get`, `create`, `update`, and `delete`.
async
What the Plugin Mounts
When mounted at the default /admin prefix, the plugin serves:
GET /admin/— dashboardGET /admin/jobs— background jobs dashboardGET /admin/jobs/counters— HTMX counter fragment, refreshed at most every 2sPOST /admin/jobs/{id}/retry— retry a failed jobPOST /admin/jobs/{id}/discard— discard a failed jobPOST /admin/jobs/{id}/cancel— cancel an enqueued jobGET /admin/{slug}— paginated list viewPOST /admin/{slug}— create recordGET /admin/{slug}/new— new-record formGET /admin/{slug}/{id}— detail viewPOST /admin/{slug}/{id}— update recordDELETE /admin/{slug}/{id}— delete recordGET /admin/{slug}/{id}/edit— edit formPOST /admin/{slug}/actions— bulk actions
The plugin also serves its same-origin JavaScript through
AppBuilder::plugin_assets, at a content-hashed URL under
/static/_plugins/autumn-admin/ (admin.<hash>.js), so long-lived caching
stays safe across deploys.
Database Backends
The plugin core is backend-agnostic. It takes the app's
autumn_web::RuntimeConnection pool, which is Postgres by default and SQLite
under the autumn-web/sqlite feature. An app on either backend can register
its own models.
The three built-in models are Postgres-only:
| Model | Manages | Audit table |
|---|---|---|
tokens::TokenAdminModel |
api_tokens |
— |
experiments::ExperimentAdminModel |
autumn_experiments |
autumn_experiment_changes |
feature_flags::FeatureFlagAdminModel |
autumn_feature_flags |
feature_flag_changes |
Do not register those three on SQLite. Their migrations use Postgres-only DDL, so the tables do not exist on SQLite. Autumn's experiment and feature-flag stores also refuse a SQLite target.
On SQLite each of the three refuses every call with an error that names the model and points here. Before issue #2108 the crate did not compile at all on SQLite, so this was a build error; now it is a clear run-time one.
AdminModel Contract
Each registered model supplies:
- A URL slug and singular/plural display names
- A field schema via
Vec<AdminField> list,get,create,update, anddeleteoperations
AdminField covers the common form/display shapes: Text, TextArea,
Integer, Float, Boolean, Date, DateTime, Select, Hidden,
Password, and Json.
Optional hooks let you customize:
actions()for extra bulk actions beyond the built-in delete actionexecute_action()to implement those custom actionsrecord_display()for breadcrumbs and page titlesper_page()andcount()for pagination behavior
All values flow through serde_json::Value so the plugin stays object-safe and
does not need to know your application's concrete model types.
Writing a model that runs on both backends
Your own models choose their own SQL. Four rules keep it portable.
- Write
$Nplaceholders in ascending order. Use each placeholder one time. Postgres reads the digits. SQLite gives each distinct$namethe next free index, in order of first appearance. If you writeSET name = $2 ... WHERE id = $1, SQLite binds the id intoname. To repeat a value, bind it two times under two placeholders. - Do not use
ILIKE. WriteLOWER(col) LIKE LOWER($1). Both backends then match without case for ASCII text. SQLite'slower()folds ASCII only, so non-ASCII text stays case-sensitive there. - Do not use a
::typecast, anANY($1)array bind, or a writable CTE. SQLite has none of the three. UseCAST(x AS TEXT), anINlist, and separate statements in one transaction. - Use
CURRENT_TIMESTAMP, notNOW(). Read a timestamp column asTimestampinto aNaiveDateTime. TheTimestamptzSQL type is Postgres-only.
tests/custom_admin_model.rs is a worked example. The same test body runs on
both backends.
Configuration
AdminPlugin::new() defaults to:
- Prefix:
/admin - Required role:
"admin" - Session auth key:
"user_id" - Actuator prefix:
/actuator
You can override those defaults with:
prefix(...)require_role(...)auth_session_key(...)actuator_prefix(...)
Example:
let plugin = new
.prefix
.actuator_prefix
.auth_session_key
.require_role;
Security Notes
- The plugin assumes Autumn session/auth middleware is already configured
- Role checks run before any admin handler by default
- CSRF tokens are rendered automatically when Autumn's
CsrfLayeris enabled - No inline JavaScript is used; the UI is compatible with Autumn's default CSP
Passwordfields are treated as write-only and never rendered back to users
Status
This crate is intended as the first-party admin plugin for autumn-web. The
API is pragmatic and functional, but still young enough that you should expect
incremental improvements around model ergonomics, docs, and batteries-included
examples.