What is Umbral?
A batteries-included web framework for Rust.
Umbral is a batteries-included web framework for Rust. You declare your data and you get migrations, CRUD, an admin, and an optional REST API almost for free, with Rust's compile-time guarantees instead of runtime hope.
The name is the adjective 'of the shadow' (from Latin umbra, shadow).
Why Umbral exists
Web development in Rust is unassembled. Unlike Rails, there's no mainstream "just works" path. Every team picks and wires a web framework, a database layer, a migration tool, serialization, auth, and background jobs.
Two things suffer:
- Greenfield productivity. A basic CRUD/REST API takes days of plumbing before you write any real code.
- Porting. Moving an existing API to Rust means re-deriving the schema, the models, and the conventions by hand. There's no
inspectdb, no managed migrations, no obvious on-ramp.
Umbral targets that on-ramp.
The shape
Thin core, plugin-heavy
Auth, sessions, admin, tasks, and REST are plugins. Structurally they're identical to third-party ones. A REST-free app compiles with zero serializer code.
Managed migrations from day one
Declare or change a model; an autodetected migration is generated; migrate applies it. The declare → migrate → change → migrate cycle is the product, not a later feature.
The easy path is the safe path
Nullable columns are Option<T>. Errors are Result. Backend mismatches fail at boot. SQL is always parameterized.
Stand on shoulders
axum, sqlx, sea-query, and tower do the heavy lifting. Umbral reimplements conventions and integration, not HTTP, async, SQL, or JSON.
What you write
A model is a struct. A migration is generated. A handler reads ambient data. This is the target shape:
use umbral::prelude::*; #[derive(Model)]pub struct Post { pub id: i64, pub title: String, pub body: String, pub published: Option<DateTime<Utc>>,} async fn list_published() -> Result<Json<Vec<Post>>> { let posts = Post::objects() .filter(post::PUBLISHED.is_not_null()) .order_by(post::PUBLISHED.desc()) .limit(20) .fetch() .await?; Ok(Json(posts))}No State<DbPool> in the handler signature. No axum:: types. The ORM is ambient; the request body, params, and session are explicit arguments.
Status
Umbral is pre-alpha. There's no published crate yet. The architecture, the PRD, and the per-subsystem specs come first, and implementation follows milestone by milestone. As features land, each gets a doc page here.
For the long version, read the design specs in the repository root: arch.md (architecture), umbral-PRD.md (product requirements), and docs/specs/ (per-subsystem deep specs).
Roadmap
Notable items on the backlog. Pages land here as each one ships. See planning/features.md in the repository for the full punch-list.
Logs plugin
Auto-logging of every DB action. Registers a logs table, surfaces 'Recent activity' in the admin.
Notifications plugin (SSE)
Server-sent events the user can subscribe to per model / per action. Composes with TasksPlugin so a logged error type can fan out to email.
Rich admin field widgets
RTE and Markdown editors (with preview) declared per-column: AdminModel::field('body', Markdown::new()).
Admin widget tooling
Helper for building dashboard widgets with built-in filters (date, search, range, choice) - plus per-user widget reordering.
GraphQL plugin
Schema + query/mutation endpoints auto-generated from the OpenAPI spec. The playground stays the same shape as the REST playground, opt-in per resource.
WebSocket playground
Opt-in playground tab that connects to declared WS endpoints with the same UI shape as the REST playground.