Umbral
Features

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.

The big idea

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.

ORM model Form & validation REST serializer
// 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.
One struct, three roles shipped

The same plain struct is your model, your form, and your serializer — declare data once, reuse it everywhere, no DTOs to keep in sync.

Model derive shipped

#[derive(Model)] turns a struct into a table, manager, and column set.

QuerySet builder shipped

filter/exclude/order_by/annotate/aggregate, Q objects, subqueries.

Relations shipped

ForeignKey, OneToOne, M2M with select_related / prefetch_related.

Managed migrations shipped

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.

Concurrent-safe migrations shipped

A Postgres advisory lock serializes migrators, so several replicas deploying at once never race the same DDL — one applies, the rest wait then skip.

Multi-database transactions shipped

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.

Soft deletes & signals shipped

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.
Routing & extractors shipped

axum-based routes, typed extractors, layered middleware.

minijinja templates shipped

Auto-escaped templates with a markdown filter and plugin dirs.

Forms & validation usable

Form derive, field validation, friendly per-field errors.

File & image fields shipped

Multipart upload to a pluggable Storage backend, with a bounded concurrency gate on background processing.

Protected file serving shipped

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.

Live reload (dev) shipped

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.
Auto CRUD shipped

List, create, edit, delete for every registered model.

Search, filters & pickers shipped

Multi-filter dialog, search, FK/M2M/O2O relation pickers.

Dashboard widgets in progress

KPI cards, charts, donuts, gauges, and tables on the index.

Bulk actions & inlines planned

Act on selected rows; edit related rows on the parent form.

Auth & Security

Users, permissions, sessions, and secure-by-default middleware.
Users & permissions shipped

Argon2 hashing (behind a concurrency gate that sheds load under a login flood), groups, RBAC, per-object checks.

Sessions shipped

Server-side session store + middleware, with a configurable SameSite policy and an absolute max-age cap alongside sliding expiry.

Trusted-proxy client IP shipped

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.

OAuth / social login shipped

Google/GitHub login + account connection (umbral-oauth).

CSRF, HSTS, headers shipped

Secure-by-default protections via umbral-security.

REST & API

Expose models as JSON, document them, and try them in a playground.
Auto REST shipped

Serializers, viewsets, pagination, filtering, auth gates — safe-by-default (writes 403 without an explicit permission).

Object-level scoping shipped

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.

OpenAPI + playground shipped

Generated spec and a mini-Postman request surface.

Nested writable serializers planned

Create a parent and its children in one request.

Background & Realtime

Move work off the request path and push updates to clients.
Task queue experimental

#[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.

Health checks shipped

/healthz and /ready probes for load balancers.

WebSockets / SSE planned

User- and room-targeted realtime push.

Email sending planned

Transactional email via SMTP / API backends.

See the batteries in action

Each capability ships as an official plugin you can enable, replace, or extend.

Browse prebuilt plugins