This version is in beta. Some features may change before release.

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).

Info
Umbral is pre-alpha. The architecture and specs are settled and the milestones are landing in code. Pages get added here as features ship. Today there's enough to declare a model, run managed migrations, port an existing database via `inspectdb`, wire the built-in plugins (auth, sessions, admin, REST, OpenAPI, tasks, permissions, security, static), render HTML through the templates substrate, and drive the whole thing through the project's `cargo run -- ` CLI (plus the global `umbral` scaffolding binary from `cargo install umbral-cli`). Start at [Your first app](/docs/v0.0.1/getting-started/your-first-app).

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:

Code
rust
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.