The payoff of declaring your data once is that several systems can read that declaration. In Umbral, the same model struct can drive a validated form, an admin CRUD screen, and a REST resource — each one an opt-in plugin.
One struct, three surfaces
Forms
The Form derive turns the #[form(...)] attributes into server-side validation with friendly errors. Fields marked #[umbral(noform)] never appear on the public form — a visitor can't set their own moderation status.
let plugin = validate.await?;
Admin
Register the model and the admin gives you a CRUD UI — list, filters, create, edit — with the #[umbral(noform)] fields editable by staff. The dashboard can add widgets that read straight from the model.
REST
Expose the same model as a JSON resource, hiding sensitive columns, in one line:
default
.resource
Pair it with the OpenAPI plugin and you get a schema and a request playground for free.
The ORM is the single interface
Every row-level read or write goes through the ORM — never hand-rolled sqlx::query("...") in plugin code. The ORM knows the backend and emits the right SQL, so one path works on Postgres and SQLite alike:
let pending = objects
.filter
.order_by
.fetch
.await?;
Declare once, and the form, the admin, the API, and your queries all read the same truth. That's the boilerplate you didn't write.