autumn generate collapses the five-file dance of "add a resource" into a
single command. Four subcommands cover the cases you actually hit:
| Command | What it produces |
|---|---|
autumn generate model | A #[model] struct, a Diesel up.sql/down.sql pair, a schema.rs entry |
autumn generate migration | A Diesel migration directory; columns are inferred when the name matches a verb |
autumn generate task | A one-off operational #[task] skeleton under tasks/ |
autumn generate scaffold | Everything model does plus #[repository], HTML routes, smoke test, routes![] registration |
autumn generate admin | An AdminModel adapter for an existing model, wired to autumn-admin-plugin |
The generators only emit code that uses macros and conventions Autumn already
ships (#[model], #[repository], #[get]/#[post], the i64-PK convention,
Diesel migrations, Maud templates). They never introduce new traits or
runtime concepts — once a generator has run, the generated files are
ordinary user code that you should edit freely.
Five commands to a working CRUD app
This is the path that every other batteries-included framework boasts about. On a fresh machine with Rust and Postgres installed:
autumn new my-app
cd my-app
autumn generate scaffold Post title:String body:Text published:bool
autumn migrate
autumn dev
Visit http://localhost:3000/posts to see the generated index page.
The JSON endpoint at http://localhost:3000/api/posts returns [] until
rows exist; mount mutating API handlers only after adding a repository policy.
The field-type DSL
Fields are passed as name:Type tokens. Only the documented public surface
is supported — anything else fails with an error that lists the supported
set.
| DSL token | Rust type | Schema type | SQL type |
|---|---|---|---|
title:String | String | Text | TEXT |
body:Text | String (alias for String) | Text | TEXT |
count:i32 | i32 | Int4 | INTEGER |
count:i64 | i64 | Int8 | BIGINT |
score:f32 | f32 | Float4 | REAL |
score:f64 | f64 | Float8 | DOUBLE PRECISION |
published:bool | bool | Bool | BOOLEAN |
token:Uuid | uuid::Uuid | Uuid | UUID |
at:NaiveDateTime | chrono::NaiveDateTime | Timestamp | TIMESTAMP |
at:DateTime | chrono::DateTime<chrono::Utc> | Timestamptz | TIMESTAMPTZ |
data:Bytea (or Vec<u8>) | Vec<u8> | Bytea | BYTEA |
Wrap any of the above in Option<…> to make the column nullable
(Option<String>, Option<i64>, Option<NaiveDateTime>, …). The generator
emits both NULL in the migration SQL and Nullable<T> in schema.rs.
Every generated table also includes:
id BIGSERIAL PRIMARY KEY(thei64-PK convention used everywhere else in Autumn).created_at TIMESTAMP NOT NULL DEFAULT NOW()annotated#[default]on the model so it stays out ofNewX.
autumn generate model
autumn generate model Post title:String body:Text published:bool
Produces:
src/models/post.rs # #[model] struct
src/models/mod.rs # `pub mod post;` (created or appended)
src/schema.rs # diesel::table! { posts (id) { ... } }
migrations/<timestamp>_create_posts/up.sql # CREATE TABLE posts (...)
migrations/<timestamp>_create_posts/down.sql # DROP TABLE posts;
The generated src/models/post.rs:
//! Generated by `autumn generate`.
use crate::schema::posts;
#[autumn_web::model]
pub struct Post {
#[id]
pub id: i64,
pub title: String,
pub body: String,
pub published: bool,
#[default]
pub created_at: chrono::NaiveDateTime,
}
| Generated file | Existing concept it maps to |
|---|---|
src/models/post.rs | The #[autumn_web::model] macro |
migrations/.../up.sql | Diesel migrations consumed by autumn migrate |
src/schema.rs | The Diesel table! block referenced by #[model] |
src/models/mod.rs | Standard Rust module aggregator |
autumn generate migration
For schema changes that aren't a brand-new table.
# Empty migration — you fill in the SQL.
autumn generate migration BackfillSomething
# AddXxxToYyy — emits ALTER TABLE yyys ADD COLUMN per field
autumn generate migration AddPublishedToPosts published:bool
# RemoveXxxFromYyy — emits ALTER TABLE yyys DROP COLUMN per field
autumn generate migration RemoveBodyFromPosts body:String
The name detection is purely cosmetic — Autumn treats both Post and
Posts as the table posts. If your name doesn't match Add…To… or
Remove…From…, the generator just emits empty up.sql and down.sql
files for you to fill in.
autumn generate task
For operational scripts that should run through the full Autumn app context.
autumn generate task cleanup_users
Produces:
tasks/cleanup_users.rs # #[task] async function skeleton
The generated task uses TaskArgs<T> for CLI flags:
#[derive(Debug, Deserialize)]
struct CleanupUsersArgs {
#[serde(default)]
pub dry_run: bool,
}
#[autumn_web::task]
pub async fn cleanup_users(TaskArgs(args): TaskArgs<CleanupUsersArgs>) -> AutumnResult<()> {
// ...
Ok(())
}
Register the function with .one_off_tasks(one_off_tasks![...]) before running
it with autumn task cleanup_users --dry-run.
autumn generate scaffold
Everything model produces, plus:
src/repositories/<snake>.rs— a#[repository(Model, api = "/api/<plural>")]block that auto-generates CRUD methods plus JSON REST handlers.src/repositories/mod.rs— module aggregator.src/routes/<plural>.rs— Maud HTML handlers forindex,show,new_form,create,edit_form, andupdate.src/routes/mod.rs— module aggregator.tests/<snake>.rs— a smoke test that hitsGET /<plural>against a running server and asserts a 2xx response (skipped unlessAUTUMN_TEST_BASE_URLis set).src/main.rs— themoddeclarations plusroutes![…]entries get added in place. Existing entries are preserved; rerunning the generator with the same arguments is a no-op. The scaffold registers only read-only API routes (GET /api/<plural>andGET /api/<plural>/{id}) by default; mountPOST/PUT/DELETEhandlers only after adding a repository policy.
Metadata flags let you keep common model and repository polish in the generation step:
autumn generate scaffold Bookmark url:String title:String tag:String alive:bool \
--index url \
--index tag \
--validate url=url \
--validate title=length:min=1,max=200 \
--default alive=true \
--query find_by_tag:tag \
--query find_by_alive:alive
| Flag | Effect |
|---|---|
--index FIELD | Adds #[indexed] and CREATE INDEX idx_<table>_<field> .... Repeatable. |
--validate FIELD=RULE | Adds #[validate(...)] and the validator dependency. Supported rules: url, email, and length:min=N,max=N on String / Text fields. |
--default FIELD=VALUE | Adds #[default] and a SQL DEFAULT for bool, string/text, integer, and float fields. i32 defaults must fit PostgreSQL's INTEGER range. Defaulted fields are omitted from generated HTML forms and update columns because the model macro keeps them out of NewX. |
--query METHOD:FIELD | Adds a derived repository method such as find_by_tag(tag: String) -> Vec<Model>. The find_by_ suffix must match FIELD. |
| Generated file | Existing concept it maps to |
|---|---|
src/models/<name>.rs | #[autumn_web::model] |
src/repositories/<name>.rs | #[autumn_web::repository] |
src/routes/<plural>.rs | #[get]/#[post] route macros returning Maud Markup |
src/main.rs routes![…] | The routes! collection macro |
migrations/<ts>_create_<plural>/ | Diesel migrations |
src/schema.rs | Diesel table! blocks |
tests/<name>.rs | Standard cargo test integration test |
Shipped example
The examples/bookmarks app is regenerated from
the current scaffold shape:
autumn new bookmarks
cd bookmarks
autumn generate scaffold Bookmark url:String title:String tag:String alive:bool \
--index url \
--index tag \
--validate url=url \
--validate title=length:min=1,max=200 \
--default alive=true \
--query find_by_tag:tag \
--query find_by_alive:alive
It is the reference for what autumn generate scaffold produces in practice
after a user makes ordinary app-specific edits. The committed follow-up diff is
intentionally small and documents which gaps are outside the generic generator:
| Bookmarks addition | Disposition |
|---|---|
| Tailwind layout, htmx delete buttons, and public local-demo write forms | UI and access-policy choices; replace the generated route templates. |
Hourly #[scheduled] link checker | Operational workflow; generate or write a task separately. |
Mounting POST/PUT/DELETE JSON API routes | Application policy; scaffold keeps only read APIs registered by default. |
Slow live scaffold verification
The CLI test suite includes two ignored scaffold checks:
# Compile-check the generated app and its generated smoke test.
cargo test -p autumn-cli --test generate generated_scaffold_cargo_checks -- --ignored --exact
# Boot Postgres, run `autumn migrate`, start the generated server, and
# verify GET /posts and GET /api/posts over real HTTP.
cargo test -p autumn-cli --test generate generated_scaffold_serves_posts_index_and_json_api -- --ignored --exact
The live HTTP test requires Docker access for the Postgres testcontainer and
the diesel CLI on PATH, because autumn migrate delegates to
diesel migration run.
Common flags
Every generator accepts:
--dry-run— print the file plan and exit. Nothing is written; existing files are not touched. Useful for previewing what the generator will do.--force— overwrite existing files. By default, the generator refuses to clobber and surfaces awould overwrite <path>error listing every collision.mod.rsandschema.rsare always treated as modify-in-place edits and don't trigger collisions.
What's intentionally not here
The generators are deliberately scoped to one resource per invocation and to the existing public macro surface. Out of scope (track separately if you need them):
- Authentication scaffolding. Auth has its own session, CSRF, and
#[secured]story; bundling it here would balloon scope. - Generators for optional plugin crates. Those plugins ship their own generators on their own timeline.
- Harvest workflow scaffolding.
autumn-harvestis a companion workflow project with its own release train, so core web generators do not depend on it. - Custom user-provided templates / template overrides.
- Reverse generation (database → models).
- Test scaffolding beyond the single smoke test.
- Multi-resource scaffolds (
autumn generate scaffold Blog Post Comment). One resource per invocation; chaining is the user's job.