CLI Overview
Quick Start Guide
wheels info
wheels reload
wheels deps
wheels destroy
wheels watch
wheels generate app
wheels generate app-wizard
wheels generate controller
wheels generate model
wheels generate view
wheels generate property
wheels generate route
wheels generate resource
wheels generate api-resource
wheels generate frontend
wheels generate test
wheels generate snippets
wheels scaffold
wheels db create
wheels db drop
wheels db setup
wheels db reset
wheels db status
wheels db version
wheels db rollback
wheels db seed
wheels db dump
wheels db restore
wheels db shell
wheels db schema
wheels dbmigrate info
wheels dbmigrate latest
wheels dbmigrate up
wheels dbmigrate down
wheels dbmigrate reset
wheels dbmigrate exec
wheels dbmigrate create blank
wheels dbmigrate create table
wheels dbmigrate create column
wheels dbmigrate remove table
wheels test
wheels test run
wheels test coverage
wheels test debug
wheels config list
wheels config set
wheels config env
wheels env
wheels env setup
wheels env list
wheels env switch
wheels environment
wheels console
wheels runner
wheels server
wheels server start
wheels server stop
wheels server restart
wheels server status
wheels server log
wheels server open
wheels plugins
wheels plugins list
wheels plugins install
wheels plugins remove
wheels analyze
wheels analyze code
wheels analyze performance
wheels analyze security
wheels security
wheels security scan
wheels optimize
wheels optimize performance
wheels docs
wheels docs generate
wheels docs serve
wheels ci init
wheels docker init
wheels docker deploy
wheels deploy
wheels deploy audit
wheels deploy exec
wheels deploy hooks
wheels deploy init
wheels deploy lock
wheels deploy logs
wheels deploy proxy
wheels deploy push
wheels deploy rollback
wheels deploy secrets
wheels deploy setup
wheels deploy status
wheels deploy stop
Configuration Management
Creating Commands
Service Architecture
Migrations Guide
Testing Guide
Object Relational Mapping
Creating Records
Reading Records
Updating Records
Deleting Records
Column Statistics
Dynamic Finders
Getting Paginated Data
Associations
Nested Properties
Object Validation
Object Callbacks
Calculated Properties
Transactions
Dirty Records
Soft Delete
Automatic Time Stamps
Using Multiple Data Sources
wheels generate resource (Coming Soon)
This command may not work as expected. A complete and stable version is coming soon.
Generate a complete RESTful resource with model, controller, views, and routes.
Synopsis
wheels generate resource [name] [properties] [options]
wheels g resource [name] [properties] [options]
Description
The wheels generate resource
command creates a complete RESTful resource including model, controller with all CRUD actions, views, routes, and optionally database migrations and tests. It's a comprehensive generator that sets up everything needed for a functioning resource.
Arguments
| Argument | Description | Default |
|----------|-------------|---------|
| name
| Resource name (singular) | Required |
Options
| Option | Description | Default |
|--------|-------------|---------|
| --api
| Generate API-only resource (no views) | false
|
| --tests
| Generate associated tests | true
|
| --migration
| Generate database migration | true
|
| belongs-to
| Parent model relationships (comma-separated) | |
| has-many
| Child model relationships (comma-separated) | |
| attributes
| Model attributes (name:type,email:string) | |
| --open
| Open generated files | false
|
| --scaffold
| Generate with full CRUD operations | true
|
Examples
Basic Resource
wheels generate resource product attributes="name:string,price:float,description:text"
Generates:
- Model:
/models/Product.cfc
- Controller:
/controllers/Products.cfc
- Views:
/views/products/
(index, show, new, edit, _form) - Route:
resources("products")
in/config/routes.cfm
- Migration:
/app/migrator/migrations/[timestamp]_create_products.cfc
- Tests:
/tests/models/ProductTest.cfc
,/tests/controllers/ProductsTest.cfc
API Resource
wheels generate resource product attributes="name:string,price:float" --api
Generates:
- Model:
/models/Product.cfc
- Controller:
/controllers/api/Products.cfc
(JSON responses only) - Route:
resources(name="products", except="new,edit")
in API namespace - Migration:
/app/migrator/migrations/[timestamp]_create_products.cfc
- Tests: API-focused test files
Resource with Associations
wheels generate resource comment attributes="content:text,approved:boolean" belongs-to="post,user"
Generates nested structure with proper associations and routing.
Generated Files
Model
/models/Product.cfc
:
component extends="Model" {
function init() {
// Properties
property(name="name", sql="name");
property(name="price", sql="price");
property(name="description", sql="description");
// Validations
validatesPresenceOf(properties="name,price");
validatesNumericalityOf(property="price", greaterThan=0);
validatesLengthOf(property="name", maximum=255);
// Callbacks
beforeSave("sanitizeInput");
}
private function sanitizeInput() {
this.name = Trim(this.name);
if (StructKeyExists(this, "description")) {
this.description = Trim(this.description);
}
}
}
Controller
/controllers/Products.cfc
:
component extends="Controller" {
function init() {
// Filters
filters(through="findProduct", only="show,edit,update,delete");
}
function index() {
products = model("Product").findAll(order="createdAt DESC");
}
function show() {
// Product loaded by filter
}
function new() {
product = model("Product").new();
}
function create() {
product = model("Product").new(params.product);
if (product.save()) {
flashInsert(success="Product was created successfully.");
redirectTo(route="product", key=product.id);
} else {
renderView(action="new");
}
}
function edit() {
// Product loaded by filter
}
function update() {
if (product.update(params.product)) {
flashInsert(success="Product was updated successfully.");
redirectTo(route="product", key=product.id);
} else {
renderView(action="edit");
}
}
function delete() {
if (product.delete()) {
flashInsert(success="Product was deleted successfully.");
} else {
flashInsert(error="Product could not be deleted.");
}
redirectTo(route="products");
}
// Filters
private function findProduct() {
product = model("Product").findByKey(params.key);
if (!IsObject(product)) {
flashInsert(error="Product not found.");
redirectTo(route="products");
}
}
}
Views
/views/products/index.cfm
:
<h1>Products</h1>
<p>
#linkTo(route="newProduct", text="New Product", class="btn btn-primary")#
</p>
<cfif products.recordCount>
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<cfoutput query="products">
<tr>
<td>#linkTo(route="product", key=products.id, text=products.name)#</td>
<td>#dollarFormat(products.price)#</td>
<td>#dateFormat(products.createdAt, "mmm dd, yyyy")#</td>
<td>
#linkTo(route="editProduct", key=products.id, text="Edit", class="btn btn-sm btn-secondary")#
#linkTo(route="product", key=products.id, text="Delete", method="delete", confirm="Are you sure?", class="btn btn-sm btn-danger")#
</td>
</tr>
</cfoutput>
</tbody>
</table>
<cfelse>
<p class="alert alert-info">No products found. #linkTo(route="newProduct", text="Create one now")#!</p>
</cfif>
/views/products/_form.cfm
:
#errorMessagesFor("product")#
#startFormTag(route=formRoute, key=formKey, class="needs-validation")#
<div class="mb-3">
#textField(objectName="product", property="name", label="Product Name", class="form-control", required=true)#
</div>
<div class="mb-3">
#numberField(objectName="product", property="price", label="Price", class="form-control", step="0.01", min="0", required=true)#
</div>
<div class="mb-3">
#textArea(objectName="product", property="description", label="Description", class="form-control", rows=5)#
</div>
<div class="mb-3">
#submitTag(value=submitValue, class="btn btn-primary")#
#linkTo(route="products", text="Cancel", class="btn btn-secondary")#
</div>
#endFormTag()#
Migration
/app/migrator/migrations/[timestamp]_create_products.cfc
:
component extends="wheels.migrator.Migration" hint="Create products table" {
function up() {
transaction {
createTable(name="products", force=true) {
t.increments("id");
t.string("name", limit=255, null=false);
t.decimal("price", precision=10, scale=2, null=false);
t.text("description");
t.timestamps();
t.index("name");
};
}
}
function down() {
transaction {
dropTable("products");
}
}
}
Routes
Added to /config/routes.cfm
:
<cfset resources("products")>
API Resource Generation
API Controller
/controllers/api/Products.cfc
:
component extends="Controller" {
function init() {
provides("json");
filters(through="findProduct", only="show,update,delete");
}
function index() {
products = model("Product").findAll(
order="createdAt DESC",
page=params.page ?: 1,
perPage=params.perPage ?: 25
);
renderWith({
data: products,
meta: {
page: products.currentPage,
totalPages: products.totalPages,
totalRecords: products.totalRecords
}
});
}
function show() {
renderWith(product);
}
function create() {
product = model("Product").new(params.product);
if (product.save()) {
renderWith(data=product, status=201);
} else {
renderWith(
data={errors: product.allErrors()},
status=422
);
}
}
function update() {
if (product.update(params.product)) {
renderWith(product);
} else {
renderWith(
data={errors: product.allErrors()},
status=422
);
}
}
function delete() {
if (product.delete()) {
renderWith(data={message: "Product deleted successfully"});
} else {
renderWith(
data={error: "Could not delete product"},
status=400
);
}
}
private function findProduct() {
product = model("Product").findByKey(params.key);
if (!IsObject(product)) {
renderWith(
data={error: "Product not found"},
status=404
);
}
}
}
Nested Resources
Generate Nested Resource
wheels generate resource review rating:integer comment:text parent=product
Nested Model
Includes association:
component extends="Model" {
function init() {
belongsTo("product");
property(name="rating", sql="rating");
property(name="comment", sql="comment");
property(name="productId", sql="product_id");
validatesPresenceOf(properties="rating,comment,productId");
validatesNumericalityOf(property="rating", greaterThanOrEqualTo=1, lessThanOrEqualTo=5);
}
}
Nested Routes
<cfset resources("products")>
<cfset resources("reviews")>
</cfset>
Property Types
Supported Types
| Type | Database Type | Validation |
|------|--------------|------------|
| string
| VARCHAR(255) | Length validation |
| text
| TEXT | None by default |
| integer
| INT | Numerical validation |
| float
| DECIMAL | Numerical validation |
| decimal
| DECIMAL | Numerical validation |
| boolean
| BOOLEAN | Boolean validation |
| date
| DATE | Date format validation |
| datetime
| DATETIME | DateTime validation |
| time
| TIME | Time validation |
| binary
| BLOB | None |
Property Options
wheels generate resource user \
email:string:required:unique \
age:integer:min=18:max=120 \
bio:text:limit=1000 \
isActive:boolean:default=true
Advanced Options
Skip Components
# Generate only model and migration
wheels generate resource product name:string --skip-controller --skip-views --skip-route
# Generate only controller and views
wheels generate resource product --skip-model --skip-migration
Namespace Resources
wheels generate resource admin/product name:string namespace=admin
Creates:
/controllers/admin/Products.cfc
/views/admin/products/
- Namespaced routes
Custom Templates
wheels generate resource product name:string template=custom
Testing
Generated Tests
Model Test (/tests/models/ProductTest.cfc
):
component extends="wheels.Test" {
function setup() {
super.setup();
model("Product").deleteAll();
}
function test_valid_product_saves() {
product = model("Product").new(
name="Test Product",
price=19.99,
description="Test description"
);
assert(product.save());
assert(product.id > 0);
}
function test_requires_name() {
product = model("Product").new(price=19.99);
assert(!product.save());
assert(ArrayLen(product.errorsOn("name")) > 0);
}
function test_requires_positive_price() {
product = model("Product").new(name="Test", price=-10);
assert(!product.save());
assert(ArrayLen(product.errorsOn("price")) > 0);
}
}
Controller Test (/tests/controllers/ProductsTest.cfc
):
component extends="wheels.Test" {
function test_index_returns_products() {
products = createProducts(3);
result = processRequest(route="products", method="GET");
assert(result.status == 200);
assert(Find("Products", result.body));
assert(FindNoCase(products[1].name, result.body));
}
function test_create_valid_product() {
params = {
product: {
name: "New Product",
price: 29.99,
description: "New product description"
}
};
result = processRequest(route="products", method="POST", params=params);
assert(result.status == 302);
assert(model("Product").count() == 1);
}
}
Best Practices
- Use singular names:
product
notproducts
- Define all properties: Include types and validations
- Add indexes: For frequently queried fields
- Include tests: Don't skip test generation
- Use namespaces: For admin or API resources
- Follow conventions: Stick to RESTful patterns
Common Patterns
Soft Delete Resource
wheels generate resource product name:string deletedAt:datetime:nullable
Publishable Resource
wheels generate resource post title:string content:text publishedAt:datetime:nullable status:string:default=draft
User-Owned Resource
wheels generate resource task title:string userId:integer:belongsTo=user completed:boolean:default=false
Hierarchical Resource
wheels generate resource category name:string parentId:integer:nullable:belongsTo=category
Customization
Custom Resource Templates
Create in ~/.wheels/templates/resources/
:
custom-resource/
├── model.cfc
├── controller.cfc
├── views/
│ ├── index.cfm
│ ├── show.cfm
│ ├── new.cfm
│ ├── edit.cfm
│ └── _form.cfm
└── migration.cfc
Template Variables
Available in templates:
${resourceName}
- Singular name${resourceNamePlural}
- Plural name${modelName}
- Model class name${controllerName}
- Controller class name${tableName}
- Database table name${properties}
- Array of property definitions
See Also
- wheels scaffold - Interactive CRUD generation
- wheels generate model - Generate models only
- wheels generate controller - Generate controllers only
- wheels generate api-resource - Generate API resources
- wheels generate route - Generate routes only
- Synopsis
- Arguments
- Options
- Examples
- Basic Resource
- API Resource
- Resource with Associations
- Generated Files
- Model
- Controller
- Views
- Migration
- Routes
- API Resource Generation
- API Controller
- Nested Resources
- Generate Nested Resource
- Nested Model
- Nested Routes
- Property Types
- Supported Types
- Property Options
- Advanced Options
- Skip Components
- Namespace Resources
- Custom Templates
- Testing
- Generated Tests
- Best Practices
- Common Patterns
- Soft Delete Resource
- Publishable Resource
- User-Owned Resource
- Hierarchical Resource
- Customization
- Custom Resource Templates
- Template Variables
- See Also