Generate complete CRUD scaffolding for a resource.
wheels scaffold name=[resourceName] [options]
The wheels scaffold
command generates a complete CRUD (Create, Read, Update, Delete) implementation including model, controller, views, tests, and database migration. It's the fastest way to create a fully functional resource.
name
Resource name (singular)
Required
properties
Model properties (format: name:type,name2:type2)
belongs-to
Parent model relationships (comma-separated)
has-many
Child model relationships (comma-separated)
--api
Generate API-only scaffold (no views)
false
--tests
Generate test files
true
--migrate
Run migrations after scaffolding
false
--force
Overwrite existing files
false
wheels scaffold name=product
wheels scaffold name=product properties=name:string,price:decimal,stock:integer
wheels scaffold name=order properties=total:decimal,status:string \
belongsTo=user hasMany=orderItems
wheels scaffold name=product api=true properties=name:string,price:decimal
wheels scaffold name=category properties=name:string migrate=true
Model (/models/Product.cfc
)
Properties and validations
Associations
Business logic
Controller (/controllers/Products.cfc
)
All CRUD actions
Flash messages
Error handling
Views (/views/products/
)
index.cfm
- List all records
show.cfm
- Display single record
new.cfm
- New record form
edit.cfm
- Edit record form
_form.cfm
- Shared form partial
Migration (/app/migrator/migrations/[timestamp]_create_products.cfc
)
Create table
Add indexes
Define columns
Tests (if enabled)
Model tests
Controller tests
Integration tests
Model - Same as standard
API Controller - JSON responses only
Migration - Same as standard
API Tests - JSON response tests
No Views - API doesn't need views
For wheels scaffold name=product properties=name:string,price:decimal,stock:integer
:
/models/Product.cfc
component extends="Model" {
function init() {
// Properties
property(name="name", label="Product Name");
property(name="price", label="Price");
property(name="stock", label="Stock Quantity");
// Validations
validatesPresenceOf("name,price,stock");
validatesUniquenessOf("name");
validatesNumericalityOf("price", greaterThan=0);
validatesNumericalityOf("stock", onlyInteger=true, greaterThanOrEqualTo=0);
}
}
/controllers/Products.cfc
component extends="Controller" {
function init() {
// Filters
}
function index() {
products = model("Product").findAll(order="name");
}
function show() {
product = model("Product").findByKey(params.key);
if (!IsObject(product)) {
flashInsert(error="Product not found.");
redirectTo(action="index");
}
}
function new() {
product = model("Product").new();
}
function create() {
product = model("Product").new(params.product);
if (product.save()) {
flashInsert(success="Product was created successfully.");
redirectTo(action="index");
} else {
flashInsert(error="There was an error creating the product.");
renderView(action="new");
}
}
function edit() {
product = model("Product").findByKey(params.key);
if (!IsObject(product)) {
flashInsert(error="Product not found.");
redirectTo(action="index");
}
}
function update() {
product = model("Product").findByKey(params.key);
if (IsObject(product) && product.update(params.product)) {
flashInsert(success="Product was updated successfully.");
redirectTo(action="index");
} else {
flashInsert(error="There was an error updating the product.");
renderView(action="edit");
}
}
function delete() {
product = model("Product").findByKey(params.key);
if (IsObject(product) && product.delete()) {
flashInsert(success="Product was deleted successfully.");
} else {
flashInsert(error="Product could not be deleted.");
}
redirectTo(action="index");
}
}
/views/products/index.cfm
<h1>Products</h1>
#flashMessages()#
<p>#linkTo(text="New Product", action="new", class="btn btn-primary")#</p>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Stock</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<cfloop query="products">
<tr>
<td>#encodeForHtml(products.name)#</td>
<td>#dollarFormat(products.price)#</td>
<td>#products.stock#</td>
<td>
#linkTo(text="Show", action="show", key=products.id)#
#linkTo(text="Edit", action="edit", key=products.id)#
#linkTo(text="Delete", action="delete", key=products.id,
method="delete", confirm="Are you sure?")#
</td>
</tr>
</cfloop>
</tbody>
</table>
/views/products/_form.cfm
#errorMessagesFor("product")#
#textField(objectName="product", property="name", label="Product Name")#
#textField(objectName="product", property="price", label="Price")#
#textField(objectName="product", property="stock", label="Stock Quantity")#
/app/migrator/migrations/[timestamp]_create_products.cfc
component extends="wheels.migrator.Migration" {
function up() {
transaction {
t = createTable("products");
t.string("name");
t.decimal("price", precision=10, scale=2);
t.integer("stock");
t.timestamps();
t.create();
addIndex(table="products", columns="name", unique=true);
}
}
function down() {
transaction {
dropTable("products");
}
}
}
Add to /config/routes.cfm
:
<cfset resources("products")>
This creates all RESTful routes:
GET /products - index
GET /products/new - new
POST /products - create
GET /products/[key] - show
GET /products/[key]/edit - edit
PUT/PATCH /products/[key] - update
DELETE /products/[key] - delete
Run migration (if not using --migrate
):
wheels dbmigrate latest
Add routes to /config/routes.cfm
:
<cfset resources("products")>
Restart application:
wheels reload
Test the scaffold:
Visit /products
to see the index
Create, edit, and delete records
Run generated tests
In controller's index()
:
function index() {
if (StructKeyExists(params, "search")) {
products = model("Product").findAll(
where="name LIKE :search",
params={search: "%#params.search#%"}
);
} else {
products = model("Product").findAll();
}
}
function index() {
products = model("Product").findAll(
page=params.page ?: 1,
perPage=20,
order="createdAt DESC"
);
}
function init() {
filters(through="authenticate", except="index,show");
}
The scaffold command uses templates to generate code. You can customize these templates to match your project's coding standards and markup preferences.
The CLI uses a template override system that allows you to customize the generated code:
CLI Templates - Default templates are located in the CLI module at /cli/templates/
App Templates - Custom templates in your application at /app/snippets/
override the CLI templates
This means you can modify the generated code structure by creating your own templates in the /app/snippets/
directory.
When generating code, the CLI looks for templates in this order:
First checks /app/snippets/[template-name]
Falls back to /cli/templates/[template-name]
if not found in app
To customize scaffold output:
Copy the template you want to customize from /cli/templates/
to /app/snippets/
Modify the template to match your project's needs
Run scaffold - it will use your custom template
Example for customizing the form template:
# Create the crud directory in your app
mkdir -p app/snippets/crud
# Copy the form template
cp /path/to/wheels/cli/templates/crud/_form.txt app/snippets/crud/
# Edit the template to match your markup
# The CLI will now use your custom template
Templates used by scaffold command:
crud/index.txt
- Index/list view
crud/show.txt
- Show single record view
crud/new.txt
- New record form view
crud/edit.txt
- Edit record form view
crud/_form.txt
- Form partial shared by new/edit
ModelContent.txt
- Model file structure
ControllerContent.txt
- Controller file structure
Templates use placeholders that get replaced during generation:
|ObjectNameSingular|
- Lowercase singular name (e.g., "product")
|ObjectNamePlural|
- Lowercase plural name (e.g., "products")
|ObjectNameSingularC|
- Capitalized singular name (e.g., "Product")
|ObjectNamePluralC|
- Capitalized plural name (e.g., "Products")
|FormFields|
- Generated form fields based on properties
<!--- CLI-Appends-Here --->
- Marker for future CLI additions
Properties: Define all needed properties upfront
Associations: Include relationships in initial scaffold
Validation: Add custom validations after generation
Testing: Always generate and run tests
Routes: Use RESTful resources when possible
Security: Add authentication/authorization
Templates: Customize templates in /app/snippets/
to match your project standards
Scaffold generates everything at once:
# Scaffold does all of this:
wheels generate model product properties="name:string,price:decimal"
wheels generate controller products --rest
wheels generate view products index,show,new,edit,_form
wheels generate test model product
wheels generate test controller products
wheels dbmigrate create table products
wheels generate model - Generate models
wheels generate controller - Generate controllers
wheels generate resource - Generate REST resources
wheels dbmigrate latest - Run migrations