wheels scaffold
Generate complete CRUD scaffolding for a resource.
Synopsis
wheels scaffold name=[resourceName] [options]
Description
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.
Arguments
name
Resource name (singular)
Required
Options
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
Examples
Basic scaffold
wheels scaffold name=product
Scaffold with properties
wheels scaffold name=product properties=name:string,price:decimal,stock:integer
Scaffold with associations
wheels scaffold name=order properties=total:decimal,status:string \
belongsTo=user hasMany=orderItems
API scaffold
wheels scaffold name=product api=true properties=name:string,price:decimal
Scaffold with auto-migration
wheels scaffold name=category properties=name:string migrate=true
What Gets Generated
Standard Scaffold
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 recordsshow.cfm
- Display single recordnew.cfm
- New record formedit.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
API Scaffold
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
Generated Files Example
For wheels scaffold name=product properties=name:string,price:decimal,stock:integer
:
Model: /models/Product.cfc
/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);
}
}
Controller: /controllers/Products.cfc
/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");
}
}
View: /views/products/index.cfm
/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>
Form Partial: /views/products/_form.cfm
/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")#
Migration: /app/migrator/migrations/[timestamp]_create_products.cfc
/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");
}
}
}
Routes Configuration
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
Post-Scaffold Steps
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 indexCreate, edit, and delete records
Run generated tests
Customization
Adding Search
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();
}
}
Adding Pagination
function index() {
products = model("Product").findAll(
page=params.page ?: 1,
perPage=20,
order="createdAt DESC"
);
}
Adding Filters
function init() {
filters(through="authenticate", except="index,show");
}
Template Customization
The scaffold command uses templates to generate code. You can customize these templates to match your project's coding standards and markup preferences.
Template Override System
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.
How It Works
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
Customizing Templates
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
Available Templates
Templates used by scaffold command:
crud/index.txt
- Index/list viewcrud/show.txt
- Show single record viewcrud/new.txt
- New record form viewcrud/edit.txt
- Edit record form viewcrud/_form.txt
- Form partial shared by new/editModelContent.txt
- Model file structureControllerContent.txt
- Controller file structure
Template Placeholders
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
Best Practices
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
Comparison with Individual Generators
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
See Also
wheels generate model - Generate models
wheels generate controller - Generate controllers
wheels generate resource - Generate REST resources
wheels dbmigrate latest - Run migrations
Last updated
Was this helpful?