autumn generate collapses the five-file dance of "add a resource" into a single command. Four subcommands cover the cases you actually hit:

CommandWhat it produces
autumn generate modelA #[model] struct, a Diesel up.sql/down.sql pair, a schema.rs entry
autumn generate migrationA Diesel migration directory; columns are inferred when the name matches a verb
autumn generate taskA one-off operational #[task] skeleton under tasks/
autumn generate scaffoldEverything model does plus #[repository], HTML routes, smoke test, routes![] registration
autumn generate adminAn 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:

Shell
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 tokenRust typeSchema typeSQL type
title:StringStringTextTEXT
body:TextString (alias for String)TextTEXT
count:i32i32Int4INTEGER
count:i64i64Int8BIGINT
score:f32f32Float4REAL
score:f64f64Float8DOUBLE PRECISION
published:boolboolBoolBOOLEAN
token:Uuiduuid::UuidUuidUUID
at:NaiveDateTimechrono::NaiveDateTimeTimestampTIMESTAMP
at:DateTimechrono::DateTime<chrono::Utc>TimestamptzTIMESTAMPTZ
data:Bytea (or Vec<u8>)Vec<u8>ByteaBYTEA

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 (the i64-PK convention used everywhere else in Autumn).
  • created_at TIMESTAMP NOT NULL DEFAULT NOW() annotated #[default] on the model so it stays out of NewX.

autumn generate model

Shell
autumn generate model Post title:String body:Text published:bool

Produces:

Code
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:

Rust
//! 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 fileExisting concept it maps to
src/models/post.rsThe #[autumn_web::model] macro
migrations/.../up.sqlDiesel migrations consumed by autumn migrate
src/schema.rsThe Diesel table! block referenced by #[model]
src/models/mod.rsStandard Rust module aggregator

autumn generate migration

For schema changes that aren't a brand-new table.

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

Shell
autumn generate task cleanup_users

Produces:

Code
tasks/cleanup_users.rs                         # #[task] async function skeleton

The generated task uses TaskArgs<T> for CLI flags:

Rust
#[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 for index, show, new_form, create, edit_form, and update.
  • src/routes/mod.rs — module aggregator.
  • tests/<snake>.rs — a smoke test that hits GET /<plural> against a running server and asserts a 2xx response (skipped unless AUTUMN_TEST_BASE_URL is set).
  • src/main.rs — the mod declarations plus routes![…] 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> and GET /api/<plural>/{id}) by default; mount POST/PUT/DELETE handlers only after adding a repository policy.

Metadata flags let you keep common model and repository polish in the generation step:

Shell
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
FlagEffect
--index FIELDAdds #[indexed] and CREATE INDEX idx_<table>_<field> .... Repeatable.
--validate FIELD=RULEAdds #[validate(...)] and the validator dependency. Supported rules: url, email, and length:min=N,max=N on String / Text fields.
--default FIELD=VALUEAdds #[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:FIELDAdds a derived repository method such as find_by_tag(tag: String) -> Vec<Model>. The find_by_ suffix must match FIELD.
Generated fileExisting 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.rsDiesel table! blocks
tests/<name>.rsStandard cargo test integration test

Shipped example

The examples/bookmarks app is regenerated from the current scaffold shape:

Shell
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 additionDisposition
Tailwind layout, htmx delete buttons, and public local-demo write formsUI and access-policy choices; replace the generated route templates.
Hourly #[scheduled] link checkerOperational workflow; generate or write a task separately.
Mounting POST/PUT/DELETE JSON API routesApplication policy; scaffold keeps only read APIs registered by default.

Slow live scaffold verification

The CLI test suite includes two ignored scaffold checks:

Shell
# 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 a would overwrite <path> error listing every collision. mod.rs and schema.rs are 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-harvest is 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.