Everything the framework gives you
Declare your data and get migrations, an admin, a REST API, and auth almost for free — with Rust's compile-time guarantees. Here's the map, grouped by area, with honest status.
One struct. Model, form, and serializer.
Umbral has no special data type. A plain Rust struct is the database model, the validated form, and the JSON serializer — the same type, reused everywhere. Declare your data once and every layer reads from it: no DTOs to keep in sync, no schema duplication, and the compiler checks all of it.
// one declaration… #[derive(Model, Form, Serialize, Deserialize)] struct Post { title: String, body: String } // …the same type, three roles: Post::objects().all() // ORM model Post::validate(&data) // form Json(post) // serializer
ORM & Migrations
Declare models, query them ergonomically, and evolve the schema safely.The same plain struct is your model, your form, and your serializer — declare data once, reuse it everywhere, no DTOs to keep in sync.
#[derive(Model)] turns a struct into a table, manager, and column set.
filter/exclude/order_by/annotate/aggregate, Q objects, subqueries.
ForeignKey, OneToOne, M2M with select_related / prefetch_related.
makemigrations diffs models — including unique_together and composite indexes — and migrate applies reversible operations, backfilling NOT NULL tightenings and refusing destructive drops unless you pass --allow-destructive.
A Postgres advisory lock serializes migrators, so several replicas deploying at once never race the same DDL — one applies, the rest wait then skip.
begin_for(alias) / transaction_on(alias) run a transaction against a replica or tenant pool, so on_tx writes for a routed model land in the right database — not silently in the default one.
deleted_at auto-filtering and pre/post save/delete hooks; mark a field #[umbral(signal_skip)] to keep secrets and PII out of signal payloads.
Web & Templates
Routing, handlers, and server-rendered templates with secure defaults.axum-based routes, typed extractors, layered middleware.
Auto-escaped templates with a markdown filter and plugin dirs.
Form derive, field validation, friendly per-field errors.
Multipart upload to a pluggable Storage backend, with a bounded concurrency gate on background processing.
Gate media downloads behind an async access-control callback — check a session or ownership before a byte is served, so private uploads aren't world-readable by URL.
Save a template, CSS, or asset and the browser refreshes itself over SSE — CSS hot-swaps in place, no manual refresh. Opt-in umbral-livereload plugin, inert in production.
Admin
An auto-generated control panel for every model.List, create, edit, delete for every registered model.
Multi-filter dialog, search, FK/M2M/O2O relation pickers.
KPI cards, charts, donuts, gauges, and tables on the index.
Act on selected rows; edit related rows on the parent form.
Auth & Security
Users, permissions, sessions, and secure-by-default middleware.Argon2 hashing (behind a concurrency gate that sheds load under a login flood), groups, RBAC, per-object checks.
Server-side session store + middleware, with a configurable SameSite policy and an absolute max-age cap alongside sliding expiry.
Declare how many reverse proxies sit in front and umbral resolves the real client IP from X-Forwarded-For, so throttles and logs key on the caller — not a forgeable header.
Google/GitHub login + account connection (umbral-oauth).
Secure-by-default protections via umbral-security.
REST & API
Expose models as JSON, document them, and try them in a playground.Serializers, viewsets, pagination, filtering, auth gates — safe-by-default (writes 403 without an explicit permission).
Scope every list and detail endpoint to the rows a caller may see with .scope() / .owned_by(), so a REST API can't leak another user's records.
Generated spec and a mini-Postman request surface.
Create a parent and its children in one request.
Background & Realtime
Move work off the request path and push updates to clients.#[task] jobs drained by a worker, with retries; the claim query uses FOR UPDATE SKIP LOCKED on Postgres so many workers each grab a different job.
/healthz and /ready probes for load balancers.
User- and room-targeted realtime push.
Transactional email via SMTP / API backends.
See the batteries in action
Each capability ships as an official plugin you can enable, replace, or extend.