Command Line Tools
Code Generation
wheels generate is a dispatcher that routes to a family of generator subcommands. Each subcommand writes one or more files into your project: a model CFC, a controller with views, a migration, a full scaffold, a helper, a test spec, an admin interface, or a reusable code snippet. The dispatcher itself does no generation — it parses the first positional argument as a <type> and forwards the rest to the matching generator.
You’ll use this for:
- Scaffolding a complete resource (model + controller + views + migration + tests + route) in one command.
- Producing individual artifacts — a blank migration, a property-add migration, a single controller — without touching anything else.
- Stamping out common patterns (authentication, soft delete, API controller) as starting-point code you edit in place.
- Turning an existing model into a CRUD admin interface via runtime introspection.
Overview
Section titled “Overview”wheels generate <type> <name> [attributes...] [flags]The g alias is supported for every form (wheels g model User). wheels create <type> exists as a forward-looking dispatcher but currently only accepts type=app, which forwards to wheels new; use wheels generate for everything else.
Running wheels generate with no arguments prints the list of supported types and one-line examples, and exits with a non-zero status. Unknown types print Unknown generator type: <type> and exit with a non-zero status without writing anything.
Every generator accepts --dry-run, which prints the paths of the files it would write or modify but writes nothing:
wheels generate model Post title:string --dry-runPreviewing is the safe way to see which files a generator will create or modify before it touches the project.
Supported types
Section titled “Supported types”| Type | Alias | Writes |
|---|---|---|
app | a | New project directory (delegates to wheels new) |
model | m | Model CFC and, if properties given, a create-table migration |
controller | c | Controller CFC and a view file per non-mutation action |
view | v | Single view template |
migration | migrate | Blank migration CFC |
scaffold | s | Model + migration + controller + views + tests + resource route |
api-resource | api | Model + JSON controller + migration + tests + namespaced route (no views) |
route | r | Adds a .resources() line to config/routes.cfm |
test | — | Model or controller spec file |
property | prop | Add-column migration for an existing model |
helper | h | Helper CFC under app/helpers/ |
policy | — | Authorization policy CFC under app/policies/ (every action denied until you grant it) |
snippets | — | Copies a named code pattern into the project |
admin | — | CRUD admin controller + views for an existing model (requires a running server) |
auth | — | Full authentication scaffold on the wheels.auth primitives (session, token, or JWT strategy) |
Attribute syntax
Section titled “Attribute syntax”Property-aware generators — model, scaffold, api-resource, and property — accept a trailing list of name:type property pairs after the resource name. They also recognise --belongsTo=, --hasMany=, and --hasOne= flags for associations. Type names are normalised to Wheels migration column types. An unknown type is rejected with an error that lists the valid types (string, text, integer, biginteger, float, decimal, boolean, date, datetime, time, binary, uuid, enum, email, url), and nothing is written.
Columns are required by default: an unmarked name:type column is created allowNull=false and added to the model’s validatesPresenceOf, so the generated migration and model agree. Mark a column nullable with name:type:optional or give it a default with name:type=value; both exclude the column from validatesPresenceOf (a defaulted column never needs a presence check because the default fills an absent value). A belongsTo foreign-key column is required by default.
The other generators (controller, view, migration, route, test, helper, snippets, admin) take their own positional arguments — see the per-subcommand Synopsis sections below for what each accepts.
| You write | Column type | Notes |
|---|---|---|
name | string | Type defaults to string when :type is omitted. |
title:string | string | Also: varchar. Default limit=255. |
title:string{50} | string | Rails-style brace sets migration limit=50 and validatesLengthOf(property="title", maximum=50) on the generated model. Same {N} form works on integer, text, binary, and varchar (length validation is string-like types only). |
body:text | text | Also: longtext. |
count:integer | integer | Also: int. Default limit=11. |
views:biginteger | bigInteger | Also: bigint. |
price:decimal | decimal | Also: numeric. Default precision=10, scale=2. |
ratio:float | float | No precision or scale is set. |
price:decimal{10,2} | decimal | Brace sets precision=10, scale=2. |
status:enum:draft,published | string | The column is a string; the values go into enum(property="status", values="draft,published") in the model’s config(). Braces must not be used for the value list. |
active:boolean | boolean | Also: bool. |
publishedAt:datetime | datetime | Also: timestamp. |
publishedOn:date | date | |
startsAt:time | time | |
payload:binary | binary | |
bio:text:optional | text | :optional makes the column nullable (allowNull=true) and omits it from validatesPresenceOf. Use the word optional, not ? — zsh (the macOS default shell) treats a bare ? as a glob. |
status:string=draft | string | =value sets a column DEFAULT and omits the column from validatesPresenceOf (the default fills an absence). Combine the two as status:string=draft:optional. |
--belongsTo=author | — | Adds belongsTo('author') and an author_id or authorId FK column, according to useUnderscoreReferenceColumns. The FK column is required by default. |
--hasMany=comments | — | Adds hasMany('comments'). Pass multiple names comma-separated. |
--hasOne=profile | — | Adds hasOne('profile'). |
wheels generate model
Section titled “wheels generate model”Generate a model CFC and, when properties are supplied, a matching create-table migration.
Synopsis
Section titled “Synopsis”wheels generate model <Name> [property:type ...] [--belongsTo=<list>] [--hasMany=<list>] [--hasOne=<list>]Description
Section titled “Description”Writes app/models/<Name>.cfc with belongsTo/hasMany/hasOne associations inserted into config() based on the flags you passed. If any property:type arguments are present, the generator also stamps out a timestamped migration under app/migrator/migrations/ that creates the table. No migration is written when you generate a model with no properties.
Example
Section titled “Example”wheels generate model User name email:string active:boolean --hasMany=postsWrites app/models/User.cfc with hasMany('posts') in config(), plus a migration that creates the users table with name, email, and active columns (and the standard timestamps() trio).
wheels generate controller
Section titled “wheels generate controller”Generate a controller CFC and view files for each read-only action.
Synopsis
Section titled “Synopsis”wheels generate controller <Name> [action ...]Description
Section titled “Description”Writes app/controllers/<Name>.cfc with a stub method per action name you pass. For each action that isn’t create, update, delete, or destroy, the generator also writes app/views/<name>/<action>.cfm. Passing no actions creates a controller with a stub index() action and no view files. Action names may be space-separated or comma-separated — index,show is equivalent to index show.
Example
Section titled “Example”wheels generate controller Users index show new createWrites app/controllers/Users.cfc with index(), show(), new(), and create() methods, plus app/views/users/index.cfm, app/views/users/show.cfm, and app/views/users/new.cfm. The create action does not get a view.
wheels generate view
Section titled “wheels generate view”Generate a single view template.
Synopsis
Section titled “Synopsis”wheels generate view <controller> <action>Description
Section titled “Description”Writes app/views/<controller>/<action>.cfm. Both arguments are required — unlike generate controller, this subcommand does not fall back to a default action.
Example
Section titled “Example”wheels generate view products editWrites app/views/products/edit.cfm.
wheels generate migration
Section titled “wheels generate migration”Generate a blank migration file.
Synopsis
Section titled “Synopsis”wheels generate migration <Name>Description
Section titled “Description”Writes app/migrator/migrations/<timestamp>_<Name>.cfc with empty up() and down() methods. The timestamp is generated at run time so sequential invocations produce a deterministic order. Use this when you want to hand-write schema changes that don’t map to the property-based model/scaffold/property generators.
Example
Section titled “Example”wheels generate migration AddIndexesToPostsWrites a file such as app/migrator/migrations/20260420120000_AddIndexesToPosts.cfc.
wheels generate scaffold
Section titled “wheels generate scaffold”Generate a complete CRUD resource.
Synopsis
Section titled “Synopsis”wheels generate scaffold <Name> [property:type ...] [--belongsTo=<list>] [--hasMany=<list>]Description
Section titled “Description”The most opinionated generator. It runs, in order: model, create-table migration, controller (pluralised from <Name>), views (index, show, new, edit, _form), a model spec and a full CRUD controller spec under tests/specs/, and a .resources() route in config/routes.cfm. Read the created, modified, skipped, and error messages after generation; use --dry-run first when scaffolding into an existing application.
belongsTo associations automatically add a foreign-key column even if you didn’t list it in the properties. --belongsTo=author uses author_id when useUnderscoreReferenceColumns=true (the wheels new default), or authorId otherwise. The child form gets a parent picker, and its detail page links back to the parent.
When the parent already exists, the scaffold also attempts to wire the inverse side. For example:
wheels generate scaffold Post title:string body:textwheels generate scaffold Comment body:text --belongsTo=postFor conventional scaffolded files, the second command:
- Adds
hasMany(name="comments")insidePost.config(). - Adds
include="comments"to the parent’sshow()finder, preserving an existing static include list. - Adds a related-record section to
posts/show.cfm, with links to each comment and an Add a comment link. It uses the child’s display property, not an assumedbodycolumn.
Existing parent files are reported as modify, not create. --dry-run previews these edits without writing them. A repeat run preserves the existing marked related-record section, including your edits—even with --force. Custom association options, dynamic finders, or ambiguous source are left alone with a message explaining what to wire manually. Cascading deletion is not enabled automatically. API-only scaffolds can add the inverse model association but do not modify the parent’s HTML controller or views.
Start the server with wheels start, then run wheels migrate latest to apply the migration and try the resource.
Example
Section titled “Example”wheels generate scaffold Post title body:text publishedAt:datetime --belongsTo=authorWrites:
app/models/Post.cfcwithbelongsTo('author')app/migrator/migrations/<timestamp>_create_posts_table.cfcwithtitle,body,publishedAt,author_id(authorIdwhenuseUnderscoreReferenceColumnsis false), and thetimestamps()trioapp/controllers/Posts.cfcwith the seven CRUD actionsapp/views/posts/index.cfm,show.cfm,new.cfm,edit.cfm,_form.cfmtests/specs/models/PostSpec.cfc,tests/specs/controllers/PostsControllerSpec.cfc— the controller spec covers all seven CRUD actions (index,new,create,show,edit,update,delete) withprocessRequestassertions for status codes, redirects, and record-count deltas. Test data is created inbeforeEachviamodel().create()(the Wheels-native stand-in for Rails YAML fixtures), so the specs pass on a fresh app afterwheels migrate latest. Existing apps keep their hand-edited specs unless you re-generate with--force..resources("posts")appended toconfig/routes.cfm
See the Database guide for what to do with the generated migration.
wheels generate api-resource
Section titled “wheels generate api-resource”Generate a JSON-only resource — model, controller, migration, tests, and a namespaced route — without any views.
Synopsis
Section titled “Synopsis”wheels generate api-resource <Name> [property:type ...] [--belongsTo=<list>] [--hasMany=<list>]Description
Section titled “Description”Same pipeline as scaffold, but the controller is generated in JSON-only mode (no view rendering), no view files are written, and the route is added under the api namespace using .namespace("api").resources(name="<names>", except="new,edit"). Use this when you’re building a pure API — a separate front end, a mobile client, or a service-to-service endpoint.
Example
Section titled “Example”wheels generate api-resource Product name price:decimal sku:stringWrites a JSON controller at app/controllers/api/Products.cfc, a create-table migration, and tests. After migrating and starting the server, curl http://localhost:8080/api/products.json returns an empty list.
wheels generate route
Section titled “wheels generate route”Add a single resource route to config/routes.cfm.
Synopsis
Section titled “Synopsis”wheels generate route <name>Description
Section titled “Description”Opens config/routes.cfm, checks that .resources("<name>") isn’t already present, and inserts it into the mapper chain. Duplicate routes are detected by literal substring match — if .resources("posts") already appears in the file, generate route posts changes nothing and exits with a non-zero status and Route already exists: .resources("posts"). If the mapper block can’t be found, the command prints the line to add manually and exits.
This is the one-line equivalent of the route step that scaffold and api-resource run for you.
Example
Section titled “Example”wheels generate route commentsAdds .resources("comments") to the mapper() chain in config/routes.cfm.
wheels generate test
Section titled “wheels generate test”Generate a BDD test spec file.
Synopsis
Section titled “Synopsis”wheels generate test <type> <Name><type> must be model or controller. Any other value is rejected.
Description
Section titled “Description”Writes tests/specs/models/<Name>Spec.cfc or tests/specs/controllers/<Name>ControllerSpec.cfc, extending wheels.WheelsTest. A controller spec covers the seven CRUD actions with processRequest (same shape wheels generate scaffold emits; attribute structs stay empty unless you scaffolded with properties). A model spec starts as a thin model().new() smoke test. Use this when you want a spec file for a model or controller that wasn’t scaffolded.
Example
Section titled “Example”wheels generate test model UserWrites tests/specs/models/UserSpec.cfc.
wheels generate property
Section titled “wheels generate property”Generate an add-column migration for an existing model.
Synopsis
Section titled “Synopsis”wheels generate property <ModelName> <property:type>Description
Section titled “Description”Writes a Add<Property>To<Tables> migration under app/migrator/migrations/. The migration calls changeTable() with the mapped column type in up() and removeColumn() in down(). Property type mapping follows the same table as the attribute syntax, including name:type:optional and name:type=value. A column added to an existing table is created nullable so the migration succeeds on a populated table (a =value default is applied when given); backfill, then enforce the value with a validation.
For a required column (no :optional, no =value) the generator prints a reminder to add a matching validatesPresenceOf() call in the model’s config() — it does not modify the model CFC for you.
Example
Section titled “Example”wheels generate property User phoneNumber:stringWrites app/migrator/migrations/<timestamp>_AddPhoneNumberToUsers.cfc with t.string(columnNames="phoneNumber") in up().
wheels generate helper
Section titled “wheels generate helper”Generate a helper CFC under app/helpers/.
Synopsis
Section titled “Synopsis”wheels generate helper <name> [functionName ...] [--force]Description
Section titled “Description”Writes app/helpers/<Name>Helper.cfc (the Helper suffix is appended automatically) with a stub method per function name you pass. Without any function names, the helper gets one sample <name>Format() method. The --force flag overwrites an existing file; without it, the command exits with an error if the file already exists.
Example
Section titled “Example”wheels generate helper Formatting truncateText formatCurrencyWrites app/helpers/FormattingHelper.cfc with empty truncateText() and formatCurrency() methods.
wheels generate snippets
Section titled “wheels generate snippets”Stamp out a named code pattern.
Synopsis
Section titled “Synopsis”wheels generate snippets [pattern] [--force]wheels generate snippets templates [--force]Description
Section titled “Description”Running with no arguments prints the registry of available patterns. Running with a pattern name writes one or more files into the project — exactly which files depends on the pattern. Re-running a pattern whose files already exist is a no-op; pass --force to overwrite.
The special templates subcommand copies every raw generator template from the CLI’s bundled directory into app/snippets/ so you can customise them for your project. Regular named patterns write curated, opinionated starting code instead.
Patterns
Section titled “Patterns”| Pattern | What it writes |
|---|---|
auth | app/controllers/Sessions.cfc, app/views/sessions/new.cfm, app/snippets/auth-filter.cfm |
soft-delete | app/snippets/soft-delete.cfm, app/snippets/soft-delete-migration.cfc |
api-controller | app/snippets/api-controller.cfc |
crud-controller | app/snippets/crud-controller.cfc |
flash-messages | app/views/shared/_flash.cfm |
pagination | app/snippets/pagination-view.cfm |
seed-data | app/db/seeds.cfm, app/db/seeds/development.cfm (kept if they already exist, unless --force) |
mailer | app/snippets/user-mailer.cfc |
Example
Section titled “Example”wheels generate snippets authWrites a session controller, a login view, and an auth filter. Edit the filter and add filters(through="authenticate") to the controllers that need protection.
wheels generate admin
Section titled “wheels generate admin”Generate a CRUD admin interface for an existing model.
Synopsis
Section titled “Synopsis”wheels generate admin <ModelName> [--force] [--no-routes]Description
Section titled “Description”Unlike the other generators, this one requires a running dev server — it hits http://localhost:<port>/wheels/cli?command=introspect&model=<ModelName> to read the model’s properties, associations, and validations, then generates a controller and views that reflect the live schema. Start the server with wheels start first.
| Flag | Default | Description |
|---|---|---|
--force | off | Overwrite existing admin controller and view files. |
--no-routes | off | Skip adding the admin route to config/routes.cfm. |
Example
Section titled “Example”wheels startwheels generate admin UserAfter reload, the admin interface is available at /admin/users.
wheels generate auth
Section titled “wheels generate auth”Generate a complete authentication scaffold on the built-in wheels.auth primitives (Authenticator and the session/token/JWT strategies). Passwords are hashed with bcrypt via the framework’s bcryptHash() / bcryptVerify() helpers.
Synopsis
Section titled “Synopsis”wheels generate auth [ModelName] [--model=User] [--strategy=session|token|jwt] [--registration|--no-registration] [--force]| Flag | Default | Description |
|---|---|---|
--model=<Name> | User | Model (and table, pluralised) to generate. A bare positional name works too. |
--strategy=<name> | session | session (browser login), token (opaque API bearer tokens), or jwt (stateless signed tokens). |
--registration / --no-registration | on | Include the public sign-up flow (session strategy only). |
--force | off | Overwrite existing generated files and regenerate the injected config blocks in place. |
Description
Section titled “Description”The generated code is code you own — every file carries a stamped header, nothing is patched by framework upgrades. To pick up generator improvements later, re-run with --force on a clean branch and review the changes with git diff. The route, service, and strategy registrations are injected between // wheels:generate-auth:* marker comments and replaced in place on re-runs, never duplicated. The migration is never overwritten, even with --force.
All strategies share the same User model: bcrypt hashing through the framework’s bcryptHash() / bcryptVerify() / bcryptNeedsRehash() helpers — bundled jBCrypt on JVM engines, native builtins on RustCFML (a transient password property is validated — presence on create, 12-character minimum, confirmation — then hashed into passwordHash and scrubbed in a beforeSave callback), an authenticate() method with transparent rehash-on-login, and single-use SHA-256-digested password reset tokens that expire after 2 hours.
Session (default) writes Sessions/Passwords/Registrations controllers (every config() starts with super.config(), so the base controller’s CSRF protection stays active), startFormTag-based views, login/logout/register/password routes, and DI registrations for authenticator and sessionStrategy in config/services.cfm, with the strategy wired into the authenticator in app/events/onapplicationstart.cfm.
Token writes app/controllers/api/Sessions.cfc instead (no views; the registration flag doesn’t apply): POST /api/session exchanges credentials for an opaque bearer token returned exactly once — only its SHA-256 digest is stored — and DELETE /api/session revokes it. The TokenStrategy validator is registered at startup and resolves accounts by token digest.
JWT writes an api/Sessions.cfc that mints tokens with JwtService, signed with the WHEELS_JWT_SECRET environment variable (at least 32 random bytes). Startup fails loudly when the secret is missing or too short. JWTs have no server-side revocation — an issued token stays valid until it expires; use --strategy=token if you need instant revocation.
Both a model spec and a sessions-controller spec are generated under tests/specs/ so the scaffold is covered by wheels test from day one.
Example
Section titled “Example”wheels generate authwheels migrate latestwheels startWrites:
app/models/User.cfcwith hashing, validations, andauthenticate()app/migrator/migrations/<timestamp>_create_users_table.cfcwith a unique email indexapp/controllers/Sessions.cfc,Passwords.cfc,Registrations.cfcapp/views/sessions/new.cfm,registrations/new.cfm,passwords/new.cfm,passwords/edit.cfm- Marked blocks in
config/routes.cfm,config/services.cfm(created if absent), andapp/events/onapplicationstart.cfm tests/specs/models/UserAuthSpec.cfc,tests/specs/controllers/SessionsControllerSpec.cfc
After migrating and restarting, /login and /register are live. See the authentication patterns guide for how the pieces fit together and how to protect actions with a filter.
Common workflows
Section titled “Common workflows”Full CRUD resource from one command
Section titled “Full CRUD resource from one command”wheels generate scaffold Post title body:text publishedAt:datetimewheels migrate latestwheels startGenerates the model, migration, controller, views, tests, and route; migrates the database; and boots the server. /posts is live.
Just the model (and its migration)
Section titled “Just the model (and its migration)”wheels generate model Comment body:text --belongsTo=postCreates Comment.cfc with belongsTo('post'), plus a migration that includes a post_id foreign-key column (postId when useUnderscoreReferenceColumns is false). Useful when you want the association wired up but don’t need a controller or views yet.
A blank migration for a hand-written schema change
Section titled “A blank migration for a hand-written schema change”wheels generate migration AddUniqueIndexOnUsersEmailUse this when the change is something the property-based generators can’t express — adding a composite index, renaming a column, seeding reference data.
An API-only resource behind /api
Section titled “An API-only resource behind /api”wheels generate api-resource Product name price:decimalwheels migrate latestNo views, no HTML controllers — just a JSON endpoint mounted under /api/products.json.