Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 202 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

3.0.0-SNAPSHOT

INTRODUCTION

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Command Line Tools

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Core Commands

Code Generation

Migration Commands

Testing Commands

Database Commands

Database Operations

Environment Management

Plugin Management

Security Commands

Getting Started

Install Wheels and get a local development server running

By far the quickest way to get started with Wheels is via CommandBox. CommandBox brings a whole host of command line capabilities to the CFML developer. It allows you to write scripts that can be executed at the command line written entirely in CFML. It allows you to start a CFML server from any directory on your machine and wire up the code in that directory as the web root of the server. What's more is, those servers can be either Lucee servers or Adobe ColdFusion servers. You can even specify what version of each server to launch. Lastly, CommandBox is a package manager for CFML. That means you can take some CFML code and package it up into a module, host it on ForgeBox.io, and make it available to other CFML developers. In fact we make extensive use of these capabilities to distribute Wheels plugins and templates. More on that later.

One module that we have created is a module that extends CommandBox itself with commands and features specific to the Wheels framework. The Wheels CLI module for CommandBox is modeled after the Ruby on Rails CLI module and gives similar capabilities to the Wheels developer.

Install CommandBox

The first step is to get CommandBox downloaded and running. CommandBox is available for Windows, Mac & Linux, and can be installed manually or using one of the respective package managers for each OS. You can use Chocolatey on Windows, Homebrew on MacOS, or Yum/Apt on Linux depending on your flavor of Linux. Please follow the instructions on how to install CommandBox on your particular operating system. At the end of the installation process you want to make sure the box command is part of your system path so you can call the command from any directory on your system.

Once installed, you can either double-click on the box executable which opens the CommandBox shell window, or run box from a CMD window in Windows, Terminal window in MacOS, or shell prompt on a Linux server. Sometimes you only want to call a single CommandBox command and don't need to launch a whole CommandBox shell window to do that, for these instances you can call the CommandBox command directly from your default system terminal window by prefixing the command with the box prefix.

So to run the CommandBox version command you could run box version from the shell or you could launch the CommandBox shell and run version inside it.

box version

version

This is a good concept to grasp, cause depending on your workflow, you may find it easier to do one versus the other. Most of the commands you will see in these CLI guides will assume that you are entering the command in the actual CommandBox shell so the box prefix is left off.

Install the wheels-cli CommandBox Module

Okay, now that we have CommandBox installed, let's add the Wheels CLI module.

install wheels-cli

Installing this module will add a number of commands to your default CommandBox installation. All of these commands are prefixed by the wheels name space. There are commands to create a brand new Wheels application or scaffold out sections of your application. We'll see some of these commands in action momentarily.

Start a new Application using the Wizard

To install a new application using version 3.0, we can use the new application wizard and select Bleeding Edge when prompted to select the template to use.

wheels new

Start a New Application Using the Command Line

Now that we have CommandBox installed and extended it with the Wheels CLI module, let's start our first Wheels app from the command line. We'll look at the simplest method for creating a Wheels app and starting our development server.

wheels generate app myApp server start

A few minutes after submitting the above commands a new browser window should open up and display the default Wheels congratulations screen.

So what just happened? Since we only passed the application name myApp to the wheels generate app command, it used default values for most of its parameters and downloaded our Base template (wheels-base-template) from ForgeBox.io, then downloaded the framework core files (wheels.dev) from ForgeBox.io and placed it in the vendor/wheels directory, then configured the application name and reload password, and started a Lucee server on a random port.

A Word About Command Aliases

CommandBox commands have the capability to be called by multiple names or aliases. The command above wheels generate app can also be initiated by typing wheels g app. In fact g is an alias for generate so wherever you see a command in the CLI documentation that has generate in it you can substitute g instead.

In addition to shortening generate to g, aliases can completely change the name space as well. A command that you haven't seen yet is the wheels generate app-wizard command. This command guides the user through a series of menu options, building up all the parameters needed to customize the start of a new Wheels project. You're likely to use the wizard when starting a new Wheels application so it's good to become familiar with it.

This command has the normal alias referenced above at wheels g app-wizard but it also has an additional alias at wheels new which is the command more prevalent in the Rails community. So the three commands wheels generate app-wizard, wheels g app-wizard, and wheels new all call the same functionality which guides the user though a set of menus, collecting details on how to configure the desired app. Once all the parameters have been gathered, this command actually calls the wheels generate app command to create the actual Wheels application.

This Getting Started guide has taken you from the very beginning and gotten you to the point where you can go into any empty directory on your local development machine and start a Wheels project by issuing a couple of CLI commands. In later guides we'll explore these options further and see what else the CLI can do for us.

Manual Installation

Instructions for installing Wheels on your system.

Installing Wheels is so simple that there is barely a need for a chapter devoted to it. But we figured we'd better make one anyway in case anyone is specifically looking for a chapter about installation.

So, here are the simple steps you need to follow to get rolling on Wheels...

Manual Installation

1. Download Wheels

You have 2 choices when downloading Wheels. You can either use the latest official release of Wheels, or you can take a walk on the wild side and go with the latest committed source code in our Git repository.

The latest official releases can always be found in the Releases section of GitHub, and the Git repository is available at our GitHub repo.

In most cases, we recommend going with the official release because it's well documented and has been through a lot of bug testing. Only if you're in desperate need of a feature that has not been released yet would we advise you to go with the version stored in the Git master branch.

Let's assume you have downloaded the latest official release. (Really, you should go with this option.) You now have a .zip file saved somewhere on your computer. On to the next step...

2. Setup the Website

Getting an empty website running with Wheels installed is an easy process if you already know your way around IIS or Apache. Basically, you need to create a new website in your web server of choice and unzip the contents of the file into the root of it.

In case you're not sure, here are the instructions for setting up an empty Wheels site that can be accessed when typing localhost in your browser. The instructions refer to a system running Windows Server 2003 and IIS, but you should be able to follow along and apply the instructions with minor modifications to your system. (See Requirements for a list of tested systems).

  • Create a new folder under your web root (usually C:\Inetpub\wwwroot) named wheels_site and unzip the Wheels .zip file into the root of it.

  • Create a new website using IIS called Wheels Site with localhost as the host header name and C:\Inetpub\wwwroot\mysite as the path to your home directory.

If you want to run a Wheels-powered application from a subfolder in an existing website, this is entirely possible, but you may need to get a little creative with your URL rewrite rules if you want to get pretty URLs--it will only work out of the box on recent versions of Apache. (Read more about this in the URL Rewriting chapter.)

3. Setup the Database (Optional)

Create a new database in MySQL, PostgreSQL, Microsoft SQL Server, or H2 and add a new data source for it in the ColdFusion/Lucee Administrator, just as you'd normally do. Now open up /config/settings.cfm and call set(dataSourceName="") with the name you chose for the data source.

If you don't want to be bothered by opening up a Wheels configuration file at all, there is a nice convention you can follow for the naming. Just name your data source with the same name as the folder you are running your website from (mysite in the example above), and Wheels will use that when you haven't set the dataSourceName setting using the Set() function.

4. Test It

When you've followed the steps above, you can test your installation by typing http://localhost/ (or whatever you set as the host header name) in your web browser. You should get a "Congratulations!" page.

That's it. You're done. This is where the fun begins!

wheels config env

Manage environment-specific configuration for your Wheels application.

Usage

wheels config env <action> [source] [target]

Parameters

  • action - (Required) Action to perform: list, create, copy

  • source - (Optional) Source environment for copy action

  • target - (Optional) Target environment for create or copy action

Description

The wheels config env command provides tools for managing environment-specific configurations. It helps you list, create, and copy configurations between different environments.

Examples

List all environments

wheels config env list

Create a new environment

wheels config env create production

Copy environment configuration

wheels config env copy development production

Actions

List Environments

Display all available environments:

wheels config env list

Output:

Available Environments:
----------------------
• development (active)
• testing
• maintenance  
• production

Create Environment

Create a new environment configuration:

wheels config env create staging

This creates a new environment configuration file at /config/staging/settings.cfm.

Copy Environment

Copy configuration from one environment to another:

wheels config env copy development staging

This copies all settings from the development environment to the staging environment, preserving environment-specific values like datasource names.

Notes

  • Some operations require application restart

  • Sensitive values protected by default

  • Changes logged for audit purposes

  • Use templates for consistency

See Also

  • wheels config list - List all settings

  • wheels config set - Set configuration values

  • wheels env - Environment management

  • Configuration Guide

Code Analysis

Server Management

Running Local Development Servers

Starting a local development server

With CommandBox, we don't need to have Lucee or Adobe ColdFusion installed locally. With a simple command, we can make CommandBox go and get the CFML engine we've requested, and quickly create a server running on Undertow. Make sure you're in the root of your website, and then run:

server start

The server will then start on a random port on 127.0.0.1 based the configuration from the server.json file that is in the root of your application that comes with wheels. We can add various options to server.json to customize our server. Your default server.json will look something like this:

In this server.json file, the server name is set to wheels, meaning I can now start the server from any directory by simply calling start myApp. We don't have any port specified, but you can specify any port you want. Lastly, we have URL rewriting enabled and pointed the URL rewrite configuration file to public/urlrewrite.xml, which is included starting from Wheels 2.x.

Using custom host names

You can also specify hosts other than localhost: there's a useful CommandBox module to do that () which will automatically create entries in your hosts file to allow for domains such as myapp.local running on port 80. You can install it via install commandbox-hostupdater when running the box shell with administrator privileges.

Controlling local servers

Obviously, anything you start, you might want to stop. Servers can be stopped either via right/ctrl clicking on the icon in the taskbar, or by the stop command. To stop a server running in the current directory issue the following:

server stop

You can also stop a server from anywhere by using its name:

server stop myApp

If you want to see what server configurations exist on your system and their current status, simply do server list

server list

To remove a server configuration from the list, you can use server forget myapp. Note the status of the servers on the list are somewhat unreliable, as it only remembers the last known state of the server: so if you start a server and then turn off your local machine, it may still remember it as running when you turn your local machine back on, which is why we recommend the use of force: true in the server.json file.

Specifying different CF engines

By default, CommandBox will run Lucee (version 6.x at time of writing). You may wish to specify an exact version of Lucee, or use Adobe ColdFusion. We can do this via either setting the appropriate cfengine setting in server.json, or at runtime with the cfengine= argument.

Start the default engine

CommandBox> start

__

Start the latest stable Lucee 5.x engine

CommandBox> start cfengine=lucee5

__

Start a specific engine and version

CommandBox> start [email protected]

__

Start the most recent Adobe server that starts with version "11"

CommandBox> start cfengine=adobe@11

__

Start the most recent adobe engine that matches the range

CommandBox> start cfengine="adobe@>9.0 <=11"

Or via server.json

CFIDE / Lucee administrators

The default username and password for all administrators is admin & commandbox

You can of course run multiple servers, so if you need to test your app on Lucee 5.x, Lucee 6.x and Adobe 2018, you can just start three servers with different cfengine= arguments!

Watch out

CommandBox 5.1 required to install dependencies easily

By default, the Lucee server that CommandBox starts includes all the essential Lucee extensions you need, but if need to minimize the size of the Lucee instance you launch, then you can use Lucee-Light by specifying cfengine=lucee-light in your server.json file. Wheels can run just fine on lucee-light (which is after all, Lucee, minus all the extensions) but at a minimum, requires the following extensions to be installed as dependencies in your box.json. Please note you may have to add any drivers you need for your database to this list as well.

Once added to your box.json file, while the server is running, just do box install, which will install the dependencies, and load them into the running server within 60 seconds.

Alternatively you can download the extensions and add them manually to your server root's deploy folder (i.e \WEB-INF\lucee-server\deploy)

wheels info

Display CLI and Wheels framework version information.

Synopsis

Description

The wheels info command displays information about the Wheels CLI module and identifies the Wheels framework version in the current directory.

Arguments

This command has no arguments.

Options

Option
Description

Output

The command displays:

  1. Wheels ASCII Art - A colorful banner

  2. Current Working Directory - Where you're running the command from

  3. CommandBox Module Root - Where the CLI module is installed

  4. Current Wheels Version - The detected Wheels framework version in this directory

Example Output

Use Cases

  • Verify CLI installation location

  • Check Wheels framework version in current directory

  • Troubleshoot path issues

  • Quick visual confirmation of Wheels environment

Notes

  • The Wheels version is detected by looking for box.json files in the vendor/wheels directory

  • If no Wheels version is found, it will show "Not Found"

  • The colorful ASCII art helps quickly identify you're using Wheels CLI

See Also

  • - Initialize a Wheels application

  • - Manage dependencies

wheels db create

Create a new database based on your datasource configuration.

Synopsis

Description

The wheels db create command creates a new database using the connection information from your configured datasource. The datasource must already exist in your CFML server configuration - this command creates the database itself, not the datasource.

Options

--datasource=

Specify which datasource to use. If not provided, uses the default datasource from your Wheels configuration.

--environment=

Specify the environment to use. Defaults to the current environment.

Examples

Basic Usage

Create database using default datasource:

Specific Datasource

Create database for development:

Create database for testing:

Database-Specific Behavior

MySQL/MariaDB

  • Creates database with UTF8MB4 character set

  • Uses utf8mb4_unicode_ci collation

  • Connects to information_schema to execute CREATE DATABASE

PostgreSQL

  • Creates database with UTF8 encoding

  • Checks if database already exists before creating

  • Connects to postgres system database

SQL Server

  • Creates database with default settings

  • Checks if database already exists before creating

  • Connects to master system database

H2

  • Displays message that H2 databases are created automatically

  • No action needed - database file is created on first connection

Prerequisites

  1. Datasource Configuration: The datasource must be configured in your CFML server admin

  2. Database Privileges: The database user must have CREATE DATABASE privileges

  3. Network Access: The database server must be accessible

Error Messages

"Datasource not found"

The specified datasource doesn't exist in your server configuration. Create it in your CFML admin first.

"Database already exists"

The database already exists. Use wheels db drop first if you need to recreate it.

"Access denied"

The database user doesn't have permission to create databases. Grant CREATE privileges to the user.

Related Commands

  • - Drop an existing database

  • - Create and setup database

  • - Run migrations after creating database

wheels db drop

Drop an existing database.

Synopsis

Description

The wheels db drop command permanently deletes a database. This is a destructive operation that cannot be undone. By default, it requires confirmation unless the --force flag is used.

Options

--datasource=

Specify which datasource's database to drop. If not provided, uses the default datasource from your Wheels configuration.

--environment=

Specify the environment to use. Defaults to the current environment.

--force

Skip the confirmation prompt. Useful for scripting.

Examples

Basic Usage

Drop database with confirmation:

Force Drop

Drop without confirmation:

Drop Test Database

Safety Features

  1. Confirmation Required: By default, you must type "yes" to confirm

  2. Production Warning: Extra warning when dropping production databases

  3. Clear Messaging: Shows database name and environment before dropping

Database-Specific Behavior

MySQL/MariaDB

  • Uses DROP DATABASE IF EXISTS statement

  • Connects to information_schema to execute command

PostgreSQL

  • Terminates existing connections before dropping

  • Uses DROP DATABASE IF EXISTS statement

  • Connects to postgres system database

SQL Server

  • Sets database to single-user mode to close connections

  • Uses DROP DATABASE IF EXISTS statement

  • Connects to master system database

H2

  • Deletes database files (.mv.db, .lock.db, .trace.db)

  • Shows which files were deleted

Warning

This operation is irreversible! Always ensure you have backups before dropping a database.

Best Practices

  1. Always backup first:

  2. Use --force carefully: Only in scripts where you're certain

  3. Double-check environment: Especially important for production

Error Messages

"Database not found"

The database doesn't exist. No action needed.

"Access denied"

The database user doesn't have permission to drop databases. Grant DROP privileges to the user.

"Database in use"

Some databases prevent dropping while connections are active. The command attempts to close connections automatically.

Related Commands

  • - Create a new database

  • - Drop and recreate database

  • - Backup before dropping

wheels dbmigrate up

Run the next pending database migration.

Synopsis

Alias: wheels db up

Description

The dbmigrate up command executes the next pending migration in your database migration queue. This command is used to incrementally apply database changes one migration at a time, allowing for controlled and reversible database schema updates.

Parameters

None.

Examples

Run the next pending migration

This will execute the next migration in the sequence and update the database schema version.

Use Cases

Incremental Database Updates

When you want to apply database changes one at a time rather than all at once:

Controlled Migration Application

Apply migrations one at a time for better control:

Notes

  • Migrations are executed in chronological order based on their timestamps

  • Each migration is tracked in the database to prevent duplicate execution

  • If already at latest version, displays: "We're all up to date already!"

  • If no more versions available, displays: "No more versions to go to?"

  • Automatically runs dbmigrate info after successful migration

  • Always backup your database before running migrations in production

Related Commands

  • - Rollback the last migration

  • - Run all pending migrations

  • - View migration status

  • - Reset all migrations

wheels config set

Set configuration values for your Wheels application.

Usage

Parameters

  • setting - (Required) Key=Value pair for the setting to update

  • --environment - (Optional) Environment to apply settings to: development, testing, production, all. Default: development

  • --encrypt - (Optional) Encrypt sensitive values

Description

The wheels config set command updates configuration settings in your Wheels application. Settings must be provided in key=value format.

Examples

Set basic configuration

Set for specific environment

Set for all environments

Set encrypted value

Configuration Values

The command accepts various value types:

String Values

Boolean Values

Numeric Values

Where Settings Are Saved

Settings are saved to environment-specific configuration files:

  • Development: /config/development/settings.cfm

  • Testing: /config/testing/settings.cfm

  • Production: /config/production/settings.cfm

  • All environments: /config/settings.cfm

Example:

Sensitive Values

Use the --encrypt flag for sensitive values:

Environment-Specific Settings

Target specific environments with the --environment flag:

Best Practices

  1. Use environment-specific settings: Don't set production values in development

  2. Encrypt sensitive data: Use --encrypt for passwords and keys

  3. Test changes: Verify settings with wheels config list

  4. Restart after changes: Some settings require application restart

Notes

  • Some settings require application restart

  • Encrypted values can't be read back

  • Changes are logged for audit

  • Use environment variables for containers

See Also

  • - List configuration

  • - Environment config

  • - Environment management

wheels server restart

Restart the Wheels development server and reload the application.

Synopsis

Description

The wheels server restart command restarts a running CommandBox server and automatically reloads the Wheels application. This ensures that both server-level and application-level changes are picked up.

Options

name

  • Type: String

  • Description: Name of the server to restart

  • Example: wheels server restart name=myapp

--force

  • Type: Boolean flag

  • Description: Force restart even if server appears stopped

  • Example: wheels server restart --force

Examples

What It Does

  1. Stops the running server

  2. Starts the server again with the same configuration

  3. Automatically runs wheels reload to refresh the application

  4. Confirms successful restart

Notes

  • This command is particularly useful when you've made changes to server configuration or installed new dependencies

  • The automatic application reload ensures that Wheels picks up any code changes

  • If the reload fails, you may need to manually refresh your browser

Related Commands

  • - Start the server

  • - Stop the server

  • - Check server status

  • - Reload just the application

See Also

{
    "name":"wheels",
    "web":{
        "host":"localhost",
        "webroot":"public",
        "rewrites":{
            "enable":true,
            "config":"public/urlrewrite.xml"
        }
    },
    "app":{
        "cfengine":"lucee"
    }
}
myapp (stopped)
 http://127.0.0.1:60000
 Webroot: /Users/wheels/Documents/myapp

myAPI (stopped)
 http://127.0.0.1:60010
 Webroot: /Users/wheels/Documents/myAPI

megasite (stopped)
 http://127.0.0.1:61280
 CF Engine: lucee 4.5.4+017
 Webroot: /Users/wheels/Documents/megasite

awesomesite (stopped)
 http://127.0.0.1:60015
 CF Engine: lucee 4.5.4+017
 Webroot: /Users/wheels/Documents/awesomeo
{
    "name":"myApp",
    "force":true,
    "web":{
        "http":{
            "host":"localhost",
            "port":60000
        },
        "rewrites":{
            "enable":true,
            "config":"urlrewrite.xml"
        }
    },
    "app":{
        "cfengine":"adobe2018"
    },
}
"dependencies":{
    "lucee-image":"lex:https://ext.lucee.org/lucee.image.extension-1.0.0.35.lex",
    "lucee-zip": "lex:https://ext.lucee.org/compress-extension-1.0.0.2.lex",
    "lucee-esapi": "lex:https://ext.lucee.org/esapi-extension-2.1.0.18.lex"
}
Host updater
wheels info

--help

Show help information

,--.   ,--.,--.                   ,--.            ,-----.,--.   ,--. 
|  |   |  ||  ,---.  ,---.  ,---. |  | ,---.     '  .--./|  |   |  | 
|  |.'.|  ||  .-.  || .-. :| .-. :|  |(  .-'     |  |    |  |   |  | 
|   ,'.   ||  | |  |\   --.\   --.|  |.-'  `)    '  '--'\|  '--.|  | 
'--'   '--'`--' `--' `----' `----'`--'`----'      `-----'`-----'`--' 
============================ Wheels CLI ============================
Current Working Directory: /Users/username/myapp
CommandBox Module Root: /Users/username/.CommandBox/cfml/modules/cfwheels-cli/
Current Wheels Version in this directory: 2.5.0
====================================================================
wheels init
wheels deps
wheels db create [--datasource=<name>] [--environment=<env>]
wheels db create --datasource=myapp_dev
wheels db create --environment=testing
wheels db create
wheels db create --datasource=myapp_dev
wheels db create --datasource=myapp_test --environment=testing
wheels db drop
wheels db setup
wheels dbmigrate latest
wheels db drop [--datasource=<name>] [--environment=<env>] [--force]
wheels db drop --datasource=myapp_dev
wheels db drop --environment=testing
wheels db drop --force
wheels db drop
# Will prompt: Are you sure you want to drop the database 'myapp_dev'? Type 'yes' to confirm:
wheels db drop --force
wheels db drop --datasource=myapp_test --environment=testing --force
wheels db dump --output=backup-before-drop.sql
wheels db drop
wheels db create
wheels db reset
wheels db dump
wheels dbmigrate up
wheels dbmigrate up
# Check pending migrations
wheels dbmigrate info

# Apply next migration
wheels dbmigrate up

# Verify the change
wheels dbmigrate info
# Check current status
wheels dbmigrate info

# Apply next migration
wheels dbmigrate up

# Verify the change was applied
wheels dbmigrate info
wheels dbmigrate down
wheels dbmigrate latest
wheels dbmigrate info
wheels dbmigrate reset
wheels config set <key>=<value> [--environment=<env>] [--encrypt]
wheels config set dataSourceName=wheels_production
wheels config set showDebugInformation=false --environment=production
wheels config set reloadPassword=newPassword --environment=production
wheels config set defaultLayout=main --environment=all
wheels config set apiKey=sk_live_abc123 --encrypt
wheels config set dataSourcePassword=mySecret --encrypt
wheels config set appName="My Wheels App"
wheels config set [email protected]
wheels config set showDebugInformation=true
wheels config set cacheQueries=false
wheels config set sessionTimeout=1800
wheels config set maxUploadSize=10485760
// Added to /config/production/settings.cfm
set(dataSourceName="wheels_production");
wheels config set reloadPassword=mySecret --encrypt
wheels config set apiKey=sk_live_123456 --encrypt
# Development only
wheels config set showDebugInformation=true --environment=development

# Production only
wheels config set cacheQueries=true --environment=production

# All environments
wheels config set appName="My App" --environment=all
wheels config list
wheels config env
wheels env
Configuration Guide
wheels server restart [options]
# Restart the default server
wheels server restart

# Restart a specific named server
wheels server restart name=myapp

# Force restart
wheels server restart --force
wheels server start
wheels server stop
wheels server status
wheels reload
CommandBox Server Documentation

wheels init

Bootstrap an existing Wheels application for CLI usage.

Synopsis

wheels init

Description

The wheels init command initializes an existing Wheels application to work with the Wheels CLI. It's an interactive command that helps set up necessary configuration files (box.json and server.json) for an existing Wheels installation.

Arguments

This command has no arguments - it runs interactively and prompts for required information.

Options

Option
Description

--help

Show help information

Interactive Prompts

When you run wheels init, you'll be prompted for:

  1. Confirmation - Confirm you want to proceed with initialization

  2. Application Name - Used to make server.json server name unique (if box.json doesn't exist)

  3. CF Engine - Default CFML engine (e.g., lucee5, adobe2021) (if server.json doesn't exist)

Examples

Initialize current directory

wheels init

Example interaction:

==================================== Wheels init ===================================
 This function will attempt to add a few things
 to an EXISTING Wheels installation to help
 the CLI interact.

 We're going to assume the following:
  - you've already setup a local datasource/database
  - you've already set a reload password

 We're going to try and do the following:
  - create a box.json to help keep track of the wheels version
  - create a server.json
====================================================================================

Sound ok? [y/n] y
Please enter an application name: myapp
Please enter a default cfengine: lucee5

What It Does

  1. Creates vendor/wheels/box.json - Tracks the Wheels framework version

  2. Creates server.json - Configures CommandBox server settings with:

    • Unique server name based on application name

    • Selected CF engine

    • Default port and settings

  3. Creates box.json - Main project configuration file with:

    • Application name

    • Wheels version dependency

    • Project metadata

Generated Files

server.json

{
  "name": "myapp",
  "web": {
    "http": {
      "port": 60000
    }
  },
  "app": {
    "cfengine": "lucee5"
  }
}

box.json

{
  "name": "myapp",
  "version": "1.0.0",
  "dependencies": {
    "wheels": "^2.5.0"
  }
}

Prerequisites

Before running wheels init:

  • Have an existing Wheels application

  • Database/datasource already configured

  • Reload password already set in your application settings

Notes

  • Run this command in the root directory of your Wheels application

  • Files are only created if they don't already exist

  • The command detects your current Wheels version automatically

  • Special characters are stripped from application names

See Also

  • wheels generate app - Create a new Wheels application

  • wheels reload - Reload the application

  • wheels info - Display version information

wheels db reset

Reset the database by dropping it and recreating it from scratch.

Synopsis

wheels db reset [--datasource=<name>] [--environment=<env>] [--force] [--skip-seed] [--seed-count=<n>]

Description

The wheels db reset command completely rebuilds your database by executing four operations in sequence:

  1. Drops the existing database (wheels db drop)

  2. Creates a new database (wheels db create)

  3. Runs all migrations (wheels dbmigrate latest)

  4. Seeds the database with sample data (wheels db seed)

This is a destructive operation that will delete all existing data.

Options

--datasource=

Specify which datasource to use. If not provided, uses the default datasource from your Wheels configuration.

wheels db reset --datasource=myapp_dev

--environment=

Specify the environment to use. Defaults to the current environment.

wheels db reset --environment=testing

--force

Skip the confirmation prompt. Use with caution!

wheels db reset --force

--skip-seed

Skip the database seeding step.

wheels db reset --skip-seed

--seed-count=

Number of records to generate per model when seeding. Defaults to 5.

wheels db reset --seed-count=20

Examples

Basic Usage

Reset with confirmation:

wheels db reset
# Will prompt: Are you sure you want to reset the database? Type 'yes' to confirm:

Force Reset

Reset without confirmation:

wheels db reset --force

Reset Test Database

wheels db reset --datasource=myapp_test --environment=testing --force

Reset Without Seeding

wheels db reset --skip-seed --force

Safety Features

  1. Confirmation Required: Must type "yes" to confirm (unless --force)

  2. Production Warning: Extra warning for production environment

  3. Special Production Confirmation: Must type "reset production database" for production

Warning

This operation is irreversible! All data will be permanently lost.

Common Use Cases

Development Reset

# When you need a fresh start
wheels db reset --force --seed-count=50

Before Major Changes

# Backup first
wheels db dump --output=backup-before-reset.sql

# Then reset
wheels db reset

Automated Testing

# In test scripts
wheels db reset --environment=testing --force --skip-seed

What Happens

  1. Drop Database

    • All tables and data are deleted

    • Database is completely removed

  2. Create Database

    • Fresh database is created

    • Character set and collation are set (MySQL)

  3. Run Migrations

    • All migrations run from scratch

    • Schema is recreated

  4. Seed Database (unless --skip-seed)

    • Sample data is generated

    • Useful for development/testing

Error Recovery

If reset fails partway through:

# Manual recovery steps
wheels db drop --force        # Ensure old database is gone
wheels db create             # Create new database
wheels dbmigrate latest      # Run migrations
wheels db seed              # Seed data (optional)

Best Practices

  1. Always backup production data first

  2. Use --force only in automated scripts

  3. Avoid resetting production databases

  4. Use db setup for new databases instead

Related Commands

  • wheels db setup - Setup new database (non-destructive)

  • wheels db drop - Just drop database

  • wheels db dump - Backup before reset

  • wheels dbmigrate reset - Reset just migrations

wheels server

Manage the Wheels development server with enhanced functionality.

Synopsis

wheels server [subcommand] [options]

Description

The wheels server command provides a suite of server management tools that wrap CommandBox's native server functionality with Wheels-specific enhancements. These commands add validation, helpful error messages, and integration with the Wheels framework.

Available Subcommands

  • start - Start the development server

  • stop - Stop the development server

  • restart - Restart the development server

  • status - Show server status

  • log - Tail server logs

  • open - Open application in browser

Key Features

Wheels Application Validation

All server commands check that you're working in a valid Wheels application directory before executing. This prevents common errors and provides helpful guidance.

Enhanced Status Information

When checking server status, the commands also display:

  • Wheels framework version

  • Application root directory

  • Quick action suggestions

Integrated Restart

The restart command not only restarts the server but also reloads the Wheels application, ensuring your changes are picked up.

Smart Browser Opening

The open command can open specific paths in your application and works with your preferred browser.

Examples

# Display available server commands
wheels server

# Start server on port 8080
wheels server start port=8080

# Check if server is running
wheels server status

# Tail the server logs
wheels server log

# Open admin panel in browser
wheels server open /admin

Configuration

Server settings can be configured through:

  1. Command line options - Pass options directly to commands

  2. server.json - Create a server.json file in your project root

  3. box.json - Configure server settings in your box.json

Example server.json:

{
  "web": {
    "port": 8080,
    "host": "127.0.0.1",
    "rewrites": {
      "enable": true
    }
  }
}

Integration with CommandBox

These commands are thin wrappers around CommandBox's native server commands, providing:

  • Validation specific to Wheels applications

  • Better error messages and guidance

  • Integration with Wheels-specific features

  • Consistent command structure

You can always use the native CommandBox commands directly if needed:

# Native CommandBox commands still work
server start
server stop
server status

Troubleshooting

Server won't start

  1. Check if a server is already running: wheels server status

  2. Try forcing a start: wheels server start --force

  3. Check for port conflicts: wheels server start port=8081

Can't find Wheels application

Ensure you're in a directory containing:

  • /vendor/wheels - The Wheels framework

  • /config - Configuration files

  • /app - Application code

Server starts but application doesn't work

  1. Check logs: wheels server log

  2. Verify database connection in datasource settings

  3. Try reloading: wheels reload

See Also

  • CommandBox Server Documentation

  • Wheels Installation Guide

wheels plugins list

Lists installed Wheels CLI plugins or shows available plugins from ForgeBox.

Usage

wheels plugins list [--global] [--format=<format>] [--available]

Parameters

  • --global - (Optional) Show globally installed plugins

  • --format - (Optional) Output format: table, json. Default: table

  • --available - (Optional) Show available plugins from ForgeBox

Description

The plugins list command displays information about all plugins installed in your Wheels application, including:

  • Plugin name and version

  • Installation status (active/inactive)

  • Compatibility with current Wheels version

  • Description and author information

  • Dependencies on other plugins

Examples

List all local plugins

wheels plugins list

Show globally installed plugins

wheels plugins list --global

Export as JSON

wheels plugins list --format=json

Show available plugins from ForgeBox

wheels plugins list --available

Output

Table Format (Default)

🧩 Installed Wheels CLI Plugins

Name                Version    Description
---------------------------------------------
wheels-vue-cli      1.2.0     Vue.js integration for Wheels
wheels-docker       2.0.1     Docker deployment tools
wheels-testing      1.5.0     Advanced testing utilities

Total: 3 plugins

Available Plugins from ForgeBox

================ Available Wheels Plugins From ForgeBox ======================
[Lists all available cfwheels-plugins from ForgeBox]
=============================================================================

JSON Format

{
  "plugins": [
    {
      "name": "wheels-vue-cli",
      "version": "1.2.0",
      "description": "Vue.js integration for Wheels"
    }
  ]
}

Notes

  • Local plugins are stored in your project

  • Global plugins are available to all projects

  • Use wheels plugins install to add new plugins

  • Use wheels plugins remove to uninstall plugins

  • The --available flag queries the ForgeBox registry

wheels plugins remove

Removes an installed Wheels CLI plugin.

Usage

wheels plugins remove <name> [--global] [--force]

Parameters

  • name - (Required) Plugin name to remove

  • --global - (Optional) Remove globally installed plugin

  • --force - (Optional) Force removal without confirmation

Description

The plugins remove command safely uninstalls a plugin from your Wheels application. It:

  • Checks for dependent plugins

  • Creates a backup (by default)

  • Removes plugin files

  • Cleans up configuration

  • Updates plugin registry

Examples

Basic plugin removal

wheels plugins remove wheels-vue-cli

Remove global plugin

wheels plugins remove wheels-docker --global

Force removal (skip confirmation)

wheels plugins remove wheels-testing --force

Remove global plugin without confirmation

wheels plugins remove wheels-cli-tools --global --force

Removal Process

  1. Dependency Check: Ensures no other plugins depend on this one

  2. Backup Creation: Saves plugin files to backup directory

  3. Deactivation: Disables plugin in application

  4. File Removal: Deletes plugin files and directories

  5. Cleanup: Removes configuration entries

  6. Verification: Confirms successful removal

Output

With confirmation prompt (default)

Are you sure you want to remove the plugin 'wheels-vue-cli'? (y/n): y
🗑️  Removing plugin: wheels-vue-cli...

✅ Plugin removed successfully

With force flag

🗑️  Removing plugin: wheels-vue-cli...

✅ Plugin removed successfully

Cancellation

Are you sure you want to remove the plugin 'wheels-vue-cli'? (y/n): n
Plugin removal cancelled.

Notes

  • The --force flag skips the confirmation prompt

  • Use --global to remove plugins installed globally

  • Use wheels plugins list to verify removal

  • Some plugins may require manual cleanup of configuration files

  • Restart your application after removing plugins that affect core functionality

Tutorial: Wheels, AJAX, and You

Using Wheels to develop web applications with AJAX features is a breeze. You have several options and tools at your disposal, which we'll cover in this chapter.

Wheels was designed to be as lightweight as possible, so this keeps your options fairly open for developing AJAX features into your application.

While there are several flavors of JavaScript libraries out there with AJAX support, we will be using the in this tutorial. Let's assume that you are fairly familiar with the basics of jQuery and know how to set it up.

For this tutorial, let's create the simplest example of all: a link that will render a message back to the user without refreshing the page.

A Simple Example

In this example, we'll wire up some simple JavaScript code that calls a Wheels action asynchronously. All of this will be done with basic jQuery code and built-in Wheels functionality.

First, let's make sure we've got an appropriate route setup. It might be you're still using the default wildcard() route which will create some default GET routes for the controller/action pattern, but we'll add a new route here just for practice. We are going to create a route named sayHello and direct it to the hello action of the say controller. There are two ways you could write this code a long hand method specifying the controller and action separately as well as a short hand method that combines the two into a single parameter.

The longhand way would look like:

The shorthand method would look like:

You can decide which method you prefer. Both sets of code above are equivalent.

Then, let's create a link to a controller's action in a view file, like so:

That piece of code by itself will work just like you expect it to. When you click the link, you will load the hello action inside the say controller.

But let's make it into an asynchronous request. Add this JavaScript (either on the page inside script tags or in a separate .js file included via ):

With that code, we are listening to the click event of the hyperlink, which will make an asynchronous request to the hello action in the say controller. Additionally, the JavaScript call is passing a URL parameter called format set to json.

Note that the success block inserts keys from the response into the empty h1 and p blocks in the calling view. (You may have been wondering about those when you saw the first example. Mystery solved.)

The last thing that we need to do is implement the say/hello action. Note that the request expects a dataType of JSON. By default, Wheels controllers only generate HTML responses, but there is an easy way to generate JSON instead using Wheels's and functions:

In this controller's config() method, we use the function to indicate that we want all actions in the controller to be able to respond with the data in HTML or JSON formats. Note that the client calling the action can request the type by passing a URL parameter named format or by sending the format in the request header.

The call to in the hello action takes care of the translation to the requested format. Our JavaScript is requesting JSON, so Wheels will format the greeting struct as JSON automatically and send it back to the client. If the client requested HTML or the default of none, Wheels will process and serve the view template at app/views/say/hello.cfm. For more information about and , reference the chapter on .

Lastly, notice the lines where we're setting greeting["message"] and greeting["time"]. Due to the case-insensitive nature of ColdFusion, we recommend setting variables to be consumed by JavaScript using bracket notation like that. If you do not use that notation (i.e., greetings.message and greetings.time instead), your JavaScript will need to reference those keys from the JSON as MESSAGE and TIME (all caps). Unless you like turning caps lock on and off, you can see how that would get annoying after some time.

Assuming you already included jQuery in your application and you followed the code examples above, you now have a simple AJAX-powered web application built on Wheels. After clicking that Alert me! link, your say controller will respond back to you the serialized message via AJAX. jQuery will parse the JSON object and populate the h1 and pwith the appropriate data.

AJAX in Wheels Explained

That is it! Hopefully now you have a clearer picture on how to create AJAX-based features for your web applications.

wheels reload

Reload the Wheels application in different modes.

Synopsis

Description

The wheels reload command reloads your Wheels application, clearing caches and reinitializing the framework. This is useful during development when you've made changes to configuration, routes, or framework settings. Note: the server must be running for this command to work.

Arguments

Argument
Description
Default

Options

Option
Description

Reload Modes

Development Mode

  • Enables debugging

  • Shows detailed error messages

  • Disables caching

  • Ideal for active development

Testing Mode

  • Optimized for running tests

  • Consistent environment

  • Predictable caching

Maintenance Mode

  • Shows maintenance page to users

  • Allows admin access

  • Useful for deployments

Production Mode

  • Full caching enabled

  • Minimal error information

  • Optimized performance

Examples

Basic reload (development mode)

Reload in production mode

Using the alias

Reload for testing

Security

  • The reload password must match the one configured in your Wheels application

  • Password is sent via URL parameter to the running application

  • Always use a strong password in production environments

Configuration

Set the reload password in your Wheels settings.cfm:

Notes

  • Reload clears all application caches

  • Session data may be lost during reload

  • Database connections are refreshed

  • All singletons are recreated

  • The server must be running for this command to work

Common Issues

  • Invalid password: Check password in settings.cfm

  • Server not running: Start server with box server start

  • Connection refused: Ensure server is accessible on expected port

  • Timeout: Large applications may take time to reload

See Also

  • - Initialize application configuration

  • - Auto-reload on file changes

  • - Display application information

wheels db setup

Setup a complete database by creating it, running migrations, and seeding data.

Synopsis

Description

The wheels db setup command performs a complete database initialization in one command. It executes three operations in sequence:

  1. Creates the database (wheels db create)

  2. Runs all migrations (wheels dbmigrate latest)

  3. Seeds the database with sample data (wheels db seed)

This is ideal for setting up a new development environment or initializing a test database.

Options

--datasource=

Specify which datasource to use. If not provided, uses the default datasource from your Wheels configuration.

--environment=

Specify the environment to use. Defaults to the current environment.

--skip-seed

Skip the database seeding step.

--seed-count=

Number of records to generate per model when seeding. Defaults to 5.

Examples

Basic Usage

Full setup with default options:

Setup Without Sample Data

Create and migrate only:

Setup Test Database

Production Setup

What It Does

The command executes these steps in order:

  1. Create Database

    • Creates new database if it doesn't exist

    • Uses datasource configuration for connection details

  2. Run Migrations

    • Executes all pending migrations

    • Creates schema from migration files

  3. Seed Database (unless --skip-seed)

    • Generates sample data for testing

    • Creates specified number of records per model

Error Handling

If any step fails:

  • The command stops execution

  • Shows which step failed

  • Provides instructions for manual recovery

Common Use Cases

New Developer Setup

Reset Development Database

Continuous Integration

Best Practices

  1. Use for development: Perfect for getting started quickly

  2. Skip seeding in production: Use --skip-seed for production

  3. Customize seed count: More data for performance testing

  4. Check migrations first: Ensure migrations are up to date

Related Commands

  • - Just create database

  • - Drop and recreate everything

  • - Just run migrations

  • - Just seed data

wheels db version

Show the current database schema version.

Synopsis

Description

The wheels db version command displays the current version of your database schema based on the last applied migration. This is useful for quickly checking which migration version your database is at.

Options

--detailed

Show additional information about the database state.

Examples

Basic Usage

Show current version:

Output:

Detailed Information

Output:

Understanding Versions

Version Format

Migrations use timestamp-based versions:

  • Format: YYYYMMDDHHMMSS

  • Example: 20231203160000 = December 3, 2023 at 4:00:00 PM

No Version

If you see "No migrations have been applied yet":

  • Database is fresh with no migrations run

  • Run wheels dbmigrate latest to apply migrations

Common Use Cases

Quick Check

Before running migrations:

Deployment Verification

Troubleshooting

Compare versions across environments:

Related Information

The version corresponds to:

  • The latest migration file that has been applied

  • An entry in the migration tracking table

  • The current schema state of your database

Related Commands

  • - Show all migrations and their status

  • - Update to latest version

  • - Rollback to previous version

  • - Detailed migration information

wheels db status

Show the status of database migrations.

Synopsis

Description

The wheels db status command displays information about the current state of database migrations, showing which migrations have been applied and which are pending.

Options

--format=

Output format. Options are table (default) or json.

--pending

Show only pending migrations.

Examples

Basic Usage

Show all migrations in table format:

Output:

Show Only Pending

JSON Output

Output:

Understanding the Output

Table Format

  • Version: Migration timestamp/version number

  • Description: Human-readable migration name

  • Status: Either "applied" or "pending"

  • Applied At: When the migration was run

Status Colors

  • Green: Applied migrations

  • Yellow: Pending migrations

Summary Section

Shows counts of:

  • Total migrations in the migrations folder

  • Applied migrations in the database

  • Pending migrations to be run

Common Scenarios

Check Before Deployment

Verify Migration Applied

CI/CD Integration

Troubleshooting

"No migrations found"

  • Check that migration files exist in /db/migrate/ directory

  • Ensure file naming follows pattern: YYYYMMDDHHMMSS_Description.cfc

Version Mismatch

If the database version doesn't match expected:

  • Check migration history in database

  • Verify no migrations were manually deleted

  • Consider running wheels dbmigrate latest

Related Commands

  • - Show just the current version

  • - Apply pending migrations

  • - Rollback migrations

  • - Similar migration information

wheels dbmigrate down

Rollback the last executed database migration.

Synopsis

Alias: wheels db down

Description

The dbmigrate down command reverses the last executed migration by running its down() method. This is useful for undoing database changes when issues are discovered or when you need to modify a migration. The command ensures safe rollback of schema changes while maintaining database integrity.

Parameters

None.

Examples

Rollback the last migration

This will execute the down() method of the most recently applied migration, reverting the database changes.

Use Cases

Fixing Migration Errors

When a migration contains errors or needs modification:

Development Iteration

During development when refining migrations:

Emergency Rollback

When a migration causes issues:

Important Considerations

Data Loss Warning

Rolling back migrations that drop columns or tables will result in data loss. Always ensure you have backups before rolling back destructive migrations.

Down Method Requirements

For a migration to be rolled back, it must have a properly implemented down() method that reverses the changes made in the up() method.

Migration Dependencies

Be cautious when rolling back migrations that other migrations depend on. This can break the migration chain.

Best Practices

  1. Always implement down() methods: Even if you think you'll never need to rollback

  2. Test rollbacks: In development, always test that your down() method works correctly

  3. Backup before rollback: Especially in production environments

  4. Document destructive operations: Clearly indicate when rollbacks will cause data loss

Notes

  • Only the last executed migration can be rolled back with this command

  • To rollback multiple migrations, run the command multiple times

  • If already at version 0, displays: "We're already on zero! No migrations to go to"

  • Automatically runs dbmigrate info after successful rollback

  • The migration version is removed from the database tracking table upon successful rollback

  • Some operations (like dropping columns with data) cannot be fully reversed

  • When migrating to version 0, displays: "Database should now be empty."

Related Commands

  • - Run the next migration

  • - Reset all migrations

  • - View migration status

  • - Run a specific migration

wheels dbmigrate exec

Execute a specific database migration by version number.

Synopsis

Alias: wheels db exec

Description

The dbmigrate exec command allows you to migrate to a specific version identified by its version number, regardless of the current migration state. This is useful for moving to any specific point in your migration history.

Parameters

Parameter
Type
Required
Description

Examples

Execute a specific migration

Migrate to version 0 (revert all migrations)

Use Cases

Migrating to a Specific Version

Move to any point in migration history:

Rolling Back to Previous Version

Move to an earlier migration state:

Reset Database

Clear all migrations:

Important Considerations

Migration Order

Executing migrations out of order can cause issues if migrations have dependencies. Always ensure that any required preceding migrations have been run.

Version Tracking

The command updates the migration tracking table to reflect the execution status.

Best Practices

  1. Check Dependencies: Ensure required migrations are already applied

  2. Test First: Run in development/testing before production

  3. Use Sparingly: Prefer normal migration flow with up/latest

  4. Document Usage: Record when and why specific executions were done

  5. Verify State: Check migration status before and after execution

Version Number Format

Migration versions are typically timestamps in the format:

  • YYYYMMDDHHmmss (e.g., 20240115123456)

  • Year: 2024

  • Month: 01

  • Day: 15

  • Hour: 12

  • Minute: 34

  • Second: 56

Notes

  • The command will migrate UP or DOWN to reach the specified version

  • Version must be a valid migration version or 0 to reset all

  • The migration file must exist in the migrations directory

  • The command displays the migration progress message

  • Both up() and down() methods should be defined in the migration

Related Commands

  • - Run the next migration

  • - Rollback last migration

  • - Run all pending migrations

  • - View migration status

  • - Create a new migration

wheels config list

List all configuration settings for your Wheels application.

Usage

Parameters

  • --environment - (Optional) Environment to display settings for: development, testing, production

  • --filter - (Optional) Filter results by this string

  • --show-sensitive - (Optional) Show sensitive information (passwords, keys, etc.)

Description

The wheels config list command displays all configuration settings for your Wheels application. It shows current values and helps you understand your application's configuration state.

Examples

List all settings

Filter by pattern

Show sensitive values

Environment-specific settings

Combined options

Output Example

When using --show-sensitive, password values are displayed:

Common Configuration Settings

The command displays all Wheels configuration settings including:

  • Database: dataSourceName, dataSourceUserName, dataSourcePassword

  • Cache: cacheQueries, cacheActions, cachePages, cachePartials

  • Security: reloadPassword, showDebugInformation, showErrorInformation

  • URLs/Routing: urlRewriting, assetQueryString, assetPaths

  • Environment: environment, hostName

Filtering

Use the --filter parameter to search for specific settings:

Security

By default, sensitive values like passwords are masked with asterisks (********). Use --show-sensitive to display actual values:

Environment-Specific Settings

View settings for different environments:

Notes

  • Some settings require restart to take effect

  • Sensitive values are automatically hidden

  • Custom settings from plugins included

  • Performance impact minimal

See Also

  • - Set configuration values

  • - Environment configuration

  • - Environment management

wheels server start

Start the Wheels development server with enhanced checks and features.

Synopsis

Description

The wheels server start command starts a CommandBox server with Wheels-specific enhancements. It checks that you're in a valid Wheels application directory before starting and provides helpful error messages if not.

This command wraps CommandBox's native server start functionality while adding:

  • Validation that the current directory is a Wheels application

  • Automatic detection of existing running servers

  • Wheels-specific configuration suggestions

  • Integration with Wheels application context

Options

port

  • Type: Numeric

  • Description: Port number to start server on

  • Example: wheels server start port=8080

host

  • Type: String

  • Default: 127.0.0.1

  • Description: Host/IP address to bind server to

  • Example: wheels server start host=0.0.0.0

--rewritesEnable

  • Type: Boolean flag

  • Description: Enable URL rewriting for clean URLs

  • Example: wheels server start --rewritesEnable

openbrowser

  • Type: Boolean

  • Default: true

  • Description: Open browser after starting server

  • Example: wheels server start openbrowser=false

directory

  • Type: String

  • Default: Current working directory

  • Description: Directory to serve

  • Example: wheels server start directory=/path/to/app

name

  • Type: String

  • Description: Name for the server instance

  • Example: wheels server start name=myapp

--force

  • Type: Boolean flag

  • Description: Force start even if server is already running

  • Example: wheels server start --force

Examples

Basic Usage

Advanced Usage

Notes

  • The command validates that the current directory contains a Wheels application by checking for /vendor/wheels, /config, and /app directories

  • If a server is already running, use --force to start anyway or wheels server restart to restart

  • After starting, the command displays helpful information about other server commands

  • The server configuration can also be managed through server.json file

Related Commands

  • - Stop the server

  • - Restart the server

  • - Check server status

  • - View server logs

  • - Open in browser

See Also

wheels server log

Tail the Wheels development server logs.

Synopsis

Description

The wheels server log command displays and follows the server log output, making it easy to monitor your application's behavior and debug issues.

Options

name

  • Type: String

  • Description: Name of the server whose logs to display

  • Example: wheels server log name=myapp

--follow

  • Type: Boolean flag

  • Default: true

  • Description: Follow log output (like tail -f)

  • Example: wheels server log --follow

lines

  • Type: Numeric

  • Default: 50

  • Description: Number of lines to show initially

  • Example: wheels server log lines=100

--debug

  • Type: Boolean flag

  • Description: Show debug-level logging

  • Example: wheels server log --debug

Examples

Log Information

The logs typically include:

  • HTTP request/response information

  • Application errors and stack traces

  • Database queries (if enabled)

  • Custom application logging

  • Server startup/shutdown messages

Keyboard Shortcuts

  • Ctrl+C - Stop following logs and return to command prompt

  • Ctrl+L - Clear the screen (while following)

Notes

  • By default, the command follows log output (similar to tail -f)

  • Use --debug to see more detailed logging information

  • The number of initial lines shown can be customized with the lines parameter

  • Logs are stored in the CommandBox server's log directory

Related Commands

  • - Start the server

  • - Check server status

  • - Debug mode for tests

See Also

wheels plugins install

Installs a Wheels CLI plugin from various sources including ForgeBox, GitHub, or local files.

Usage

Parameters

  • name - (Required) Plugin name or repository URL

  • --dev - (Optional) Install as development dependency

  • --global - (Optional) Install globally

  • --version - (Optional) Specific version to install

Description

The plugins install command downloads and installs Wheels plugins into your application. It supports multiple installation sources:

  • ForgeBox Registry: Official and community plugins

  • GitHub Repositories: Direct installation from GitHub

  • Local Files: ZIP files or directories

  • URL Downloads: Direct ZIP file URLs

The command automatically:

  • Checks plugin compatibility

  • Resolves dependencies

  • Backs up existing plugins

  • Runs installation scripts

Examples

Install from ForgeBox

Install specific version

Install from GitHub

Install as development dependency

Install globally

Install with multiple options

Installation Process

  1. Download: Fetches plugin from specified source

  2. Validation: Checks compatibility and requirements

  3. Backup: Creates backup of existing plugin (if any)

  4. Installation: Extracts files to plugins directory

  5. Dependencies: Installs required dependencies

  6. Initialization: Runs plugin setup scripts

  7. Verification: Confirms successful installation

Output

If installation fails:

Plugin Sources

ForgeBox

GitHub

Direct URL

Notes

  • Plugins must be compatible with your Wheels version

  • Always backup your application before installing plugins

  • Some plugins require manual configuration after installation

  • Use wheels plugins list to verify installation

  • Restart your application to activate new plugins

wheels analyze code

Analyzes code quality in your Wheels application, checking for best practices, potential issues, and code standards compliance.

Usage

Parameters

  • path - (Optional) Path to analyze. Default: current directory (.)

  • --fix - (Optional) Attempt to fix issues automatically

  • --format - (Optional) Output format: console, json, junit. Default: console

  • --severity - (Optional) Minimum severity level: info, warning, error. Default: warning

  • --report - (Optional) Generate HTML report

Description

The analyze code command performs comprehensive code quality analysis on your Wheels application. It checks for:

  • Code complexity and maintainability

  • Adherence to Wheels coding standards

  • Potential bugs and code smells

  • Duplicate code detection

  • Function length and complexity metrics

  • Variable naming conventions

  • Deprecated function usage

Examples

Basic code analysis

Analyze specific directory

Auto-fix issues

Generate HTML report

Analyze with JSON output for CI/CD

Check only errors (skip warnings)

Analyze and fix specific path with report

Output

The command provides detailed feedback including:

  • Complexity Score: Cyclomatic complexity for functions

  • Code Standards: Violations of Wheels conventions

  • Duplicate Code: Similar code blocks that could be refactored

  • Suggestions: Recommendations for improvement

  • Metrics Summary: Overall code health indicators

Notes

  • Large codebases may take several minutes to analyze

  • The --fix flag will automatically fix issues where possible

  • HTML reports are saved to the reports/ directory with timestamps

  • Integration with CI/CD pipelines is supported via JSON and JUnit output formats

  • Use .wheelscheck config file for custom rules and configurations

wheels analyze performance

Analyzes application performance, identifying bottlenecks and optimization opportunities in your Wheels application.

Usage

Parameters

  • --target - (Optional) Analysis target: all, controller, view, query, memory. Default: all

  • --duration - (Optional) Duration to run analysis in seconds. Default: 30

  • --report - (Optional) Generate HTML performance report

  • --threshold - (Optional) Performance threshold in milliseconds. Default: 100

  • --profile - (Optional) Enable profiling mode

Description

The analyze performance command profiles your Wheels application to identify performance bottlenecks and provide optimization recommendations. It monitors:

  • Request execution times

  • Database query performance

  • Memory usage patterns

  • Cache effectiveness

  • View rendering times

  • Component instantiation overhead

Examples

Basic performance analysis

Analyze for 60 seconds with profiling

Focus on database queries only

Show only slow operations (>500ms)

Generate HTML performance report

Complete analysis with all options

Output

The analysis provides:

  • Slowest Requests: Top 10 slowest request paths

  • Query Analysis: Slow queries and N+1 query detection

  • Memory Hotspots: Areas of high memory allocation

  • Cache Statistics: Hit/miss ratios for various caches

  • Recommendations: Specific optimization suggestions

Sample Output

Notes

  • Profiling adds minimal overhead to your application

  • Best run in a staging environment with production-like data

  • Can be integrated with APM tools for continuous monitoring

  • Results are aggregated across all application instances

Screencasts

Tutorials, demonstrations, and presentations about the ColdFusion on Wheels framework.

Wheels 2.x

Create a basic CRUD interface in Wheels 2.x

Create a basic JSON API in Wheels 2.x

Routing in Wheels 2.x - Part 1

Routing in Wheels 2.x - Part 2

Introduction to Unit Testing in Wheels 2.x

Unit Testing Controllers in Wheels 2.x

Wheels 1.x

Please note that all the webcasts below were created with Wheels 1.x in mind, and are listed here as they might still be useful to those starting out.

CRUD series

Learn about basic create operations when building standard CRUD functionality in Wheels

Learn about basic read operations when building standard CRUD functionality in Wheels

Chris Peters demonstrates updating data in a simple CRUD Wheels application

Learn how simple it is to delete records in a basic CRUD application using Wheels

"Building a Social Network"

Chris Peters starts the webcast series by demonstrating how to set up ColdFusion on Wheels

Chris Peters demonstrates how to bind a Wheels model object to a form through the use of form helpers

Chris Peters adds data validation to the user registration form

Chris Peters finishes the "success" portion of the registration functionality by adding a success message to the Flash and redirecting the user to their home screen

Chris Peters teaches you about more validation options and how you can add them to the registration form quickly and easily

Chris Peters stylizes form markup globally using a Wheels feature called global helpers

Learn how to set up simple user authentication on a website by using a Wheels feature called filters

Learn the mechanics of reading a single record from the database and displaying its data in the view

Creating custom URL patterns is a breeze in ColdFusion on Wheels

Learn how to fetch multiple records from your model with findAll() and then display them to the user using ColdFusion on Wheels

Learn how to factor out logic in your view templates into custom helper functions in ColdFusion on Wheels

Chris Peters demonstrates joining data together with model associations using ColdFusion on Wheels

All it takes to offer pagination is two extra arguments to findAll() and a call to a view helper called paginationLinks()

Learn how to use the provides() and renderWith() functions to automatically serialize data into XML, JSON, and more

Other

Peter Amiri walks you through setting up a "Hello World" application using the ColdFusion on Wheels framework

Chris Peters gives a high level overview of the ORM included with ColdFusion on Wheels

Chris Peters from Liquifusion demonstrates the ColdRoute plugin for Wheels

Doug Boude demonstrates using his new Wirebox plugin for Wheels

Chris Peters from Liquifusion demonstrates creating tables and records using database migrations in ColdFusion on Wheels

Online ColdFusion Meetup (coldfusionmeetup.com) session for March 10 2011, "What's New in Wheels 1.1", with Chris Peters:

A quick demo of the Wheels Textmate bundle by Russ Johnson

wheels plugins install <name> [--dev] [--global] [--version=<version>]
wheels plugins install wheels-vue-cli
wheels plugins install wheels-docker --version=2.0.0
wheels plugins install https://github.com/user/wheels-plugin
wheels plugins install wheels-docker --dev
wheels plugins install wheels-cli-tools --global
wheels plugins install wheels-testing --dev --version=1.5.0
📦 Installing plugin: wheels-vue-cli...

✅ Plugin installed successfully
📦 Installing plugin: invalid-plugin...

❌ Plugin installation failed
Error: Plugin not found in repository
# Install by name (searches ForgeBox)
wheels plugins install plugin-name

# Install specific ForgeBox ID
wheels plugins install forgebox:plugin-slug
# HTTPS URL
wheels plugins install https://github.com/user/repo

# GitHub shorthand
wheels plugins install github:user/repo

# Specific branch/tag
wheels plugins install github:user/repo#v2.0.0
wheels plugins install https://example.com/plugin.zip
wheels analyze code [path] [--fix] [--format=<format>] [--severity=<severity>] [--report]
wheels analyze code
wheels analyze code app/controllers
wheels analyze code --fix
wheels analyze code --report
wheels analyze code --format=json
wheels analyze code --severity=error
wheels analyze code app/models --fix --report
wheels analyze performance [--target=<target>] [--duration=<seconds>] [--report] [--threshold=<ms>] [--profile]
wheels analyze performance
wheels analyze performance --duration=60 --profile
wheels analyze performance --target=query
wheels analyze performance --threshold=500
wheels analyze performance --report
wheels analyze performance --target=all --duration=60 --threshold=200 --profile --report
Performance Analysis Results
===========================

Slowest Requests:
1. GET /users/search (avg: 850ms, calls: 45)
2. POST /orders/create (avg: 650ms, calls: 12)
3. GET /reports/generate (avg: 1200ms, calls: 8)

Database Issues:
- N+1 queries detected in UsersController.index
- Slow query in Order.findRecent() - 450ms avg
- Missing index suggested for users.created_at

Memory Usage:
- High allocation in ReportService.generate()
- Potential memory leak in SessionManager

Recommendations:
1. Add eager loading to UsersController.index
2. Create index on users.created_at
3. Implement query result caching for Order.findRecent()
/config/routes.cfm
mapper()
  .get(name="sayHello", controller="say", action="hello")
.end()
/config/routes.cfm
mapper()
  .get(name="sayHello", to="say##hello")
.end()
app/views/say/hello.cfm
<cfoutput>

<!--- View code --->
<h1></h1>
<p></p>

#linkTo(text="Alert me!", route="sayHello", id="alert-button")#

</cfoutput>
JavaScript
(function($) {
    // Listen to the "click" event of the "alert-button" link and make an AJAX request
    $("#alert-button").on("click", function(event) {
        $.ajax({
            type: "GET",
            // References "/say/hello?format=json"
            url: $(this).attr("href") + "?format=json",
            dataType: "json",
            success: function(response) {
                $("h1").html(response.message);
                $("p").html(response.time);
            }
        });

        event.preventDefault();
    });
})(jQuery);
app/controllers/Say.cfc
component extends="Controller" {
    function config() {
        provides("html,json");
    }

    function hello() {
        // Prepare the message for the user.
        greeting = {};
        greeting["message"] = "Hi there";
        greeting["time"] = Now();

        // Respond to all requests with `renderWith()`.
        renderWith(greeting);
    }
}
jQuery framework
javaScriptIncludeTag()
provides()
renderWith()
provides()
renderWith()
provides()
renderWith()
Responding with Multiple Formats
wheels reload [options]
wheels r [options]

mode

Reload mode: development, testing, maintenance, production

development

password

Required - The reload password configured in your application

None

--help

Show help information

wheels reload password=mypassword
wheels reload mode=testing password=mypassword
wheels reload mode=maintenance password=mypassword
wheels reload mode=production password=mypassword
wheels reload password=wheels
wheels reload mode=production password=mySecretPassword
wheels r password=wheels
wheels reload mode=testing password=wheels
set(reloadPassword="mySecretPassword");
wheels init
wheels watch
wheels info
wheels db setup [--datasource=<name>] [--environment=<env>] [--skip-seed] [--seed-count=<n>]
wheels db setup --datasource=myapp_dev
wheels db setup --environment=testing
wheels db setup --skip-seed
wheels db setup --seed-count=20
wheels db setup
wheels db setup --skip-seed
wheels db setup --datasource=myapp_test --environment=testing --seed-count=10
wheels db setup --environment=production --skip-seed
git clone https://github.com/myproject/repo.git
cd repo
box install
wheels db setup
server start
wheels db drop --force
wheels db setup --seed-count=50
# In CI script
wheels db setup --environment=testing --skip-seed
wheels test run
wheels db create
wheels db reset
wheels dbmigrate latest
wheels db seed
wheels db version [--detailed]
wheels db version --detailed
wheels db version
Current database version: 20231203160000
wheels db version --detailed
Current database version: 20231203160000

Last migration:
  Version: 20231203160000
  Description: CreatePostsTable
  Applied at: 2023-12-03 16:45:32

Total migrations: 15
Pending migrations: 2

Next migration to apply:
  Version: 20231204180000
  Description: AddIndexToPostsUserId

Environment: development
Datasource: myapp_dev
wheels db version
wheels dbmigrate latest
# Check production is up to date
wheels db version --environment=production --detailed
# Development
wheels db version --environment=development

# Staging
wheels db version --environment=staging

# Production
wheels db version --environment=production
wheels db status
wheels dbmigrate latest
wheels db rollback
wheels dbmigrate info
wheels db status [--format=<format>] [--pending]
wheels db status --format=json
wheels db status --pending
wheels db status
Current database version: 20231203160000

| Version              | Description                      | Status   | Applied At         |
|--------------------|----------------------------------|----------|-------------------|
| 20231201120000     | CreateUsersTable                 | applied  | 2023-12-01 12:30  |
| 20231202140000     | AddEmailToUsers                  | applied  | 2023-12-02 14:15  |
| 20231203160000     | CreatePostsTable                 | applied  | 2023-12-03 16:45  |
| 20231204180000     | AddIndexToPostsUserId            | pending  | Not applied       |

Total migrations: 4
Applied: 3
Pending: 1
wheels db status --pending
wheels db status --format=json
{
  "success": true,
  "currentVersion": "20231203160000",
  "migrations": [
    {
      "version": "20231201120000",
      "description": "CreateUsersTable",
      "status": "applied",
      "appliedAt": "2023-12-01 12:30:00"
    },
    {
      "version": "20231204180000",
      "description": "AddIndexToPostsUserId",
      "status": "pending",
      "appliedAt": null
    }
  ],
  "summary": {
    "total": 4,
    "applied": 3,
    "pending": 1
  }
}
# See what migrations will run in production
wheels db status --environment=production --pending
# Check if specific migration was applied
wheels db status | grep "AddEmailToUsers"
# Get pending count for automation
wheels db status --format=json | jq '.summary.pending'
wheels db version
wheels dbmigrate latest
wheels db rollback
wheels dbmigrate info
wheels dbmigrate down
wheels dbmigrate down
# Run the migration
wheels dbmigrate up

# Discover an issue
# Rollback the migration
wheels dbmigrate down

# Edit the migration file
# Re-run the migration
wheels dbmigrate up
# Apply migration
wheels dbmigrate up

# Test the changes
# Need to modify? Rollback
wheels dbmigrate down

# Make changes to migration
# Apply again
wheels dbmigrate up
# Check current migration status
wheels dbmigrate info

# Rollback the problematic migration
wheels dbmigrate down

# Verify rollback
wheels dbmigrate info
wheels dbmigrate up
wheels dbmigrate reset
wheels dbmigrate info
wheels dbmigrate exec
wheels dbmigrate exec version=<version>

version

string

Yes

Version to migrate to

wheels dbmigrate exec version=20240115123456
wheels dbmigrate exec version=0
# Check current status
wheels dbmigrate info

# Migrate to specific version
wheels dbmigrate exec version=20240115123456
# Check migration history
wheels dbmigrate info

# Go back to specific version
wheels dbmigrate exec version=20240101000000
# Migrate to version 0
wheels dbmigrate exec version=0

# Verify empty state
wheels dbmigrate info
wheels dbmigrate up
wheels dbmigrate down
wheels dbmigrate latest
wheels dbmigrate info
wheels dbmigrate create blank
wheels config list [--environment=<env>] [--filter=<pattern>] [--show-sensitive]
wheels config list
wheels config list --filter=cache
wheels config list --filter=database
wheels config list --show-sensitive
wheels config list --environment=production
wheels config list --environment=testing
wheels config list --environment=production --filter=cache --show-sensitive
Wheels Configuration Settings
============================

Setting                          Value
-------------------------------- --------------------------------
dataSourceName                   wheels_dev
environment                      development
reloadPassword                   ********
showDebugInformation            true
showErrorInformation            true
cacheFileChecking               false
cacheQueries                    false
cacheActions                    false
urlRewriting                    partial
assetQueryString                true
assetPaths                      true
reloadPassword                   mySecretPassword123
# Find all cache-related settings
wheels config list --filter=cache

# Find datasource settings
wheels config list --filter=datasource
# Show all settings including passwords
wheels config list --show-sensitive
# Production settings
wheels config list --environment=production

# Testing environment
wheels config list --environment=testing
wheels config set
wheels config env
wheels env
Configuration Guide
wheels server start [options]
# Start server with defaults
wheels server start

# Start on specific port
wheels server start port=3000

# Start without opening browser
wheels server start openbrowser=false
# Start with multiple options
wheels server start port=8080 host=0.0.0.0 --rewritesEnable

# Start with custom name
wheels server start name=myapp port=8080

# Force restart if already running
wheels server start --force
wheels server stop
wheels server restart
wheels server status
wheels server log
wheels server open
CommandBox Server Documentation
Wheels Configuration Guide
wheels server log [options]
# Follow logs (default behavior)
wheels server log

# Show last 100 lines
wheels server log lines=100

# Show logs without following
wheels server log --follow=false

# Enable debug logging
wheels server log --debug
wheels server start
wheels server status
wheels test debug
CommandBox Logging Documentation
Wheels Debugging Guide
https://youtu.be/K5HLItTru1g
https://youtu.be/qZr5JzO0vo4
https://youtu.be/BnPGApAvMVQ
https://youtu.be/0CiGxJyJEIQ
https://youtu.be/XgMuzzmBQ98
https://youtu.be/cygj9WDqHjY
View all screencasts on Vimeo
Episode 1: "C" Is for "Create" - Basic CRUD
Episode 2: "R"; Is for "Read" - Basic CRUD
Episode 3: "U" Is for "Update" - Basic CRUD
Episode 4: "D" Is for Delete - Basic CRUD
Episode 1: Setting up ColdFusion on Wheels
Episode 2: Form Helpers
Episode 3: Object Validation and Showing Errors
Episode 4: Redirects and the Flash
Episode 5: Object Validation
Episode 6: Styling Forms
Episode 7: Authentication with Filters
Episode 8: Reading and Displaying a Single Record
Episode 9: Adding a Route for User Profiles
Episode 10: Displaying Sets of Records
Episode 11: Custom View Helpers
Episode 12: Joining Models with Associations
Episode 13: Pagination
Episode 14: Responding with Multiple Formats
Hello World
CFUnited 2010: Simplifying Database Code with the ColdFusion on Wheels ORM
ColdRoute Plugin
Wirebox Plugin for Wheels
Database Migrations
CF Meetup, March 10 2011
Wheels Textmate Bundle Demo

wheels server open

Open the Wheels application in a web browser.

Synopsis

wheels server open [path] [options]

Description

The wheels server open command opens your Wheels application in a web browser. It automatically detects the server URL and can open specific paths within your application.

Arguments

path

  • Type: String

  • Default: /

  • Description: URL path to open (e.g., /admin, /users)

  • Example: wheels server open /admin

Options

--browser

  • Type: String

  • Description: Specific browser to use (chrome, firefox, safari, etc.)

  • Example: wheels server open --browser=firefox

name

  • Type: String

  • Description: Name of the server to open

  • Example: wheels server open name=myapp

Examples

# Open application homepage
wheels server open

# Open admin panel
wheels server open /admin

# Open users listing
wheels server open /users

# Open in specific browser
wheels server open --browser=chrome

# Open path in Firefox
wheels server open /dashboard --browser=firefox

Supported Browsers

The --browser option supports:

  • chrome - Google Chrome

  • firefox - Mozilla Firefox

  • safari - Safari (macOS)

  • edge - Microsoft Edge

  • opera - Opera

If no browser is specified, your system's default browser is used.

Notes

  • The command first checks if the server is running before attempting to open

  • If the server isn't running, it will suggest starting it first

  • The path argument should start with / for proper URL construction

  • The browser must be installed on your system to use the --browser option

Error Messages

"Server doesn't appear to be running"

The server must be started before you can open it in a browser:

wheels server start
wheels server open

Related Commands

  • wheels server start - Start the server

  • wheels server status - Check if server is running

See Also

  • CommandBox Browser Integration

Frameworks and Wheels

Learn the goals of Wheels as well as web development frameworks in general. Then learn more about some key concepts in Wheels.

This chapter will introduce you to frameworks in general and later specifically to Wheels. We'll help you decide if you even need a framework at all and what common problems a framework tries to solve. If we're able to convince you that using a framework is the right thing for you, then we'll present our goals with creating Wheels and show you some key Wheels concepts.

So let's get started.

Do I Really Need to Use a Framework?

Short answer, no. If you don't mind doing the same thing over and over again and are getting paid by the hour to do so, then by all means keep doing that.

Slightly longer answer, no. If you're working on a highly customized project that does not fall within what 9 out of 10 web sites/applications normally do then you likely need a high percentage of custom code, and a framework will not help much.

However, if you're like most of us and have noticed that for every new project you start on--or even every new feature you add to an existing project--you waste a lot of time re-creating the wheel, then you should read on because Wheels may just be the solution for you!

Wheels will make starting a new project or building a new feature quick and painless. You can get straight to solving business problems on day one! To understand how this is achieved, we figured that a little background info on frameworks in general may help you out.

All good frameworks rise from the need to solve real problems in real world situations. Wheels is based heavily on the Rails framework for Ruby and also gets inspiration from Django and, though to a lesser extent, other frameworks in the ColdFusion space (like Fusebox, for example). Over the years the contributors to these frameworks have identified problems and tedious tasks in their own development processes, built a solution for it, and abstracted (made it more generic so it suits any project) the solution into the framework in question. Piggy-backing on what all these great programmers have already created and adding a few nice solutions of our own, Wheels stands on solid ground.

OK, so that was the high level overview of what frameworks are meant to do. But let's get a little more specific.

Framework Goals in General

Most web development frameworks set out to address some or all of these common concerns:

  • Map incoming requests to the code that handles them.

  • Separate your business logic from your presentation code.

  • Let you work at a higher level of abstraction, thus making you work faster.

  • Give you a good code organization structure to follow.

  • Encourage clean and pragmatic design.

  • Simplify saving data to a storage layer.

Like all other good frameworks, Wheels does all this. But there are some subtle differences, and certain things are more important in Wheels than in other frameworks and vice versa. Let's have a look at the specific goals with Wheels so you can see how it relates to the overall goals of frameworks in general.

Our Goals With Wheels

As we've said before, Wheels is heavily based on Ruby on Rails, but it's not a direct port, and there are some things that have been changed to better fit the CFML language. Here's a brief overview of the goals we're striving for with Wheels (most of these will be covered in greater detail in later chapters):

Simplicity

We strive for simplicity on a lot of different levels in Wheels. We'll gladly trade code beauty in the framework's internal code for simplicity for the developers who will use it. This goal to keep things simple is evident in a lot of different areas in Wheels. Here are some of the most notable ones:

  • The concept of object oriented programming is very simple and data-centric in Wheels, rather than 100% "pure" at all times.

  • By default, you'll always get a query result set back when dealing with multiple records in Wheels, simply because that is the way we're all used to outputting data.

  • Wheels encourages best practices, but it will never give you an error if you go against any of them.

  • With Wheels, you won't program yourself into a corner. If worse comes to worse, you can always drop right out of the framework and go back to old school code for a while if necessary.

  • Good old CFML code is used for everything, so there is no need to mess with XML for example.

What this means is that you don't have to be a fantastic programmer to use the framework (although it doesn't hurt). It's enough if you're an average programmer. After using Wheels for a while, you'll probably find that you've become a better programmer though!

Documentation

If you've ever downloaded a piece of open source software, then you know that most projects lack documentation. Wheels hopes to change that. We're hoping that by putting together complete, up-to-date documentation that this framework will appeal, and be usable, by everyone. Even someone who has little ColdFusion programming background, let alone experience with frameworks.

Key Wheels Concepts

Besides what is already mentioned above, there are some key concepts in Wheels that makes sense to familiarize yourself with early on. If you don't feel that these concepts are to your liking, feel free to look for a different framework or stick to using no framework at all. Too often programmers choose a framework and spend weeks trying to bend it to do what they want to do rather than follow the framework conventions.

Speaking of conventions, this brings us to the first key concept:

Convention Over Configuration

Instead of having to set up tons of configuration variables, Wheels will just assume you want to do things a certain way by using default settings. In fact, you can start programming a Wheels application without setting any configuration variables at all!

If you find yourself constantly fighting the conventions, then that is a hint that you're not yet ready for Wheels or Wheels is not ready for you.

Beautiful Code

Beautiful (for lack of a better word) code is code that you can scan through and immediately see what it's meant to do. It's code that is never repeated anywhere else. And, most of all, it's code that you'll enjoy writing and will enjoy coming back to 6 months from now.

Sometimes the Wheels structure itself encourages beautiful code (separating business logic from request handling, for example). Sometimes it's just something that comes naturally after reading documentation, viewing other Wheels applications, and talking to other Wheels developers.

Model-View-Controller (MVC)

If you've investigated frameworks in the past, then you've probably heard this terminology before. Model-View-Controller, or MVC, is a way to structure your code so that it is broken down into three easy-to-manage pieces:

  • Model: Just another name for the representation of data, usually a database table.

  • View: What the user or their browser sees and interacts with (a web page in most cases).

  • Controller: The behind-the-scenes guy that's coordinating everything.

"Uh, yeah. So what's this got to do with anything?" you may ask. MVC is how Wheels structures your code for you. As you start working with Wheels applications, you'll see that most of the code you write (database queries, forms, and data manipulation) are very nicely separated into one of these three categories.

The benefits of MVC are limitless, but one of the major ones is that you almost always know right where to go when something needs to change.

If you've added a column to the vehicles table in your database and need to give the user the ability to edit that field, all you need to change is your View. That's where the form is presented to the user for editing.

If you find yourself constantly getting a list of all the red cars in your inventory, you can add a new method to your model called getRedCars() that does all the work for you. Then when you want that list, just add a call to that method in your controller and you've got 'em!

Object Relational Mapping (ORM)

The Object Relational Mapping, or ORM, in Wheels is perhaps the one thing that could potentially speed up your development the most. An ORM handles mapping objects in memory to how they are stored in the database. It can replace a lot of your query writing with simple methods such as user.save(), blogPost.comments(order="date"), and so on. We'll talk a lot more about the ORM in Wheels in the chapters about models.

There's Your Explanation

So there you have it, a completely fair and unbiased introduction to Wheels. ;)

If you've been developing ColdFusion applications for a while, then we know this all seems hard to believe. But trust us; it works. And if you're new to ColdFusion or even web development in general, then you probably aren't aware of most of the pains that Wheels was meant to alleviate!

That's okay. You're welcome in the Wheels camp just the same.

wheels destroy

Remove generated code and files associated with a model, controller, views, and tests.

Synopsis

wheels destroy <name>
wheels d <name>

Description

The wheels destroy command removes all files and code associated with a resource that was previously generated. It's useful for cleaning up mistakes or removing features completely. This command will also drop the associated database table and remove resource routes.

Arguments

Argument
Description
Required

name

Name of the resource to destroy

Yes

Options

This command has no additional options. It always prompts for confirmation before proceeding.

What Gets Removed

When you destroy a resource, the following items are deleted:

  • Model file (/app/models/[Name].cfc)

  • Controller file (/app/controllers/[Names].cfc)

  • Views directory (/app/views/[names]/)

  • Model test file (/tests/Testbox/specs/models/[Name].cfc)

  • Controller test file (/tests/Testbox/specs/controllers/[Names].cfc)

  • View test directory (/tests/Testbox/specs/views/[names]/)

  • Resource route entry in /config/routes.cfm

  • Database table (if confirmed)

Examples

Basic destroy

wheels destroy user

This will prompt:

================================================
= Watch Out!                                   =
================================================
This will delete the associated database table 'users', and
the following files and directories:

/app/models/User.cfc
/app/controllers/Users.cfc
/app/views/users/
/tests/Testbox/specs/models/User.cfc
/tests/Testbox/specs/controllers/Users.cfc
/tests/Testbox/specs/views/users/
/config/routes.cfm
.resources("users")

Are you sure? [y/n]

Using the alias

wheels d product

Confirmation

The command always asks for confirmation and shows exactly what will be deleted:

================================================
= Watch Out!                                   =
================================================
This will delete the associated database table 'users', and
the following files and directories:

/app/models/User.cfc
/app/controllers/Users.cfc
/app/views/users/
/tests/Testbox/specs/models/User.cfc
/tests/Testbox/specs/controllers/Users.cfc
/tests/Testbox/specs/views/users/
/config/routes.cfm
.resources("users")

Are you sure? [y/n]

Safety Features

  1. Confirmation Required: Always asks for confirmation before proceeding

  2. Shows All Changes: Lists all files and directories that will be deleted

  3. Database Migration: Creates and runs a migration to drop the table

  4. Route Cleanup: Automatically removes resource routes from routes.cfm

What Gets Destroyed

  1. Files Deleted:

    • Model file

    • Controller file

    • Views directory and all view files

    • Test files (model, controller, and view tests)

  2. Database Changes:

    • Creates a migration to drop the table

    • Runs wheels dbmigrate latest to execute the migration

  3. Route Changes:

    • Removes .resources("name") from routes.cfm

    • Cleans up extra whitespace

Best Practices

  1. Commit First: Always commit your changes before destroying

  2. Review Carefully: Read the confirmation list carefully

  3. Check Dependencies: Make sure other code doesn't depend on what you're destroying

  4. Backup Database: Have a database backup before running in production

Common Workflows

Undo a generated resource

# Generated the wrong name
wheels generate resource prduct  # Oops, typo!
wheels destroy prduct            # Remove it
wheels generate resource product # Create correct one

Clean up after experimentation

# Try out a feature
wheels generate scaffold blog_post title:string content:text
# Decide you don't want it
wheels destroy blog_post

Notes

  • Cannot be undone - files are permanently deleted

  • Database table is dropped via migration

  • Resource routes are automatically removed from routes.cfm

  • Only works with resources that follow Wheels naming conventions

See Also

  • wheels generate resource - Generate resources

  • wheels generate scaffold - Generate scaffolding

  • wheels dbmigrate remove table - Remove database tables

wheels db rollback

Rollback database migrations to a previous state.

Synopsis

wheels db rollback [--steps=<n>] [--target=<version>] [--force]

Description

The wheels db rollback command reverses previously applied migrations by running their down methods. You can rollback a specific number of migrations or to a specific version.

Options

--steps=

Number of migrations to rollback. Defaults to 1.

wheels db rollback --steps=3

--target=

Rollback to a specific migration version.

wheels db rollback --target=20231201120000

--force

Skip the confirmation prompt.

wheels db rollback --force

Examples

Basic Usage

Rollback the last migration:

wheels db rollback

Rollback Multiple Migrations

Rollback the last 3 migrations:

wheels db rollback --steps=3

Rollback to Specific Version

Rollback to a specific point in time:

wheels db rollback --target=20231201120000

Force Rollback

Skip confirmation:

wheels db rollback --steps=5 --force

How It Works

  1. Identifies Migrations: Determines which migrations to rollback

  2. Confirmation: Asks for confirmation (unless --force)

  3. Executes Down Methods: Runs the down() method of each migration in reverse order

  4. Updates Version: Updates the database version tracking

Important Considerations

Data Loss Warning

Rollbacks can result in data loss if migrations:

  • Drop tables

  • Remove columns

  • Delete records

Always backup before rolling back:

wheels db dump --output=backup-before-rollback.sql
wheels db rollback --steps=3

Migration Requirements

For rollback to work, migrations must:

  • Have a properly implemented down() method

  • Be reversible (some operations can't be undone)

Example migration with down method:

component {
    function up() {
        addColumn(table="users", columnName="age", columnType="integer");
    }
    
    function down() {
        removeColumn(table="users", columnName="age");
    }
}

Common Scenarios

Development Corrections

Made a mistake in the last migration:

# Rollback
wheels db rollback

# Fix the migration file
# Edit: db/migrate/20231204180000_AddAgeToUsers.cfc

# Run it again
wheels dbmigrate latest

Feature Rollback

Remove a feature and its database changes:

# Rollback feature migrations
wheels db rollback --target=20231201120000

# Remove feature code
# Deploy

Testing Migrations

Test that migrations are reversible:

# Apply migration
wheels dbmigrate latest

# Test rollback
wheels db rollback

# Reapply
wheels dbmigrate latest

Troubleshooting

"No down method"

If you see this error:

  • The migration doesn't have a down() method

  • Add the method to make it reversible

  • Or use wheels db reset if in development

"Cannot rollback"

Some operations can't be reversed:

  • Data deletions (unless backed up in migration)

  • Complex transformations

  • External system changes

Rollback Fails

If rollback fails partway:

  1. Check error message for specific issue

  2. Fix the migration's down method

  3. Manually correct database if needed

  4. Update migration tracking table

Best Practices

  1. Always implement down methods in migrations

  2. Test rollbacks in development before production

  3. Backup before rollback in production

  4. Document irreversible changes in migrations

  5. Use transactions in complex rollbacks

Related Commands

  • wheels db status - Check current migration state

  • wheels dbmigrate down - Similar single rollback

  • wheels db reset - Full database reset

  • wheels db dump - Backup before rollback

wheels dbmigrate reset

Reset all database migrations by migrating to version 0.

Synopsis

wheels dbmigrate reset

Alias: wheels db reset

Description

The dbmigrate reset command resets your database by migrating to version 0, effectively rolling back all executed migrations. This is useful during development when you need to start fresh.

Parameters

None.

Examples

Reset all migrations

wheels dbmigrate reset

This will migrate the database to version 0, rolling back all migrations.

Use Cases

Fresh Development Database

Start with a clean slate during development:

# Reset all migrations
wheels dbmigrate reset

# Re-run all migrations
wheels dbmigrate latest

# Seed with test data
wheels db seed

Testing Migration Sequence

Verify that all migrations run correctly from scratch:

# Reset all migrations
wheels dbmigrate reset

# Run migrations one by one to test
wheels dbmigrate up
wheels dbmigrate up
# ... continue as needed

Fixing Migration Order Issues

When migrations have dependency problems:

# Reset all migrations
wheels dbmigrate reset

# Manually fix migration files
# Re-run all migrations
wheels dbmigrate latest

Continuous Integration Setup

Reset database for each test run:

# CI script
wheels dbmigrate reset
wheels dbmigrate latest
wheels test run

Important Warnings

Data Loss

WARNING: This command will result in complete data loss as it rolls back all migrations. Always ensure you have proper backups before running this command, especially in production environments.

Production Usage

Using this command in production is strongly discouraged. If you must use it in production:

  1. Take a complete database backup

  2. Put the application in maintenance mode

  3. Have a rollback plan ready

Migration Dependencies

The reset process rolls back migrations in reverse chronological order. Ensure all your down() methods are properly implemented.

Best Practices

  1. Development Only: Primarily use this command in development environments

  2. Backup First: Always backup your database before resetting

  3. Test Down Methods: Ensure all migrations have working down() methods

  4. Document Usage: If used in production, document when and why

Process Flow

  1. Displays "Resetting Database Schema"

  2. Executes dbmigrate exec version=0

  3. Automatically runs dbmigrate info to show the reset status

Notes

  • The command will fail if any migration's down() method fails

  • Migration files must still exist for rollback to work

  • The migration tracking table itself is preserved

  • Use wheels dbmigrate info after reset to verify status

Related Commands

  • wheels dbmigrate up - Run the next migration

  • wheels dbmigrate down - Rollback last migration

  • wheels dbmigrate latest - Run all pending migrations

  • wheels dbmigrate info - View migration status

  • wheels db seed - Seed the database with data

Configuration Commands

wheels dbmigrate info

Display database migration status and information.

Synopsis

Alias: wheels db info

Description

The wheels dbmigrate info command shows the current state of database migrations, including which migrations have been run, which are pending, and the current database version.

Parameters

None.

Output

The command displays:

  1. Datasource: The database connection being used

  2. Database Type: The type of database (MySQL, PostgreSQL, etc.)

  3. Total Migrations: Count of all migration files found

  4. Available Migrations: Number of pending migrations

  5. Current Version: The latest migration that has been run

  6. Latest Version: The newest migration available

  7. Migration List: All migrations with their status (migrated or pending)

Example Output

Migration Files Location

Migrations are stored in /app/migrator/migrations/ and follow the naming convention:

Example:

Understanding Version Numbers

  • Version numbers are timestamps in format: YYYYMMDDHHmmss

  • Higher numbers are newer migrations

  • Migrations run in chronological order

Database Schema Table

Migration status is tracked in schema_migrations table:

Use Cases

  1. Check before deployment

  2. Verify after migration

  3. Troubleshoot issues

    • See which migrations have run

    • Identify pending migrations

    • Confirm database version

Common Scenarios

All Migrations Complete

Fresh Database

Partial Migration

Troubleshooting

Migration Not Showing

  • Check file is in /app/migrator/migrations/

  • Verify .cfc extension

  • Ensure proper timestamp format

Version Mismatch

  • Check schema_migrations table

  • Verify migration files haven't been renamed

  • Look for duplicate timestamps

Connection Issues

  • Verify datasource configuration

  • Check database credentials

  • Ensure database server is running

Integration with CI/CD

Use in deployment scripts:

Best Practices

  1. Always check info before running migrations

  2. Review pending migrations before deployment

  3. Keep migration files in version control

  4. Don't modify completed migration files

  5. Use info to verify production deployments

See Also

  • - Run all pending migrations

  • - Run next migration

  • - Rollback migration

  • - Create new migration

wheels dbmigrate info
+-----------------------------------------+-----------------------------------------+
| Datasource: myapp_development           | Total Migrations: 6                     |
| Database Type: MySQL                    | Available Migrations: 2                 |
|                                         | Current Version: 20240115120000         |
|                                         | Latest Version: 20240125160000          |
+-----------------------------------------+-----------------------------------------+
+----------+------------------------------------------------------------------------+
| migrated | 20240101100000_create_users_table.cfc                                 |
| migrated | 20240105150000_create_products_table.cfc                              |
| migrated | 20240110090000_add_email_to_users.cfc                                 |
| migrated | 20240115120000_create_orders_table.cfc                                |
|          | 20240120140000_add_status_to_orders.cfc                               |
|          | 20240125160000_create_categories_table.cfc                            |
+----------+------------------------------------------------------------------------+
[timestamp]_[description].cfc
20240125160000_create_users_table.cfc
SELECT * FROM schema_migrations;
+----------------+
| version        |
+----------------+
| 20240101100000 |
| 20240105150000 |
| 20240110090000 |
| 20240115120000 |
+----------------+
wheels dbmigrate info
wheels dbmigrate latest
wheels dbmigrate info
Current Version: 20240125160000
Status: 6 completed, 0 pending
✓ Database is up to date
Current Version: 0
Status: 0 completed, 6 pending
⚠ No migrations have been run
Current Version: 20240110090000
Status: 3 completed, 3 pending
⚠ Database needs migration
#!/bin/bash
# Check migration status
wheels dbmigrate info

# Run if needed
if [[ $(wheels dbmigrate info | grep "pending") ]]; then
    echo "Running pending migrations..."
    wheels dbmigrate latest
fi
wheels dbmigrate latest
wheels dbmigrate up
wheels dbmigrate down
wheels dbmigrate create blank

wheels deps

Manage application dependencies using box.json.

Synopsis

wheels deps <action> [name] [options]

Description

The wheels deps command provides a streamlined interface for managing your Wheels application's dependencies through box.json. It integrates with CommandBox's package management system while providing Wheels-specific conveniences.

Arguments

Argument
Description
Default

action

Required - Action to perform: list, install, update, remove, report

None

name

Package name (required for install/update/remove actions)

None

version

Specific version to install (optional, for install action only)

Latest version

Options

Option
Description
Default

--dev

Install as development dependency (install action only)

false

Actions

List

Display all dependencies from box.json with their installation status.

wheels deps list

Output shows:

  • Package name

  • Version specification

  • Type (Production/Development)

  • Installation status

Example output:

Dependencies:
  wheels-core @ ^3.0.0 (Production) - Installed
  wirebox @ ^7 (Production) - Installed
  testbox @ ^5 (Production) - Installed

Dev Dependencies:
  commandbox-dotenv @ * (Development) - Not Installed
  commandbox-cfformat @ * (Development) - Installed

Install

Install a new dependency and add it to box.json.

wheels deps install <name>
wheels deps install <name> <version>
wheels deps install <name> --dev

Examples:

# Install latest version as production dependency
wheels deps install cbvalidation

# Install specific version
wheels deps install cbvalidation 3.0.0

# Install as development dependency
wheels deps install testbox --dev

Update

Update an existing dependency to the latest version allowed by its version specification.

wheels deps update <name>

Example:

wheels deps update wirebox

The command will:

  • Check if the dependency exists in box.json

  • Determine if it's a production or dev dependency

  • Update to the latest compatible version

  • Show version change information

Remove

Remove a dependency from both box.json and the file system.

wheels deps remove <name>

Example:

wheels deps remove oldpackage

Note: Remove action will ask for confirmation before proceeding.

Report

Generate a comprehensive dependency report with outdated package check.

wheels deps report

The report includes:

  • Project information (name, version)

  • Wheels version

  • CFML engine details

  • All dependencies with installation status

  • Development dependencies

  • Installed modules information

  • Outdated package check

  • Export to JSON file

Example output:

Dependency Report:

Generated: 2025-06-04 10:05:35
Project: my-wheels-app
Project Version: 1.0.0
Wheels Version: 3.0.0
CFML Engine: Lucee 5.4.6.9

Dependencies:
  wheels-core @ ^3.0.0 - Installed: Yes
  wirebox @ ^7 - Installed: Yes

Dev Dependencies:
  testbox @ ^5 - Installed: Yes

Checking for outdated packages...
All packages are up to date!

Full report exported to: dependency-report-20250604-100535.json

Integration with CommandBox

The wheels deps commands delegate to CommandBox's package management system:

  • install uses box install

  • update uses box update

  • remove uses box uninstall

  • report uses box outdated

This ensures compatibility with the broader CFML package ecosystem.

Working with box.json

The command manages two dependency sections in box.json:

Production Dependencies

{
  "dependencies": {
    "wheels-core": "^3.0.0",
    "wirebox": "^7"
  }
}

Development Dependencies

{
  "devDependencies": {
    "testbox": "^5",
    "commandbox-cfformat": "*"
  }
}

Installation Status

The command checks for installed packages in the /modules directory. It handles various package naming conventions:

  • Simple names: wirebox

  • Namespaced: forgebox:wirebox

  • Versioned: [email protected]

Error Handling

Common scenarios:

  • No box.json: Prompts to run box init

  • Package not found: Shows available dependencies

  • Update failures: Shows current and attempted versions

  • Network issues: Displays CommandBox error messages

Best Practices

  1. Initialize First: Run box init before managing dependencies

  2. Use Version Constraints: Specify version ranges for stability

  3. Separate Dev Dependencies: Use --dev for test/build tools

  4. Regular Updates: Run wheels deps report to check for outdated packages

  5. Commit box.json: Always version control your dependency specifications

Notes

  • Dependencies are installed to the /modules directory

  • The command respects CommandBox's dependency resolution

  • Version specifications follow npm-style semver patterns

  • Dev dependencies are not installed in production environments

See Also

  • box install - CommandBox package installation

  • box.json - Package descriptor documentation

  • wheels init - Initialize a Wheels application

  • wheels plugins - Manage Wheels CLI plugins

wheels db dump

Export database schema and data to a file.

Synopsis

wheels db dump [--output=<file>] [--datasource=<name>] [--environment=<env>] 
              [--schema-only] [--data-only] [--tables=<list>] [--compress]

Description

The wheels db dump command exports your database to a SQL file that can be used for backups, migrations, or setting up new environments. It supports various options for customizing what gets exported.

Options

--output=

Output file path. Defaults to dump_[datasource]_[timestamp].sql.

wheels db dump --output=backup.sql

--datasource=

Specify which datasource to dump. If not provided, uses the default datasource.

wheels db dump --datasource=myapp_prod

--environment=

Specify the environment to use. Defaults to the current environment.

wheels db dump --environment=production

--schema-only

Export only the database structure (no data).

wheels db dump --schema-only

--data-only

Export only the data (no structure).

wheels db dump --data-only

--tables=

Comma-separated list of specific tables to dump.

wheels db dump --tables=users,posts,comments

--compress

Compress the output file using gzip.

wheels db dump --compress

Examples

Basic Backup

Create a timestamped backup:

wheels db dump
# Creates: dump_myapp_dev_20231204153045.sql

Production Backup

wheels db dump --environment=production --output=prod-backup.sql --compress

Schema Only Export

For version control:

wheels db dump --schema-only --output=schema.sql

Specific Tables

Export user-related tables:

wheels db dump --tables=users,user_roles,user_sessions --output=user-data.sql

Compressed Backup

Save space with compression:

wheels db dump --output=backup.sql.gz --compress

Database-Specific Behavior

MySQL/MariaDB

  • Uses mysqldump utility

  • Includes stored procedures and triggers

  • Preserves character sets and collations

PostgreSQL

  • Uses pg_dump utility

  • Includes schemas, functions, and extensions

  • Handles permissions and ownership

SQL Server

  • Basic export functionality

  • For full backups, use SQL Server Management Studio

H2

  • Uses built-in SCRIPT command

  • Exports to SQL script format

  • Supports compression natively

Output Format

The dump file contains:

  1. Database structure (CREATE TABLE statements)

  2. Indexes and constraints

  3. Data (INSERT statements)

  4. Views, procedures (if supported)

Example output structure:

-- Database dump generated by Wheels
-- Date: 2023-12-04 15:30:45

-- Table structure for users
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    email VARCHAR(255) UNIQUE
);

-- Data for users
INSERT INTO users (id, name, email) VALUES
(1, 'John Doe', '[email protected]'),
(2, 'Jane Smith', '[email protected]');

Common Use Cases

Daily Backups

Automated backup script:

#!/bin/bash
DATE=$(date +%Y%m%d)
wheels db dump --output=backups/daily-$DATE.sql.gz --compress

Before Major Changes

# Before deployment
wheels db dump --output=pre-deployment-backup.sql

# Deploy changes
# ...

# If something goes wrong
wheels db restore pre-deployment-backup.sql

Environment Sync

# Export from production
wheels db dump --environment=production --output=prod-data.sql

# Import to staging
wheels db restore prod-data.sql --environment=staging

Data Migration

# Export specific tables
wheels db dump --tables=legacy_users,legacy_orders --output=migration-data.sql

# Process with migration scripts
# Import to new structure

Performance Considerations

  • Large databases may take time to dump

  • Use --compress to reduce file size

  • Consider --tables for partial backups

  • Off-peak hours for production dumps

Security Notes

  1. Protect dump files - They contain sensitive data

  2. Encrypt backups - Use additional encryption for sensitive data

  3. Secure transfer - Use secure methods to transfer dumps

  4. Clean up - Don't leave dump files in public directories

Troubleshooting

"mysqldump not found"

Install MySQL client tools:

# macOS
brew install mysql-client

# Linux
sudo apt-get install mysql-client

"Permission denied"

  • Check database user has SELECT permissions

  • Ensure write permissions for output directory

Large Database Issues

  • Use --compress to reduce size

  • Consider table-by-table exports

  • Increase timeout settings if needed

Related Commands

  • wheels db restore - Restore from dump

  • wheels db status - Check before dump

  • wheels db shell - Manual export options

wheels db restore

Restore a database from a dump file.

Synopsis

wheels db restore <file> [--datasource=<name>] [--environment=<env>] 
                        [--clean] [--force] [--compressed]

Description

The wheels db restore command imports a database dump file created by wheels db dump or other database export tools. It can handle both plain SQL files and compressed dumps.

Arguments

file

Required. Path to the dump file to restore.

wheels db restore backup.sql

Options

--datasource=

Specify which datasource to restore to. If not provided, uses the default datasource.

wheels db restore backup.sql --datasource=myapp_dev

--environment=

Specify the environment to use. Defaults to the current environment.

wheels db restore backup.sql --environment=staging

--clean

Drop existing database objects before restore.

wheels db restore backup.sql --clean

--force

Skip confirmation prompts.

wheels db restore backup.sql --force

--compressed

Indicate the file is compressed. Auto-detected for .gz files.

wheels db restore backup.sql.gz --compressed

Examples

Basic Restore

wheels db restore backup.sql

Restore Compressed Backup

# Auto-detects compression from .gz extension
wheels db restore backup.sql.gz

# Or explicitly specify
wheels db restore backup.sql --compressed

Clean Restore

Drop existing objects first:

wheels db restore backup.sql --clean

Force Restore

Skip confirmation in scripts:

wheels db restore backup.sql --force

Restore to Different Environment

wheels db restore prod-backup.sql --environment=staging --force

Safety Features

  1. Confirmation Required: Prompts before overwriting data

  2. Production Warning: Extra warning for production environments

  3. File Validation: Checks file exists before starting

Database-Specific Behavior

MySQL/MariaDB

  • Uses mysql client

  • Handles large files efficiently

  • Preserves character sets

PostgreSQL

  • Uses psql client

  • Supports custom formats from pg_dump

  • Handles permissions and ownership

SQL Server

  • Uses sqlcmd client

  • Limited support for complex backups

  • Best with SSMS for full restores

H2

  • Uses RUNSCRIPT command

  • Native support for compressed files

  • Fast for embedded databases

Common Workflows

Development Reset

# Get fresh production data
wheels db dump --environment=production --output=prod-latest.sql
wheels db restore prod-latest.sql --environment=development --clean

Disaster Recovery

# Restore from latest backup
wheels db restore backups/daily-20231204.sql.gz --force

Environment Cloning

# Clone staging to test
wheels db dump --environment=staging --output=staging-snapshot.sql
wheels db restore staging-snapshot.sql --environment=test --clean

Important Warnings

Data Loss

  • Restoring overwrites existing data

  • Always backup current database first

  • Use --clean carefully

Version Compatibility

  • Ensure dump is from compatible database version

  • Check character set compatibility

  • Verify schema matches application version

Large Files

  • Monitor disk space during restore

  • Consider using --compressed dumps

  • May need to adjust timeout settings

Pre-Restore Checklist

  1. Backup current database

    wheels db dump --output=pre-restore-backup.sql
  2. Verify dump file

    ls -lh backup.sql
    head -n 20 backup.sql  # Check format
  3. Check disk space

    df -h  # Ensure enough space
  4. Stop application (if needed)

    server stop

Troubleshooting

"Access denied"

  • Check database user has CREATE/DROP permissions

  • Verify credentials in datasource

"File not found"

  • Check file path is correct

  • Use absolute paths for clarity

"Syntax error"

  • Verify dump file isn't corrupted

  • Check database version compatibility

  • Ensure correct database type

Restore Hangs

  • Large databases take time

  • Check database server resources

  • Monitor with wheels db shell in another terminal

Character Set Issues

  • Ensure database charset matches dump

  • Check connection encoding settings

Best Practices

  1. Test restores regularly - Verify backups work

  2. Document source - Note where dumps came from

  3. Version control - Track schema version with dumps

  4. Automate testing - Script restore verification

  5. Secure dumps - Protect sensitive data in dumps

Recovery Scenarios

Partial Restore

If full restore fails:

# Extract specific tables from dump
grep -E "(CREATE TABLE|INSERT INTO) users" backup.sql > users-only.sql
wheels db restore users-only.sql

Manual Recovery

Use database shell for control:

wheels db shell
# Then manually run SQL from dump file

Related Commands

  • wheels db dump - Create database dumps

  • wheels db reset - Alternative fresh start

  • wheels db shell - Manual restore control

  • wheels db status - Verify after restore

wheels dbmigrate remove table

Generate a migration file for dropping a database table.

Synopsis

wheels dbmigrate remove table name=<table_name>

Alias: wheels db remove table

Description

The dbmigrate remove table command generates a migration file that drops an existing database table. The generated migration includes a dropTable() call in the up() method.

Parameters

Parameter
Type
Required
Description

name

string

Yes

The name of the table to remove

Examples

Basic table removal

wheels dbmigrate remove table name=temp_import_data

Remove user table

wheels dbmigrate remove table name=user

Remove archive table

wheels dbmigrate remove table name=orders_archive_2023

Generated Migration Example

For the command:

wheels dbmigrate remove table name=product_archive

Generates:

component extends="wheels.migrator.Migration" hint="remove product_archive table" {

    function up() {
        transaction {
            dropTable("product_archive");
        }
    }

    function down() {
        transaction {
            // Add code here to recreate the table if needed for rollback
            // createTable(name="product_archive") { ... }
        }
    }

}

Use Cases

Removing Temporary Tables

Clean up temporary or staging tables:

# Remove import staging table
wheels dbmigrate remove table name=temp_customer_import

# Remove data migration table
wheels dbmigrate remove table name=migration_backup_20240115

Refactoring Database Schema

Remove tables during schema refactoring:

# Remove old table after data migration
wheels dbmigrate remove table name=legacy_orders

# Remove deprecated table
wheels dbmigrate remove table name=user_preferences_old

Cleaning Up Failed Features

Remove tables from cancelled features:

# Remove tables from abandoned feature
wheels dbmigrate remove table name=beta_feature_data
wheels dbmigrate remove table name=beta_feature_settings

Archive Table Cleanup

Remove old archive tables:

# Remove yearly archive tables
wheels dbmigrate remove table name=orders_archive_2020
wheels dbmigrate remove table name=orders_archive_2021

Safety Considerations

Data Loss Warning

CRITICAL: Dropping a table permanently deletes all data. Always:

  1. Backup the table data before removal

  2. Verify data has been migrated if needed

  3. Test in development/staging first

  4. Have a rollback plan

Dependent Objects

Consider objects that depend on the table:

  • Foreign key constraints

  • Views

  • Stored procedures

  • Triggers

  • Application code

Handling Dependencies

Be aware of dependent objects when removing tables:

  • Foreign key constraints

  • Views that reference the table

  • Stored procedures using the table

  • Application code dependencies

Best Practices

1. Document Removals

Add clear documentation about why the table is being removed:

# Create descriptive migration
wheels dbmigrate remove table name=obsolete_analytics_cache

# Then edit the migration file to add detailed comments about why it's being removed

2. Backup Data First

Before removing tables, create data backups:

# First backup the data
wheels db schema format=sql > backup_before_removal.sql

# Then create removal migration
wheels dbmigrate remove table name=user_preferences

3. Staged Removal

For production systems, consider staged removal:

# Stage 1: Rename table (keep for rollback)
wheels dbmigrate create blank name=rename_orders_to_orders_deprecated

# Stage 2: After verification period, remove
wheels dbmigrate remove table name=orders_deprecated

4. Check Dependencies

Verify no active dependencies before removal:

-- Check foreign keys
SELECT * FROM information_schema.referential_constraints 
WHERE referenced_table_name = 'table_name';

-- Check views
SELECT * FROM information_schema.views 
WHERE table_schema = DATABASE() 
AND view_definition LIKE '%table_name%';

Migration Structure

The generated migration contains:

  • An up() method with dropTable()

  • An empty down() method for you to implement rollback logic if needed

You should edit the down() method to add table recreation logic if you want the migration to be reversible.

Recovery Strategies

If Removal Was Mistake

  1. Don't run the migration in production

  2. Use wheels dbmigrate down if already run

  3. Restore from backup if down() fails

Preserving Table Structure

Before removal, capture structure:

# Export entire database schema
wheels db schema format=sql --save file=schema_backup.sql

# Then remove table
wheels dbmigrate remove table name=user_preferences

Notes

  • The command analyzes table structure before generating migration

  • Foreign key constraints must be removed before table removal

  • The migration is reversible if table structure is preserved

  • Always review generated migration before running

Related Commands

  • wheels dbmigrate create table - Create tables

  • wheels dbmigrate create blank - Create custom migrations

  • wheels dbmigrate up - Run migrations

  • wheels dbmigrate down - Rollback migrations

  • wheels db schema - Export table schemas

wheels analyze security

⚠️ DEPRECATED: This command has been deprecated. Please use wheels security scan instead.

Migration Notice

The analyze security command has been moved to provide better organization and expanded functionality.

Old Command (Still Works)

wheels analyze security [path] [--fix] [--report=<format>] [--severity=<level>] [--deep]

New Command

wheels security scan [path] [--fix] [--output=<format>] [--detailed]

Parameter Mapping

When called, the deprecated command forwards parameters to the new command:

  • path → path

  • --fix → --fix

  • --report → --output

  • --severity → (handled internally)

  • --deep → --detailed

Why the Change?

  • Better command organization with dedicated security namespace

  • Enhanced scanning capabilities

  • Improved reporting options

  • Integration with security vulnerability databases

See Also

  • security scan - The replacement command with enhanced features

Deprecation Timeline

  • Deprecated: v1.5.0

  • Warning Added: v1.6.0

  • Removal Planned: v2.0.0

The command currently redirects to wheels security scan with a deprecation warning.

wheels server stop

Stop the Wheels development server.

Synopsis

wheels server stop [options]

Description

The wheels server stop command stops a running CommandBox server. It provides a simple way to shut down your development server cleanly.

Options

name

  • Type: String

  • Description: Name of the server to stop (if multiple servers are running)

  • Example: wheels server stop name=myapp

--force

  • Type: Boolean flag

  • Description: Force stop all running servers

  • Example: wheels server stop --force

Examples

# Stop the default server
wheels server stop

# Stop a specific named server
wheels server stop name=myapp

# Force stop all servers
wheels server stop --force

Notes

  • If no server name is specified, stops the server in the current directory

  • Use --force to stop all running servers at once

  • The command will confirm when the server has been stopped successfully

Related Commands

  • wheels server start - Start the server

  • wheels server restart - Restart the server

  • wheels server status - Check server status

See Also

  • CommandBox Server Documentation

wheels watch

Watch Wheels application files for changes and automatically reload the application.

Synopsis

Description

The wheels watch command monitors your application files for changes and automatically triggers actions like reloading the application, running tests, or executing custom commands. This provides a smooth development workflow with instant feedback.

Arguments

Argument
Description
Default

Note: In CommandBox, boolean flags are specified with --flagname and value parameters with paramname=value.

Examples

Basic file watching

Watches default directories for changes and reloads the application

Watch with tests

Runs tests automatically when files change

Watch specific directories

Exclude file patterns

Watch with all features

Custom command on changes

Adjust check interval

Disable reload but run tests

What It Does

The watch command starts with:

  1. An initial scan of all watched directories to establish baseline

  2. Displays count of files being monitored

  3. Checks for changes at the specified interval

When changes are detected:

  • With --reload: Reloads the application

  • With --tests: Runs tests (smart filtering based on changed files)

  • With --migrations: Runs migrations if schema files changed

  • With --command: Executes the specified command

Output Example

File Exclusion

The excludeFiles parameter supports patterns:

  • *.txt - Exclude all .txt files

  • *.log - Exclude all .log files

  • temp.cfc - Exclude specific file name

  • Multiple patterns: excludeFiles="*.txt,*.log,temp.*"

Smart Test Running

When --tests is enabled, the command intelligently determines which tests to run:

  • Changes to models run model tests

  • Changes to controllers run controller tests

  • Multiple changes batch test execution

Migration Detection

With --migrations enabled, the command detects:

  • New migration files in /migrator/migrations/

  • Changes to schema files

  • Automatically runs wheels dbmigrate up

Performance Considerations

  • Initial scan time depends on project size

  • Use includeDirs to limit scope

  • Use excludeFiles to skip large files

  • Adjust interval for less frequent checks

  • Use debounce to batch rapid changes

Common Workflows

Development Workflow

Frontend + Backend

Test-Driven Development

Database Development

Best Practices

  1. Start Simple: Use wheels watch with defaults first

  2. Add Features Gradually: Enable tests, migrations as needed

  3. Optimize Scope: Use includeDirs for faster performance

  4. Exclude Wisely: Skip log files, temp files, etc.

  5. Batch Changes: Increase debounce for multiple file saves

Troubleshooting

  • High CPU Usage: Reduce check frequency with interval

  • Missed Changes: Check excluded patterns

  • Reload Errors: Ensure reload password is configured

  • Test Failures: Run tests manually to debug

Notes

  • Changes are tracked by file modification time

  • New files are automatically detected

  • Deleted files are removed from tracking

  • Press Ctrl+C to stop watching

See Also

  • - Manual application reload

  • - Run tests manually

  • - Run migrations

wheels db shell

Launch an interactive database shell for direct SQL access to your database.

Synopsis

Description

The wheels db shell command provides direct access to your database through its native command-line interface. It automatically detects your database type and launches the appropriate shell client.

For H2 databases (commonly used with Lucee), it can also launch a web-based console interface.

Options

--datasource=

Specify which datasource to connect to. If not provided, uses the default datasource from your Wheels configuration.

--environment=

Specify the environment to use. Defaults to the current environment.

--web

For H2 databases only, launches the web-based console interface instead of the CLI shell.

--command=

Execute a single SQL command and exit, rather than entering interactive mode.

Database-Specific Behavior

H2 Database

For H2 databases (default with Lucee), you have two options:

CLI Shell:

  • Provides a command-line SQL interface

  • Type SQL commands directly

  • Use help for available commands

  • Exit with exit or Ctrl+D

Web Console:

  • Opens H2's web interface in your default browser

  • Provides a GUI for browsing tables and running queries

  • More user-friendly for complex operations

  • Press Ctrl+C in terminal to stop the console server

MySQL/MariaDB

  • Launches the mysql client

  • Connects using datasource credentials

  • Full MySQL command-line interface

  • Exit with exit, quit, or Ctrl+D

PostgreSQL

  • Launches the psql client

  • Connects using datasource credentials

  • Full PostgreSQL command-line interface

  • Type \h for help, \q to quit

SQL Server

  • Launches the sqlcmd client

  • Connects using datasource credentials

  • Full SQL Server command-line interface

  • Type :help for help, :quit to exit

Examples

Basic Usage

Launch shell for default datasource:

Web Console for H2

Open H2's web interface:

Execute Single Command

Get row count without entering interactive mode:

Check database version:

Different Datasources

Connect to test database:

Connect to production (with caution):

Common SQL Commands

Once in the shell, here are some useful commands:

Show Tables

Describe Table Structure

Basic Queries

Requirements

Client Tools

The shell command requires database-specific client tools:

  • H2: No additional installation (included with Lucee)

  • MySQL: Install mysql client

  • PostgreSQL: Install psql client

  • SQL Server: Install sqlcmd

Troubleshooting

"Command not found" Errors

If you get errors about mysql/psql/sqlcmd not being found:

  1. Install the appropriate client tool (see Requirements above)

  2. Ensure the tool is in your PATH

  3. Restart your terminal/command prompt

H2 Connection Issues

If the H2 shell fails to connect:

  1. Check that your datasource is properly configured

  2. Ensure the database file exists (check db/h2/ directory)

  3. Try the web console instead: wheels db shell --web

Authentication Failures

If you get authentication errors:

  1. Verify datasource credentials in your CFML admin

  2. Check that the database user has appropriate permissions

  3. For PostgreSQL, you might need to set PGPASSWORD environment variable

H2 JAR Not Found

If you get "H2 JAR not found" error:

  1. Ensure H2 is installed as a Lucee extension

  2. Check for org.lucee.h2-*.jar in Lucee's lib directory

  3. Try reinstalling the H2 extension through Lucee admin

Security Considerations

  • Be careful in production: The shell provides full database access

  • Avoid hardcoding credentials: Use datasource configuration

  • Limit permissions: Database users should have appropriate restrictions

  • Audit usage: Shell commands may not be logged by your application

Related Commands

  • - Create a new database

  • - Check migration status

  • - Export database

  • - Run migrations

  • - CFML interactive console

wheels db schema

Visualize the current database schema.

Synopsis

Description

The wheels db schema command retrieves and displays the current database schema in various formats. This is useful for documentation, debugging, and understanding your database structure.

Parameters

Parameter
Type
Required
Default
Description

Examples

Display schema in console (default SQL format)

Display schema as text

Export schema to file

Export as JSON

Output Formats

Text Format

Displays a human-readable table structure:

SQL Format (Default)

Generates CREATE TABLE statements:

JSON Format

Provides structured schema information:

Use Cases

Version Control

Track schema changes in git:

Documentation

Generate schema documentation:

Backup Before Changes

Create schema backups before major updates:

Review Database Structure

Quickly review your database structure:

Notes on Table Filtering

Currently, the command exports all tables in the database. Future versions may support filtering specific tables.

Best Practices

1. Regular Exports

Export schema after migrations:

2. Use Version Control

Track schema changes over time:

3. Document Changes

Keep a record of schema state:

Integration with Migrations

Schema vs Migrations

  • Migrations: Track incremental changes over time

  • Schema: Shows current database state

Typical Workflow

  1. Create migration: wheels dbmigrate create table name=users

  2. Edit and run migration: wheels dbmigrate up

  3. Export current schema: wheels db schema --save file=db/schema.sql

  4. Commit both: git add app/migrator/migrations/* db/schema.sql

Notes

  • Schema export captures current database state

  • The command connects to your configured datasource

  • Output varies based on your database type (MySQL, PostgreSQL, etc.)

  • Some database-specific features may require manual adjustment

  • Use migrations for incremental changes, schemas for documentation

Related Commands

  • - Run all migrations

  • - View migration status

  • - Seed database with data

  • - Generate models from schema

wheels console

Start an interactive REPL console with Wheels application context loaded.

Synopsis

Description

The wheels console command starts an interactive Read-Eval-Print Loop (REPL) with your Wheels application context fully loaded. This allows you to interact with your models, run queries, test helper functions, and debug your application in real-time.

The console requires your Wheels server to be running as it connects via HTTP to maintain the proper application context.

Options

environment

  • Type: String

  • Default: development

  • Description: Environment to load (development, testing, production)

  • Example: wheels console environment=testing

execute

  • Type: String

  • Description: Execute a single command and exit

  • Example: wheels console execute="model('User').count()"

script

  • Type: Boolean

  • Default: true

  • Description: Use CFScript mode (false for tag mode)

  • Example: wheels console script=false

directory

  • Type: String

  • Default: Current working directory

  • Description: Application directory

  • Example: wheels console directory=/path/to/app

Examples

Basic Usage

Console Commands

Once in the console, you can use these special commands:

  • help or ? - Show help information

  • examples - Show usage examples

  • script - Switch to CFScript mode

  • tag - Switch to tag mode

  • clear or cls - Clear screen

  • history - Show command history

  • exit, quit, or q - Exit console

Working with Models

Using Helper Functions

Direct Database Queries

Inspecting Application State

How It Works

  1. The console connects to your running Wheels server via HTTP

  2. Code is sent to a special console endpoint for execution

  3. The code runs in the full application context with access to all models and helpers

  4. Results are returned and displayed in the console

  5. Variables persist between commands during the session

Requirements

  • Wheels server must be running (wheels server start)

  • Server must be accessible at the configured URL

  • Application must be in development, testing, or maintenance mode

Troubleshooting

"Server must be running to use console"

Start your server first:

"Failed to initialize Wheels context"

  1. Check that your server is running: wheels server status

  2. Verify the server URL is correct

  3. Ensure your application is not in production mode

Code execution errors

  • Check syntax - console shows line numbers for errors

  • Remember you're in CFScript mode by default

  • Use tag command to switch to tag mode if needed

Best Practices

  1. Use for debugging: Test model methods and queries before implementing

  2. Data exploration: Quickly inspect and modify data during development

  3. Testing helpers: Verify helper function outputs

  4. Learning tool: Explore Wheels functionality interactively

  5. Avoid in production: Console should not be accessible in production mode

Security Notes

  • Console is only available in development, testing, and maintenance modes

  • Never expose console access in production environments

  • Be cautious when manipulating production data

Related Commands

  • - Execute script files

  • - Manage environment settings

  • - Start the server

See Also

wheels security scan

Scans your Wheels application for security vulnerabilities and provides remediation recommendations.

Usage

Parameters

  • path - (Optional) Path to scan. Default: current directory (.)

  • --fix - (Optional) Attempt to fix issues automatically

  • --report - (Optional) Report format: console, json, html. Default: console

  • --severity - (Optional) Minimum severity to report: low, medium, high, critical. Default: medium

  • --output - (Optional) Output file for report

Description

The security scan command performs comprehensive security analysis of your Wheels application, checking for:

  • SQL injection vulnerabilities

  • Cross-site scripting (XSS) risks

  • Cross-site request forgery (CSRF) issues

  • Insecure direct object references

  • Security misconfigurations

  • Outdated dependencies with known vulnerabilities

  • Weak authentication patterns

  • Information disclosure risks

Examples

Standard security scan

Scan with auto-fix

Generate HTML security report

Scan specific directory with high severity only

JSON report for CI/CD integration

Severity Levels

The --severity parameter filters which issues are reported:

Low

  • Code style issues that could lead to vulnerabilities

  • Missing best practices

  • Informational findings

Medium (Default)

  • Potential security issues requiring review

  • Missing security headers

  • Weak configurations

High

  • Confirmed vulnerabilities with moderate impact

  • Authentication/authorization issues

  • Data validation problems

Critical

  • Severe vulnerabilities requiring immediate attention

  • SQL injection risks

  • Remote code execution possibilities

Output

Auto-Fix Feature

The --fix flag automatically resolves safe issues:

Report Formats

HTML Report

Generates interactive HTML report with:

  • Executive summary

  • Detailed findings with code snippets

  • Remediation steps

  • Compliance mapping (OWASP, CWE)

JSON Report

Machine-readable format for CI/CD integration

Integration

CI/CD Pipeline

Pre-commit Hook

Notes

  • Scans are performed locally; no code is sent externally

  • False positives can be suppressed with inline comments

  • Regular scanning is recommended as part of development workflow

  • Keep scan rules updated with wheels deps update

  • Some fixes require manual review to ensure functionality

wheels security scan [path] [--fix] [--report=<format>] [--severity=<level>] [--output=<file>]
wheels security scan
wheels security scan --fix
wheels security scan --report=html --output=security-audit.html
wheels security scan app/models --severity=high
wheels security scan --report=json --output=scan-results.json
Security Scan Results
====================

Scanning application...
✓ Configuration files
✓ Controllers (15 files)
✓ Models (8 files)
✓ Views (23 files)
✗ Dependencies (2 issues)

CRITICAL: 1 issue found
-----------------------
1. SQL Injection Risk
   File: /app/models/User.cfc
   Line: 45
   Code: findOne(where="id = #params.id#")
   Fix: Use parameterized queries
   
HIGH: 3 issues found
--------------------
1. XSS Vulnerability
   File: /app/views/users/show.cfm
   Line: 12
   Code: <h1>#user.name#</h1>
   Fix: Use htmlEditFormat() or encodeForHTML()

2. Missing CSRF Token
   File: /app/views/users/edit.cfm
   Line: 8
   Fix: Add authenticityToken() to form

3. Outdated Dependency
   Package: jackson-databind
   Version: 2.9.0 (CVE-2019-12345)
   Fix: Update to version 2.14.0 or higher

MEDIUM: 5 issues found
LOW: 12 issues found

Summary:
- Critical: 1
- High: 3
- Medium: 5
- Low: 12
- Total: 21 vulnerabilities

Recommended Actions:
1. Fix all CRITICAL issues immediately
2. Address HIGH issues before deployment
3. Plan remediation for MEDIUM issues
4. Review LOW issues for false positives
wheels security scan --fix

Auto-fixing security issues...
✓ Added htmlEditFormat() to 3 output statements
✓ Added CSRF tokens to 2 forms
✓ Updated .htaccess security headers
✗ Cannot auto-fix: SQL injection (requires manual review)

Fixed 5 of 8 fixable issues
Manual intervention required for 3 issues
wheels security scan --report=security-report.html
wheels security scan --report=security-report.json
# Example GitHub Actions
- name: Security Scan
  run: |
    wheels security scan --severity=medium --report=json --output=scan.json
    if [ $? -ne 0 ]; then
      echo "Security vulnerabilities found"
      exit 1
    fi
#!/bin/bash
wheels security scan --severity=high
if [ $? -ne 0 ]; then
  echo "Commit blocked: Security issues detected"
  exit 1
fi
wheels watch [options]

includeDirs

Comma-delimited list of directories to watch

controllers,models,views,config,migrator/migrations

excludeFiles

Comma-delimited list of file patterns to ignore

(none)

interval

Interval in seconds to check for changes

1

reload

Reload framework on changes

true

tests

Run tests on changes

false

migrations

Run migrations on schema changes

false

command

Custom command to run on changes

(none)

debounce

Debounce delay in milliseconds

500

wheels watch
wheels watch --tests
wheels watch includeDirs="controllers,models"
wheels watch excludeFiles="*.txt,*.log"
wheels watch --reload --tests --migrations
wheels watch command="wheels test run"
wheels watch interval=2 debounce=1000
wheels watch reload=false --tests
🔄 Wheels Watch Mode
Monitoring files for changes...
Press Ctrl+C to stop watching

✓ Will reload framework on changes
✓ Will run tests on changes

Watching 145 files across 5 directories

📝 Detected changes:
  ~ /app/models/User.cfc (modified)
  + /app/models/Profile.cfc (new)

🔄 Reloading application...
✅ Application reloaded successfully at 14:32:15

🧪 Running tests...
✅ All actions completed, watching for more changes...
# Terminal 1: Run server
box server start

# Terminal 2: Watch with reload and tests
wheels watch --reload --tests
# Watch backend files and run build command
wheels watch command="npm run build"
# Focus on models and controllers with tests
wheels watch includeDirs="models,controllers" --tests
# Watch for migration changes
wheels watch includeDirs="migrator/migrations" --migrations
wheels reload
wheels test run
wheels dbmigrate up
wheels db shell [--datasource=<name>] [--environment=<env>] [--web] [--command=<sql>]
wheels db shell --datasource=myapp_dev
wheels db shell --environment=production
wheels db shell --web
wheels db shell --command="SELECT COUNT(*) FROM users"
wheels db shell
wheels db shell --web
wheels db shell
wheels db shell
wheels db shell
wheels db shell
wheels db shell --web
wheels db shell --command="SELECT COUNT(*) FROM users"
wheels db shell --command="SELECT VERSION()"
wheels db shell --datasource=myapp_test
wheels db shell --datasource=myapp_prod --environment=production
-- H2/MySQL
SHOW TABLES;

-- PostgreSQL
\dt

-- SQL Server
SELECT name FROM sys.tables;
-- H2/MySQL
DESCRIBE users;

-- PostgreSQL
\d users

-- SQL Server
sp_help users;
-- Count records
SELECT COUNT(*) FROM users;

-- View recent records
SELECT * FROM users ORDER BY created_at DESC LIMIT 10;

-- Check for specific data
SELECT * FROM users WHERE email = '[email protected]';
# macOS
brew install mysql-client

# Ubuntu/Debian
sudo apt-get install mysql-client

# RHEL/CentOS
sudo yum install mysql
# macOS
brew install postgresql

# Ubuntu/Debian
sudo apt-get install postgresql-client

# RHEL/CentOS
sudo yum install postgresql
# macOS
brew install mssql-tools

# Linux
# Follow Microsoft's instructions for your distribution
wheels db create
wheels db status
wheels db dump
wheels dbmigrate
wheels console
wheels db schema [options]

format

string

No

"sql"

Output format (text, json, or sql)

--save

boolean

No

false

Save output to file instead of console

file

string

No

-

File path to write schema to (when using --save)

engine

string

No

"default"

Database engine to use

wheels db schema
wheels db schema format=text
wheels db schema --save file=schema.sql
wheels db schema format=json --save file=schema.json
TABLE: USERS
--------------------------------------------------------------------------------
  id INTEGER NOT NULL PRIMARY KEY
  username VARCHAR(50) NOT NULL
  email VARCHAR(150) NOT NULL
  created_at TIMESTAMP
  updated_at TIMESTAMP

  INDEXES:
  - UNIQUE INDEX idx_users_email (email)
  - INDEX idx_users_username (username)
CREATE TABLE users (
    id INTEGER NOT NULL PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(150) NOT NULL,
    created_at TIMESTAMP,
    updated_at TIMESTAMP
);

CREATE UNIQUE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_username ON users(username);
{
  "tables": {
    "users": {
      "columns": [
        {"name": "id", "type": "INTEGER", "nullable": false, "primaryKey": true},
        {"name": "username", "type": "VARCHAR(50)", "nullable": false},
        {"name": "email", "type": "VARCHAR(150)", "nullable": false}
      ],
      "indexes": [
        {"name": "idx_users_email", "columns": ["email"], "unique": true}
      ]
    }
  }
}
# Export schema after migrations
wheels dbmigrate latest
wheels db schema --save file=db/schema.sql

# Commit schema file
git add db/schema.sql
git commit -m "Update database schema"
# Export human-readable schema
wheels db schema format=text --save file=docs/database-schema.txt

# Export as JSON for documentation tools
wheels db schema format=json --save file=docs/database-schema.json
# Backup current schema
wheels db schema format=sql --save file=backups/schema-$(date +%Y%m%d).sql

# Make your changes
wheels dbmigrate latest

# Export new schema
wheels db schema format=sql --save file=db/schema.sql
# View all tables in text format
wheels db schema format=text

# Export for team review
wheels db schema format=text --save file=database-review.txt
# After running migrations
wheels dbmigrate latest
wheels db schema --save file=db/schema.sql
# Add to git
git add db/schema.sql
git commit -m "Update schema after adding user table"
# Before major changes
wheels db schema --save file=db/schema-before-refactor.sql

# After changes
wheels db schema --save file=db/schema-after-refactor.sql
wheels dbmigrate latest
wheels dbmigrate info
wheels db seed
wheels generate model
wheels console [options]
# Start interactive console
wheels console

# Start in testing environment
wheels console environment=testing

# Execute single command
wheels console execute="model('User').findAll().recordCount"

# Start in tag mode
wheels console script=false
// Find a user by ID
user = model("User").findByKey(1)

// Update user properties
user.name = "John Doe"
user.email = "[email protected]"
user.save()

// Create new user
newUser = model("User").create(
    name="Jane Smith",
    email="[email protected]",
    password="secure123"
)

// Find users with conditions
activeUsers = model("User").findAll(
    where="active=1 AND createdAt >= '#dateAdd('d', -7, now())#'",
    order="createdAt DESC"
)

// Delete a user
model("User").deleteByKey(5)
// Text helpers
pluralize("person")  // Returns: "people"
singularize("users") // Returns: "user"
capitalize("hello world") // Returns: "Hello World"

// Date helpers
timeAgoInWords(dateAdd('h', -2, now())) // Returns: "2 hours ago"
distanceOfTimeInWords(now(), dateAdd('d', 7, now())) // Returns: "7 days"

// URL helpers
urlFor(route="user", key=1) // Generate URL for user route
linkTo(text="Home", route="root") // Generate link HTML
// Run custom SQL query
results = query("
    SELECT u.*, COUNT(p.id) as post_count 
    FROM users u 
    LEFT JOIN posts p ON u.id = p.userId 
    GROUP BY u.id
")

// Simple count query
userCount = query("SELECT COUNT(*) as total FROM users").total
// View application settings
application.wheels.environment
application.wheels.dataSourceName
application.wheels.version

// Check loaded models
structKeyArray(application.wheels.models)

// View routes (if available)
application.wheels.routes
wheels server start
wheels console
wheels runner
wheels environment
wheels server start
Wheels Model Documentation
Wheels Helper Functions

wheels generate app

Create a new Wheels application from templates.

Synopsis

wheels generate app [name] [template] [directory] [options]
wheels g app [name] [template] [directory] [options]
wheels new [name] [template] [directory] [options]

Description

The wheels generate app command creates a new Wheels application with a complete directory structure, configuration files, and optionally sample code. It supports multiple templates for different starting points.

Arguments

Argument
Description
Default

name

Application name

MyApp

template

Template to use

wheels-base-template@BE

directory

Target directory

./{name}

Options

Option
Description
Default

reloadPassword

Set reload password

'' (empty)

datasourceName

Database datasource name

App name

cfmlEngine

CFML engine (lucee/adobe)

lucee

--useBootstrap

Include Bootstrap CSS

false

--setupH2

Setup H2 embedded database

true

--init

Initialize as CommandBox package

false

--force

Overwrite existing directory

false

--help

Show help information

Available Templates

wheels-base-template@BE (Default)

wheels generate app myapp
  • Backend Edition template

  • Complete MVC structure

  • Sample code and configuration

  • H2 database setup by default

HelloWorld

wheels generate app myapp HelloWorld
  • Simple "Hello World" example

  • One controller and view

  • Great for learning

HelloDynamic

wheels generate app myapp HelloDynamic
  • Dynamic content example

  • Database interaction

  • Form handling

HelloPages

wheels generate app myapp HelloPages
  • Static pages example

  • Layout system

  • Navigation structure

Examples

Create basic application

wheels generate app blog

Create with custom template

wheels generate app api Base@BE

Create in specific directory

wheels generate app name=myapp directory=./projects/

Create with Bootstrap

wheels generate app portfolio --useBootstrap

Create with H2 database (default is true)

wheels generate app demo --setupH2

Create with all options

wheels generate app name=enterprise template=HelloDynamic directory=./apps/ \
  reloadPassword=secret \
  datasourceName=enterprise_db \
  cfmlEngine=adobe \
  --useBootstrap \
  --setupH2

Generated Structure

myapp/
├── .wheels-cli.json      # CLI configuration
├── box.json              # Dependencies
├── server.json           # Server configuration
├── Application.cfc       # Application settings
├── config/
│   ├── app.cfm          # App configuration
│   ├── routes.cfm       # URL routes
│   └── settings.cfm     # Framework settings
├── controllers/
│   └── Main.cfc         # Default controller
├── models/
├── views/
│   ├── layout.cfm       # Default layout
│   └── main/
│       └── index.cfm    # Home page
├── public/
│   ├── stylesheets/
│   ├── javascripts/
│   └── images/
├── tests/
└── wheels/              # Framework files

Configuration Files

box.json

{
  "name": "myapp",
  "version": "1.0.0",
  "dependencies": {
    "wheels": "^2.5.0"
  }
}

server.json

{
  "web": {
    "http": {
      "port": 3000
    }
  },
  "app": {
    "cfengine": "lucee5"
  }
}

.wheels-cli.json

{
  "name": "myapp",
  "version": "1.0.0",
  "framework": "wheels",
  "reload": "wheels"
}

Database Setup

With H2 (Embedded)

wheels generate app myapp
  • H2 is setup by default (--setupH2=true)

  • No external database needed

  • Perfect for development

  • Auto-configured datasource

  • To disable: --setupH2=false

With External Database

  1. Create application:

    wheels generate app myapp datasourceName=myapp_db --setupH2=false
  2. Configure in CommandBox:

    server set app.datasources.myapp_db={...}

Post-Generation Steps

  1. Navigate to directory

    cd myapp
  2. Install dependencies

    box install
  3. Start server

    box server start
  4. Open browser

    http://localhost:3000

Template Development

Create custom templates in ~/.commandbox/cfml/modules/wheels-cli/templates/apps/:

mytemplate/
├── config/
├── controllers/
├── models/
├── views/
└── template.json

Best Practices

  1. Use descriptive application names

  2. Choose appropriate template for project type

  3. Set secure reload password for production

  4. Configure datasource before starting

  5. Run tests after generation

Common Issues

  • Directory exists: Use --force or choose different name

  • Template not found: Check available templates with wheels info

  • Datasource errors: Configure database connection

  • Port conflicts: Change port in server.json

See Also

  • wheels init - Initialize existing application

  • wheels generate app-wizard - Interactive app creation

  • wheels scaffold - Generate CRUD scaffolding

wheels dbmigrate create blank

Create an empty database migration file with up and down methods.

Synopsis

wheels dbmigrate create blank --name=<name> [options]

Description

The dbmigrate create blank command generates a new empty migration file with the basic structure including up() and down() methods. This provides a starting point for custom migrations where you need full control over the migration logic.

Options

--name

  • Type: String

  • Required: Yes

  • Description: The name of the migration (will be prefixed with timestamp)

--datasource

  • Type: String

  • Default: Application default

  • Description: Specify the datasource this migration targets

--description

  • Type: String

  • Default: Empty

  • Description: Add a description comment to the migration file

--template

  • Type: String

  • Default: blank

  • Description: Use a custom template for the migration

Examples

Create a basic empty migration

wheels dbmigrate create blank --name=add_custom_indexes

Create migration with description

wheels dbmigrate create blank --name=update_user_permissions --description="Add role-based permissions to users"

Create migration for specific datasource

wheels dbmigrate create blank --name=legacy_data_cleanup --datasource=legacyDB

Generated File Structure

The command creates a file named YYYYMMDDHHmmss_<name>.cfc with the following structure:

component extends="wheels.migrator.Migration" hint="<description>" {

    function up() {
        transaction {
            // Add your migration code here
        }
    }

    function down() {
        transaction {
            // Add code to reverse the migration
        }
    }

}

Use Cases

Custom Database Operations

For complex operations not covered by other generators:

# Create migration for custom stored procedure
wheels dbmigrate create blank --name=create_reporting_procedures

# Edit the file to add:
# - CREATE PROCEDURE statements
# - Complex SQL operations
# - Multiple related changes

Data Migrations

When you need to migrate data, not just schema:

# Create data migration
wheels dbmigrate create blank --name=normalize_user_emails

# Edit to add data transformation logic
# Example: lowercase all email addresses

Multi-Step Operations

For migrations requiring multiple coordinated changes:

# Create complex migration
wheels dbmigrate create blank --name=refactor_order_system

# Edit to include:
# - Create new tables
# - Migrate data
# - Drop old tables
# - Update foreign keys

Database-Specific Features

For database-specific features not abstracted by Wheels:

# Create migration for PostgreSQL-specific features
wheels dbmigrate create blank --name=add_json_columns

# Edit to use PostgreSQL JSON operations

Best Practices

1. Descriptive Names

Use clear, descriptive names that indicate the migration's purpose:

# Good
wheels dbmigrate create blank --name=add_user_authentication_tokens

# Bad
wheels dbmigrate create blank --name=update1

2. Implement Both Methods

Always implement both up() and down() methods:

function up() {
    transaction {
        execute("CREATE INDEX idx_users_email ON users(email)");
    }
}

function down() {
    transaction {
        execute("DROP INDEX idx_users_email");
    }
}

3. Use Transactions

Wrap operations in transactions for atomicity:

function up() {
    transaction {
        // All operations succeed or all fail
        createTable("new_table");
        execute("INSERT INTO new_table SELECT * FROM old_table");
        dropTable("old_table");
    }
}

4. Add Comments

Document complex operations:

function up() {
    transaction {
        // Create composite index for query optimization
        // This supports the findActiveUsersByRegion() query
        execute("
            CREATE INDEX idx_users_active_region 
            ON users(is_active, region_id) 
            WHERE is_active = 1
        ");
    }
}

Available Migration Methods

Within your blank migration, you can use these helper methods:

  • createTable(name, options) - Create a new table

  • dropTable(name) - Drop a table

  • addColumn(table, column, type, options) - Add a column

  • removeColumn(table, column) - Remove a column

  • changeColumn(table, column, type, options) - Modify a column

  • addIndex(table, column, options) - Add an index

  • removeIndex(table, column) - Remove an index

  • execute(sql) - Execute raw SQL

  • announce(message) - Output a message during migration

Notes

  • Migration files are created in /app/migrator/migrations/ or your configured migration path

  • The timestamp ensures migrations run in the correct order

  • Always test migrations in development before production

  • Keep migrations focused on a single purpose

Related Commands

  • wheels dbmigrate create table - Create a table migration

  • wheels dbmigrate create column - Create a column migration

  • wheels dbmigrate up - Run migrations

  • wheels dbmigrate down - Rollback migrations

  • wheels dbmigrate info - View migration status

wheels env

Base command for environment management in Wheels applications.

Usage

wheels env [subcommand]

Description

The wheels env command provides environment management for Wheels applications. It handles environment configuration, switching between environments, and managing environment-specific settings.

Subcommands

Command
Description

list

List all configured environments

setup

Setup a new environment

switch

Switch to a different environment

Direct Usage

When called without subcommands, displays help information:

wheels env

Output:

🌍 Wheels Environment Management

Available commands:

  wheels env list
    List all configured environments

  wheels env setup <environment>
    Setup a new environment (development, staging, production)
    Options: --template=docker --database=postgres

  wheels env switch <environment>
    Switch to a different environment

Examples:
  wheels env setup development
  wheels env setup production --template=docker --database=postgres
  wheels env switch staging

Examples

List all environments

wheels env list

Setup new environment

wheels env setup development
wheels env setup production --template=docker --database=postgres

Switch environment

wheels env switch production

Environment Configuration

Each environment has its own configuration:

/config/
  ├── development/
  │   └── settings.cfm
  ├── testing/
  │   └── settings.cfm
  ├── production/
  │   └── settings.cfm
  └── environment.cfm

Environment Variables

The command respects these environment variables:

Variable
Description
Default

WHEELS_ENV

Current environment

development

WHEELS_DATASOURCE

Database name

Per environment

WHEELS_DEBUG

Debug mode

Per environment

Environment Detection

Order of precedence:

  1. Command line argument

  2. WHEELS_ENV environment variable

  3. .wheels-env file

  4. Default (development)

Common Environments

Development

  • Debug enabled

  • Detailed error messages

  • Hot reload active

  • Development database

Testing

  • Test database

  • Fixtures loaded

  • Debug enabled

  • Isolated from production

Production

  • Debug disabled

  • Optimized performance

  • Production database

  • Error handling active

Staging

  • Production-like

  • Separate database

  • Debug configurable

  • Pre-production testing

Environment Files

.wheels-env

Local environment override:

production

.env.[environment]

Environment-specific variables:

# .env.production
DATABASE_URL=mysql://prod@host/db
CACHE_ENABLED=true
DEBUG_MODE=false

Integration

With Other Commands

Many commands respect current environment:

# Uses current environment's database
wheels dbmigrate latest

# Reloads in current environment
wheels reload

# Tests run in test environment
wheels test run

In Application Code

Access current environment:

<cfset currentEnv = get("environment")>
<cfif currentEnv eq "production">
    <!--- Production-specific code --->
</cfif>

Best Practices

  1. Never commit .wheels-env file

  2. Use testing environment for tests

  3. Match staging to production closely

  4. Separate databases per environment

  5. Environment-specific configuration files

Use Cases

  1. Local Development: Switch between feature environments

  2. Testing: Isolated test environment

  3. Deployment: Environment-specific configurations

  4. Debugging: Quick environment switching

  5. Team Development: Consistent environments

Notes

  • Environment changes may require application restart

  • Database connections are environment-specific

  • Some settings only take effect after reload

  • Use version control for environment configs

See Also

  • wheels env setup - Setup new environment

  • wheels env list - List environments

  • wheels env switch - Switch environments

  • wheels config - Configuration management

wheels server status

Show detailed status of the Wheels development server.

Synopsis

wheels server status [options]

Description

The wheels server status command displays the current status of your CommandBox server along with Wheels-specific information. It provides more context than the standard server status command.

Options

name

  • Type: String

  • Description: Name of the server to check

  • Example: wheels server status name=myapp

--json

  • Type: Boolean flag

  • Description: Output status in JSON format

  • Example: wheels server status --json

--verbose

  • Type: Boolean flag

  • Description: Show detailed server information

  • Example: wheels server status --verbose

Examples

# Check default server status
wheels server status

# Check specific server
wheels server status name=myapp

# Get JSON output for scripting
wheels server status --json

# Show verbose details
wheels server status --verbose

Output Information

When the server is running, displays:

  • Server running status

  • URL and port information

  • Wheels framework version

  • Application root directory

  • Quick action suggestions

Example output:

Wheels Server Status
===================

Status: Running
URL: http://127.0.0.1:60000
PID: 12345

Wheels Application Info:
  Wheels Version: 2.5.0
  Application Root: /Users/you/myapp

Quick Actions:
  wheels server open     - Open in browser
  wheels server log      - View logs
  wheels reload          - Reload application

Notes

  • The command enhances CommandBox's native status with Wheels-specific information

  • Use --json format when integrating with scripts or other tools

  • The verbose flag shows additional server configuration details

Related Commands

  • wheels server start - Start the server

  • wheels server stop - Stop the server

  • wheels server log - View server logs

See Also

  • CommandBox Server Documentation

Beginner Tutorial: Hello World

In this tutorial, we'll be writing a simple application to make sure we have Wheels installed properly and that everything is working as it should.

Testing Your Install

Let's make sure we're all on the same page. I'm going to assume that you've followed the Getting Started guide and have CommandBox all setup. If you haven't done that, stop and read that guide get everything setup. It's okay, this web page will wait for you.

Okay, so you have Wheels installed and can see the Wheels "Congratulations!" page as shown below. That wasn't that hard now, was it?

Figure 1: Wheels congratulations screen

Hello World: Your First Wheels App

Okay, let's get to some example code. We know that you've been dying to get your hands on some code!

To continue with Programming Tutorial Tradition, we'll create the ubiquitous Hello World! application. But to keep things interesting, let's add a little Wheels magic along the way.

Setting up the Controller

Let's create a controller from scratch to illustrate how easy it is to set up a controller and plug it into the Wheels framework.

First, create a file called Say.cfc in the app/controllers directory and add the code below to the file.

app/controllers/Say.cfc
component extends="Controller"{
}

Congratulations, you just created your first Wheels controller! What does this controller do, you might ask? Well, to be honest, not much. It has no methods defined, so it doesn't add any new functionality to our application. But because it extends the base Controller component, it inherits quite a bit of powerful functionality and is now tied into our Wheels application.

So what happens if we try to call our new controller right now? Lets take a look. Open your browser and point your browser to the new controller. Because my local server is installed on port 60000, my URL is http://127.0.0.1:60000/say. You may need to enter a different URL, depending on how your web server is configured. In my case, I'm using CommandBox.

Figure 2: Wheels error after setting up your blank say controller

The error says "Could not find the view page for the 'index' action in the 'say' controller." Where did "index" come from? The URL we typed in only specified a controller name but no action. When an action is not specified in the URL, Wheels assumes that we want the default action. Out of the box, the default action in Wheels is set to index. So in our example, Wheels tried to find the index action within the say controller, and it threw an error because it couldn't find its view page.

Setting up an Action

But let's jump ahead. Now that we have the controller created, let's add an action to it called hello. Change your say controller so it looks like the code block below:

app/controllers/Say.cfc
component extends="Controller" {
    function hello() {
    }
}

As you can see, we created an empty method named hello.

Now let's call our new action in the browser and see what we get. To call the hello action, we simply add /hello to the end of the previous URL that we used to call our say controller:

http://127.0.0.1:60000/say/hello

Once again, we get a ColdFusion error. Although we have created the controller and added the hello action to it, we haven't created the view.

Setting up the View

By default, when an action is called, Wheels will look for a view file with the same name as the action. It then hands off the processing to the view to display the user interface. In our case, Wheels tried to find a view file for our say/hello action and couldn't find one.

Let's remedy the situation and create a view file. View files are simple CFML pages that handle the output of our application. In most cases, views will return HTML code to the browser. By default, the view files will have the same name as our controller actions and will be grouped into a directory under the view directory. This new directory will have the same name as our controller.

Find the views directory inside the app directory, located at the root of your Wheels installation. There will be a few directories in there already. For now, we need to create a new directory in the views directory called say. This is the same name as the controller that we created above.

Now inside the say directory, create a file called hello.cfm. In the hello.cfm file, add the following line of code:

app/views/say/hello.cfm
<h1>Hello World!</h1>

Save your hello.cfm file, and let's call our say/hello action once again. You have your first working Wheels page if your browser looks like Figure 3 below.

Figure 3: Your first working Wheels action.

You have just created your first functional Wheels page, albeit it is a very simple one. Pat yourself on the back, go grab a snack, and when you're ready, let's go on and extend the functionality of our Hello World! application a little more.

Adding Dynamic Content to Your View

We will add some simple dynamic content to our hello action and add a second action to the application. We'll then use some Wheels code to tie the 2 actions together. Let's get get to it!

The Dynamic Content

The first thing we are going to do is to add some dynamic content to our say/hello action. Modify your say controller so it looks like the code block below:

app/controllers/Say.cfc
component extends="Controller" {
    function hello() {
        time = Now();
    }
}

All we are doing here is creating a variable called time and setting its value to the current server time using the basic ColdFusion Now() function. When we do this, the variable becomes immediately available to our view code.

Why not just set up this value directly in the view? If you think about it, maybe the logic behind the value of time may eventually change. What if eventually we want to display its value based on the user's time zone? What if later we decide to pull it from a web service instead? Remember, the controller is supposed to coordinate all of the data and business logic, not the view.

Displaying the Dynamic Content

Next, we will modify our say/hello.cfm view file so that it looks like the code block below. When we do this, the value will be displayed in the browser.

app/views/say/hello.cfm
<h1>Hello World!</h1>
<p>Current time: <cfoutput>#time#</cfoutput></p>

call your say/hello action again in your browser. Your browser should look like Figure 4 below.

Figure 4: Hello World with the current date and time

This simple example showed that any dynamic content created in a controller action is available to the corresponding view file. In our application, we created a time variable in the say/hello controller action and display that variable in our say/hello.cfm view file.

Adding a Second Action: Goodbye

Now we will expand the functionality of our application once again by adding a second action to our say controller. If you feel adventurous, go ahead and add a goodbye action to the say controller on your own, then create a goodbye.cfm view file that displays a "Goodbye" message to the user. If you're not feeling that adventurous, we'll quickly go step by step.

First, modify the the say controller file so that it looks like the code block below.

app/controllers/Say.cfc
component extends="Controller" {
    function hello() {
        time = Now();
    }

    function goodbye() {
    }
}

Now go to the app/views/say directory and create a goodbye.cfm page.

Add the following code to the goodbye.cfm page and save it.

app/views/say/goodbye.cfm
Goodbye World!

If we did everything right, we should be able to call the new say/goodbye action using the following URL:

http://127.0.0.1:60000/say/goodbye

Your browser should look like Figure 5 below:

Figure 5: Your new goodbye action

Linking to Other Actions

Now let's link our two actions together. We will do this by adding a link to the bottom of each page so that it calls the other page.

Linking Hello to Goodbye

Open the say/hello.cfm view file. We are going to add a line of code to the end of this file so our say/hello.cfm view file looks like the code block below:

app/views/say/hello.cfm
<h1>Hello World!</h1>
<p>Current time: <cfoutput>#time#</cfoutput></p>
<p>Time to say <cfoutput>#linkTo(text="goodbye", action="goodbye")#?</cfoutput></p>

The linkTo() function is a built-in Wheels function. In this case, we are passing 2 named parameters to it. The first parameter, text, is the text that will be displayed in the hyperlink. The second parameter, action, defines the action to point the link to. By using this built-in function, your application's main URL may change, and even controllers and actions may get shifted around, but you won't suffer from the dreaded dead link. Wheels will always create a valid link for you as long as you configure it correctly when you make infrastructure changes to your application.

Once you have added the additional line of code to the end of the say/hello.cfm view file, save your file and call the say/hello action from your browser. Your browser should look like Figure 6 below.

Figure 6: Your say/hello action with a link to the goodbye action

You can see that Wheels created a link for us and added an appropriate URL for the say/goodbye action to the link.

Linking Goodbye to Hello

Let's complete our little app and add a corresponding link to the bottom of our say/goodbye.cfm view page.

Open your say/goodbye.cfm view page and modify it so it looks like the code block below.

CFML: app/views/say/goodbye.cfm

app/views/say/goodbye.cfm
<h1>Goodbye World!</h1>
<p>Time to say <cfoutput>#linkTo(text="hello", action="hello")#?</cfoutput></p>

If you now call the say/goodbye action in your browser, your browser should look like Figure 7 below.

Figure 7: Your say/goodbye action with a link to the hello action

Much More to Learn

You now know enough to be dangerous with Wheels. Look out! But there are many more powerful features to cover. You may have noticed that we haven't even talked about the M in MVC.

No worries. We will get there. And we think you will enjoy it.

CLI Overview

Welcome to the comprehensive documentation for the Wheels CLI - a powerful command-line interface for the Wheels framework.

What is Wheels CLI?

Wheels CLI is a CommandBox module that provides a comprehensive set of tools for developing Wheels applications. It offers:

  • Code Generation - Quickly scaffold models, controllers, views, and complete CRUD operations

  • Database Migrations - Manage database schema changes with version control

  • Testing Tools - Run tests, generate coverage reports, and use watch mode

  • Development Tools - File watching, automatic reloading, and development servers

  • Code Analysis - Security scanning, performance analysis, and code quality checks

  • Plugin Management - Install and manage Wheels plugins

  • Environment Management - Switch between development, testing, and production

Documentation Structure

📚

Complete reference for all CLI commands organized by category:

  • - Essential commands like init, reload, watch

  • - Generate applications, models, controllers, views

  • - Complete database management and migrations

  • - Run tests and generate coverage

  • - Manage application settings

🚀

Get up and running with Wheels CLI in minutes. Learn how to:

  • Install Wheels CLI

  • Create your first application

  • Generate CRUD scaffolding

  • Run tests and migrations

📖 Guides

Development Guides

  • - Understand the CLI's architecture

  • - Extend the CLI with your own commands

  • - Customize code generation templates

  • - Write and run tests effectively

Best Practices

  • - Database migration best practices

  • - Security scanning and hardening

  • - Optimization techniques

📋 Reference

  • - All available configuration settings

  • - Variables available in templates

  • - Understanding command exit codes

  • - Environment configuration

Key Features

🛠️ Code Generation

Generate complete applications or individual components:

🗄️ Database Management

Complete database lifecycle management:

🧪 Testing

Comprehensive testing support:

👀 Development Tools

Enhance your development workflow:

Getting Started

  1. Install CommandBox (if not already installed):

  2. Install Wheels CLI:

  3. Create Your First App:

  4. Explore Commands:

Version Compatibility

Wheels CLI
Wheels
CommandBox
CFML Engine

Community & Support

  • Documentation:

  • GitHub:

  • Slack: - #wheels channel

  • Forums:

Contributing

We welcome contributions! See our for details on:

  • Reporting issues

  • Suggesting features

  • Submitting pull requests

  • Creating custom commands

Recent Updates

Version 3.0.0

  • 🆕 Modernized service architecture

  • 🆕 Enhanced testing capabilities with watch mode

  • 🆕 Security scanning and performance optimization

  • 🆕 Plugin and environment management

  • 🆕 Improved code generation with more options

  • 🔧 Better error handling and user feedback

  • 📚 Comprehensive documentation

Quick Links

  • - Complete command reference

  • - Get started in minutes

  • - Extend the CLI

  • - Technical deep dive

  • - Testing best practices

License

Wheels CLI is open source software licensed under the Apache License 2.0. See for details.


Ready to get started? Head to the or explore the .

Quick Start Guide

Get up and running with Wheels CLI in minutes.

Prerequisites

  • CommandBox 5.0+

  • Java 8+

  • Database (MySQL, PostgreSQL, SQL Server, or H2)

Installation

Install CommandBox

Install Wheels CLI

Creating Your First Application

1. Generate Application

This creates a new Wheels application with:

  • Complete directory structure

  • Configuration files

  • Sample code

2. Configure Database

Edit /config/settings.cfm:

Or use H2 embedded database:

Create the database:

3. Start Server

Visit http://localhost:3000

Creating Your First Feature

Let's create a blog post feature:

1. Generate Scaffold

This generates:

  • Model with validations

  • Controller with CRUD actions

  • Views for all actions

  • Database migration

  • Test files

2. Run Migration

3. Add Routes

Edit /config/routes.cfm:

4. Reload Application

5. Test Your Feature

Visit http://localhost:3000/posts

You now have a fully functional blog post management system!

Development Workflow

File Watching

In a new terminal:

Now changes to .cfc and .cfm files trigger automatic reloads.

Running Tests

Adding Relationships

Let's add comments to posts:

Common Tasks

Adding Authentication

Adding API Endpoints

Working with Views

Best Practices

1. Use Migrations

Always use migrations for database changes:

2. Write Tests

Generate tests for your code:

3. Use Environment Configuration

4. Version Control

Add to .gitignore:

Debugging

Check Logs

Enable Debug Mode

In /config/settings.cfm:

Common Issues

Port already in use:

Database connection failed:

Migration failed:

Need to reset database:

Access database directly:

Next Steps

  1. Read the Guides:

  2. Explore Commands:

    • wheels --help

    • wheels generate --help

    • wheels dbmigrate --help

  3. Join the Community:

    • #wheels channel

Example: Complete Blog Application

Here's a complete blog setup:

You now have a working blog with posts, authors, and comments!

wheels test coverage

Generate code coverage reports for your test suite.

Synopsis

Description

The wheels test coverage command runs your test suite while collecting code coverage metrics. It generates detailed reports showing which parts of your code are tested and identifies areas that need more test coverage.

Options

Option
Description
Default

Examples

Generate basic coverage report

Generate coverage for core tests

Custom output directory

Force reload before coverage

Coverage for specific server

What It Does

  1. Instruments Code: Adds coverage tracking to your application

  2. Runs Tests: Executes all specified tests

  3. Collects Metrics: Tracks which lines are executed

  4. Generates Reports: Creates coverage reports in requested formats

  5. Analyzes Results: Provides insights and recommendations

Coverage Metrics

Line Coverage

Percentage of code lines executed:

Function Coverage

Percentage of functions tested:

Branch Coverage

Percentage of code branches tested:

Statement Coverage

Percentage of statements executed:

Report Formats

HTML Report

Interactive web-based report:

Features:

  • File browser

  • Source code viewer

  • Line-by-line coverage

  • Sortable metrics

  • Trend charts

JSON Report

Machine-readable format:

XML Report

For CI/CD integration:

Compatible with:

  • Jenkins

  • GitLab CI

  • GitHub Actions

  • SonarQube

Console Report

Quick terminal output:

Coverage Thresholds

Global Threshold

Per-Metric Thresholds

Configure in .wheels-coverage.json:

File-Specific Thresholds

Configuration

Coverage Configuration File

.wheels-coverage.json:

Integration

CI/CD Pipeline

Git Hooks

.git/hooks/pre-push:

Badge Generation

Analyzing Results

Identify Untested Code

The HTML report highlights:

  • Red: Uncovered lines

  • Yellow: Partially covered branches

  • Green: Fully covered code

Focus Areas

  1. Critical Paths: Ensure high coverage

  2. Complex Logic: Test all branches

  3. Error Handling: Cover edge cases

  4. New Features: Maintain coverage

Best Practices

  1. Set Realistic Goals: Start with achievable thresholds

  2. Incremental Improvement: Gradually increase thresholds

  3. Focus on Quality: 100% coverage doesn't mean bug-free

  4. Test Business Logic: Prioritize critical code

  5. Regular Monitoring: Track coverage trends

Performance Considerations

Coverage collection adds overhead:

  • Slower test execution

  • Increased memory usage

  • Larger test artifacts

Tips:

  • Run coverage in CI/CD, not every test run

  • Use incremental coverage for faster feedback

  • Exclude third-party code

Troubleshooting

Low Coverage

  • Check if tests are actually running

  • Verify include/exclude patterns

  • Look for untested files

Coverage Not Collected

  • Ensure code is instrumented

  • Check file path patterns

  • Verify test execution

Report Generation Failed

  • Check output directory permissions

  • Verify report format support

  • Review error logs

Advanced Usage

Incremental Coverage

Coverage Trends

Merge Coverage

Notes

  • Coverage data is collected during test execution

  • Some code may be unreachable and shouldn't count

  • Focus on meaningful coverage, not just percentages

  • Different metrics provide different insights

See Also

  • - Run tests

  • - Run specific tests

  • - Debug test execution

wheels environment

Display or switch the current Wheels environment.

Synopsis

Description

The wheels environment command manages your Wheels application environment settings. It can display the current environment, switch between environments, and list all available environments. Environment changes can trigger automatic application reloads to ensure your settings take effect immediately.

Arguments

action

  • Type: String

  • Default: show

  • Options: show, set, list

  • Description: Action to perform

  • Example: wheels environment set production

value

  • Type: String

  • Description: Environment value when using set action

  • Options: development, testing, production, maintenance

  • Example: wheels environment set development

Options

--reload

  • Type: Boolean

  • Default: true

  • Description: Reload application after changing environment

  • Example: wheels environment set production --reload=false

Examples

Basic Usage

Quick Switching

Environment Details

development

  • Description: Development mode with debugging enabled, no caching

  • Use for: Local development and debugging

  • Features:

    • Debug information displayed

    • Error details shown

    • No query caching

    • No view caching

    • Hot reloading enabled

testing

  • Description: Testing mode for running automated tests

  • Use for: Running test suites and CI/CD pipelines

  • Features:

    • Consistent test environment

    • Test database connections

    • Predictable caching behavior

    • Error details available

production

  • Description: Production mode with caching enabled, debugging disabled

  • Use for: Live production servers

  • Features:

    • Query caching enabled

    • View caching enabled

    • Debug information hidden

    • Optimized performance

    • Error pages for users

maintenance

  • Description: Maintenance mode to show maintenance page

  • Use for: During deployments or maintenance windows

  • Features:

    • Maintenance page displayed

    • Admin access still available

    • Database migrations possible

    • Public access restricted

How It Works

  1. Configuration Storage: Environment settings are stored in:

    • .env file (WHEELS_ENV variable)

    • Environment variables

    • Application configuration

  2. Runtime Detection: The environment is determined by:

    • Checking server environment variables

    • Reading .env file

    • Defaulting to development

  3. Change Process:

    • Updates .env file

    • Optionally reloads application

    • Changes take effect immediately (if reloaded)

Output Examples

Current Environment Display

Environment List

Configuration

Using .env File

Create or modify .env in your project root:

Using Environment Variables

Set system environment variable:

Precedence Order

  1. System environment variables (highest priority)

  2. .env file

  3. Default (development)

Best Practices

  1. Development Workflow

    • Use development for local work

    • Switch to testing before running tests

    • Never use development in production

  2. Production Deployment

    • Always use production environment

    • Set via environment variables for security

    • Disable reload after deployment

  3. Testing Strategy

    • Use testing for automated tests

    • Ensure consistent test environment

    • Reset between test runs

  4. Maintenance Windows

    • Switch to maintenance during deployments

    • Provide clear maintenance messages

    • Switch back to production when complete

Troubleshooting

Environment not changing

  1. Check if server needs restart: wheels server restart

  2. Verify .env file permissions

  3. Check for system environment variable conflicts

Server required for some operations

  • Some environment checks require running server

  • Start server first: wheels server start

  • File-based changes work without server

Permission issues

  • Ensure write access to .env file

  • Check directory permissions

  • Run with appropriate user privileges

Security Notes

  • Don't commit .env files with production settings

  • Use environment variables in production

  • Restrict access to environment commands in production

  • Log environment changes for audit trails

Related Commands

  • - Reload application

  • - Restart server

  • - Test in different environments

See Also

# Create new application
wheels new blog

# Generate complete CRUD scaffolding
wheels scaffold name=post properties=title:string,content:text,published:boolean

# Generate individual components
wheels generate model user
wheels generate controller users --rest
wheels generate view users index
# Database operations
wheels db create              # Create database
wheels db setup              # Create + migrate + seed
wheels db reset              # Drop + recreate + migrate + seed
wheels db shell              # Interactive database shell
wheels db dump               # Backup database
wheels db restore backup.sql # Restore from backup

# Migrations
wheels dbmigrate create table posts
wheels dbmigrate latest
wheels db status            # Check migration status
wheels db rollback          # Rollback migrations
# Run all tests
wheels test run

# Advanced testing with TestBox CLI
wheels test:all              # Run all tests
wheels test:unit             # Run unit tests only
wheels test:integration      # Run integration tests only
wheels test:watch            # Watch mode
wheels test:coverage         # Generate coverage reports
# Watch for file changes
wheels watch

# Reload application
wheels reload development

# Analyze code
wheels analyze code
wheels security scan
# macOS/Linux
curl -fsSl https://downloads.ortussolutions.com/debs/gpg | sudo apt-key add -
or
  brew install commandbox

# Windows
choco install commandbox
box install wheels-cli
wheels new myapp
cd myapp
box server start
wheels --help
wheels generate --help
wheels dbmigrate --help

3.0.x

2.5+

5.0+

Lucee 5.3+, Adobe 2018+

2.0.x

2.0-2.4

4.0+

Lucee 5.2+, Adobe 2016+

Command Reference
Core Commands
Code Generation
Database Commands
Testing Commands
Configuration
And more...
Quick Start Guide
Service Architecture
Creating Custom Commands
Template System
Testing Guide
Migration Guide
Security Guide
Performance Guide
Configuration Options
Template Variables
Exit Codes
Environment Variables
https://wheels.dev/docs
https://github.com/wheels-dev/wheels
CFML Slack
https://groups.google.com/forum/#!forum/wheels
Contributing Guide
All Commands
Quick Start
Creating Commands
Service Architecture
Testing Guide
LICENSE
Quick Start Guide
Command Reference
# macOS/Linux
curl -fsSl https://downloads.ortussolutions.com/debs/gpg | sudo apt-key add -
echo "deb https://downloads.ortussolutions.com/debs/noarch /" | sudo tee -a /etc/apt/sources.list.d/commandbox.list
sudo apt-get update && sudo apt-get install commandbox

# Windows (PowerShell as Admin)
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
choco install commandbox
box install wheels-cli
wheels new blog
cd blog
<cfset set(dataSourceName="blog_development")>
wheels new blog --setupH2
# If using external database (MySQL, PostgreSQL, etc.)
wheels db create
box server start
wheels scaffold name=post properties=title:string,content:text,published:boolean
wheels dbmigrate latest
<cfscript>
    // Add this line
    resources("posts");
</cfscript>
wheels reload
wheels watch
# Run all tests
wheels test run

# Watch mode
wheels test run --watch

# Specific tests
wheels test run tests/models/PostTest.cfc
# Generate comment model
wheels generate model comment --properties="author:string,content:text,postId:integer" \
  --belongs-to="post"

# Update post model
wheels generate property post comments --has-many

# Generate comments controller
wheels generate controller comments --rest

# Run migration
wheels dbmigrate latest
# Generate user model
wheels scaffold name=user properties=email:string,password:string,admin:boolean

# Generate session controller
wheels generate controller sessions new,create,delete

# Run migrations
wheels dbmigrate latest
# Generate API resource
wheels generate api-resource product --properties="name:string,price:decimal"

# Or convert existing to API
wheels generate controller api/posts --api
# Generate specific views
wheels generate view posts featured
wheels generate view users profile

# Add layouts
echo '<cfoutput><!DOCTYPE html>...</cfoutput>' > views/layout.cfm
# Create tables
wheels dbmigrate create table products

# Add columns
wheels dbmigrate create column products featured

# Create indexes
wheels dbmigrate create blank add_index_to_products
# After creating a model
wheels generate test model post

# After creating a controller
wheels generate test controller posts
# Development
wheels reload development

# Testing
wheels reload testing

# Production
wheels reload production
git init
git add .
git commit -m "Initial Wheels application"
/db/sql/
/logs/
/temp/
.env
tail -f logs/wheels.log
<cfset set(showDebugInformation=true)>
box server start port=3001
# Check datasource
box server info
box server show
# Check status
wheels db status

# Run specific migration
wheels dbmigrate exec 20240120000000

# Or rollback and try again
wheels db rollback
# Complete reset (careful - destroys all data!)
wheels db reset --force
# CLI shell
wheels db shell

# Web console (H2 only)
wheels db shell --web
# Create application
wheels new myblog --setupH2
cd myblog

# Generate blog structure
wheels scaffold post title:string,slug:string,content:text,publishedAt:datetime
wheels scaffold author name:string,email:string,bio:text
wheels generate model comment author:string,email:string,content:text,postId:integer \
  --belongs-to=post

# Update associations
wheels generate property post authorId:integer --belongs-to=author
wheels generate property post comments --has-many
wheels generate property author posts --has-many

# Add routes
echo '<cfset resources("posts")>' >> config/routes.cfm
echo '<cfset resources("authors")>' >> config/routes.cfm

# Setup and seed database
wheels db setup --seed-count=10

# Start development
wheels server start
wheels watch

# Visit http://localhost:3000/posts
Service Architecture
Testing Guide
Migration Guide
Wheels Documentation
GitHub Discussions
CFML Slack
wheels test coverage [options]

--type

Type of tests to run: app, core, or plugin

app

--servername

Name of server to reload

(current server)

--reload

Force a reload of wheels

false

--debug

Show debug info

false

--output-dir

Directory to output the coverage report

tests/coverageReport

wheels test coverage
wheels test coverage --type=core
wheels test coverage --output-dir=reports/coverage
wheels test coverage --reload --debug
wheels test coverage --servername=myapp
File: /app/models/User.cfc
Lines: 156/200 (78%)
Functions: 45/50 (90%)
Branches: 120/150 (80%)
Statements: 890/1000 (89%)
wheels test coverage --format=html
wheels test coverage --format=json
{
  "summary": {
    "lines": { "total": 1000, "covered": 850, "percent": 85 },
    "functions": { "total": 100, "covered": 92, "percent": 92 },
    "branches": { "total": 200, "covered": 160, "percent": 80 }
  },
  "files": {
    "/app/models/User.cfc": {
      "lines": { "total": 200, "covered": 156, "percent": 78 }
    }
  }
}
wheels test coverage --format=xml
wheels test coverage --format=console
Code Coverage Report
===================

Overall Coverage: 85.3%

File                          Lines    Funcs    Branch   Stmt
---------------------------- -------- -------- -------- --------
/app/models/User.cfc           78.0%    85.0%    72.0%    80.0%
/app/models/Order.cfc          92.0%    95.0%    88.0%    90.0%
/app/controllers/Users.cfc     85.0%    90.0%    82.0%    86.0%

Uncovered Files:
- /app/models/Legacy.cfc (0%)
- /app/helpers/Deprecated.cfc (0%)
wheels test coverage --threshold=80
{
  "thresholds": {
    "global": 80,
    "lines": 85,
    "functions": 90,
    "branches": 75,
    "statements": 85
  }
}
{
  "thresholds": {
    "global": 80,
    "files": {
      "/app/models/User.cfc": 90,
      "/app/models/Order.cfc": 95
    }
  }
}
{
  "include": [
    "app/models/**/*.cfc",
    "app/controllers/**/*.cfc"
  ],
  "exclude": [
    "app/models/Legacy.cfc",
    "**/*Test.cfc"
  ],
  "reporters": ["html", "json"],
  "reportDir": "./coverage",
  "thresholds": {
    "global": 80
  },
  "watermarks": {
    "lines": [50, 80],
    "functions": [50, 80],
    "branches": [50, 80],
    "statements": [50, 80]
  }
}
- name: Run tests with coverage
  run: |
    wheels test coverage --format=xml --threshold=80 --fail-on-low
    
- name: Upload coverage
  uses: codecov/codecov-action@v3
  with:
    file: ./coverage/coverage.xml
#!/bin/bash
wheels test coverage --threshold=80 --fail-on-low
wheels test coverage --format=badge > coverage-badge.svg
# Coverage for changed files only
wheels test coverage --since=HEAD~1
# Generate trend data
wheels test coverage --save-baseline

# Compare with baseline
wheels test coverage --compare-baseline
# From multiple test runs
wheels test coverage --merge coverage1.json coverage2.json
wheels test
wheels test run
wheels test debug
Testing Best Practices
wheels environment [action] [value] [options]
# Show current environment
wheels environment

# Set environment to production
wheels environment set production

# Set environment without reload
wheels environment set development --reload=false

# List all available environments
wheels environment list
# Shortcut syntax - if action matches an environment name
wheels environment development   # Same as: wheels environment set development
wheels environment production    # Same as: wheels environment set production
wheels environment testing       # Same as: wheels environment set testing
Current Wheels Environment
=========================

Environment: development
Wheels Version: 2.5.0
Data Source: myapp_dev
Server: Lucee 5.3.10.120

Environment Settings:
  cacheQueries: false
  cachePartials: false
  cachePages: false
  cacheActions: false
  showDebugInformation: true
  showErrorInformation: true
Available Wheels Environments
============================

development (current)
  Description: Development mode with debugging enabled, no caching
  Use for: Local development and debugging

testing
  Description: Testing mode for running automated tests
  Use for: Running test suites and CI/CD pipelines

production
  Description: Production mode with caching enabled, debugging disabled
  Use for: Live production servers

maintenance
  Description: Maintenance mode to show maintenance page
  Use for: During deployments or maintenance windows
WHEELS_ENV=production
export WHEELS_ENV=production
wheels reload
wheels server restart
wheels console
Wheels Configuration Guide
Environment Variables Documentation

wheels dbmigrate latest

Run all pending database migrations to bring database to latest version.

Synopsis

wheels dbmigrate latest

Alias: wheels db latest

Description

The wheels dbmigrate latest command runs all pending migrations in chronological order, updating your database schema to the latest version. This is the most commonly used migration command.

Parameters

None.

How It Works

  1. Retrieves current database version and latest version

  2. Executes dbmigrate exec with the latest version

  3. Automatically runs dbmigrate info after completion

  4. Updates version tracking after successful migration

Example Output

╔═══════════════════════════════════════════════╗
║          Running Pending Migrations           ║
╚═══════════════════════════════════════════════╝

Current Version: 20240110090000
Target Version: 20240125160000

Migrating...

→ Running 20240115120000_create_orders_table.cfc
  Creating table: orders
  Adding indexes...
  ✓ Success (0.125s)

→ Running 20240120140000_add_status_to_orders.cfc
  Adding column: status to orders
  ✓ Success (0.089s)

→ Running 20240125160000_create_categories_table.cfc
  Creating table: categories
  Adding foreign keys...
  ✓ Success (0.143s)

╔═══════════════════════════════════════════════╗
║            Migration Complete                 ║
╚═══════════════════════════════════════════════╝

Previous Version: 20240110090000
Current Version:  20240125160000
Migrations Run:   3
Total Time:       0.357s

Migration Execution

Each migration file must contain:

component extends="wheels.migrator.Migration" {

    function up() {
        // Database changes go here
        transaction {
            // Use transaction for safety
        }
    }

    function down() {
        // Rollback logic (optional)
        transaction {
            // Reverse the up() changes
        }
    }

}

Transaction Safety

Migrations run within transactions:

  • All changes in a migration succeed or fail together

  • Database remains consistent

  • Failed migrations can be retried

Common Migration Operations

Create Table

function up() {
    transaction {
        t = createTable("products");
        t.string("name");
        t.decimal("price");
        t.timestamps();
        t.create();
    }
}

Add Column

function up() {
    transaction {
        addColumn(table="users", column="email", type="string");
    }
}

Add Index

function up() {
    transaction {
        addIndex(table="users", columns="email", unique=true);
    }
}

Modify Column

function up() {
    transaction {
        changeColumn(table="products", column="price", type="decimal", precision=10, scale=2);
    }
}

Error Handling

If a migration fails:

→ Running 20240120140000_add_status_to_orders.cfc
  Adding column: status to orders
  ✗ ERROR: Column 'status' already exists

Migration failed at version 20240115120000
Error: Column 'status' already exists in table 'orders'

To retry: Fix the migration file and run 'wheels dbmigrate latest' again
To skip: Run 'wheels dbmigrate up' to run one at a time

Best Practices

  1. Test migrations locally first

    # Test on development database
    wheels dbmigrate latest
    
    # Verify
    wheels dbmigrate info
  2. Backup before production migrations

    # Backup database
    mysqldump myapp_production > backup.sql
    
    # Run migrations
    wheels dbmigrate latest
  3. Use transactions

    function up() {
        transaction {
            // All changes here
        }
    }
  4. Make migrations reversible

    function down() {
        transaction {
            dropTable("products");
        }
    }

Environment-Specific Migrations

Migrations can check environment:

function up() {
    transaction {
        // Always run
        addColumn(table="users", column="lastLogin", type="datetime");
        
        // Development only
        if (get("environment") == "development") {
            // Add test data
            sql("INSERT INTO users (email) VALUES ('[email protected]')");
        }
    }
}

Checking Migrations

Preview migrations before running:

# Check what would run
wheels dbmigrate info

# Review migration files
ls app/migrator/migrations/

Performance Considerations

For large tables:

function up() {
    transaction {
        // Add index concurrently (if supported)
        if (get("databaseType") == "postgresql") {
            sql("CREATE INDEX CONCURRENTLY idx_users_email ON users(email)");
        } else {
            addIndex(table="users", columns="email");
        }
    }
}

Continuous Integration

Add to CI/CD pipeline:

# .github/workflows/deploy.yml
- name: Run migrations
  run: |
    wheels dbmigrate latest
    wheels test app

Rollback Strategy

If issues occur after migration:

  1. Use down migrations

    wheels dbmigrate down
    wheels dbmigrate down
  2. Restore from backup

    mysql myapp_production < backup.sql
  3. Fix and retry

    • Fix migration file

    • Run wheels dbmigrate latest

Common Issues

Timeout on Large Tables

function up() {
    // Increase timeout for large operations
    setting requestTimeout="300";
    
    transaction {
        // Long running operation
    }
}

Foreign Key Constraints

function up() {
    transaction {
        // Disable checks temporarily
        sql("SET FOREIGN_KEY_CHECKS=0");
        
        // Make changes
        dropTable("orders");
        
        // Re-enable
        sql("SET FOREIGN_KEY_CHECKS=1");
    }
}

See Also

  • wheels dbmigrate info - Check migration status

  • wheels dbmigrate up - Run single migration

  • wheels dbmigrate down - Rollback migration

  • wheels dbmigrate create blank - Create migration

wheels env list

List all available environments for your Wheels application.

Synopsis

wheels env list [options]

Description

The wheels env list command displays all configured environments in your Wheels application. It shows environment details, current active environment, and configuration status.

Options

Option
Description
Default

--format

Output format (table, json, yaml)

table

--verbose

Show detailed configuration

false

--check

Validate environment configurations

false

--filter

Filter by environment type

All

--sort

Sort by (name, type, modified)

name

--help

Show help information

Examples

List all environments

wheels env list

Show detailed information

wheels env list --verbose

Output as JSON

wheels env list --format=json

Check environment validity

wheels env list --check

Filter production environments

wheels env list --filter=production

Output Example

Basic Output

Available Environments
=====================

  NAME          TYPE         DATABASE           STATUS
  development * Development  wheels_dev         ✓ Active
  testing       Testing      wheels_test        ✓ Valid
  staging       Staging      wheels_staging     ✓ Valid
  production    Production   wheels_prod        ✓ Valid
  qa            Custom       wheels_qa          ⚠ Issues

* = Current environment

Verbose Output

Available Environments
=====================

development * [Active]
  Type:        Development
  Database:    wheels_dev
  Datasource:  wheels_development
  Debug:       Enabled
  Cache:       Disabled
  Config:      /config/development/settings.cfm
  Modified:    2024-01-10 14:23:45
  
testing
  Type:        Testing
  Database:    wheels_test
  Datasource:  wheels_testing
  Debug:       Enabled
  Cache:       Disabled
  Config:      /config/testing/settings.cfm
  Modified:    2024-01-08 09:15:22

staging
  Type:        Staging
  Database:    wheels_staging
  Datasource:  wheels_staging
  Debug:       Partial
  Cache:       Enabled
  Config:      /config/staging/settings.cfm
  Modified:    2024-01-12 16:45:00

JSON Output Format

{
  "environments": [
    {
      "name": "development",
      "type": "Development",
      "active": true,
      "database": "wheels_dev",
      "datasource": "wheels_development",
      "debug": true,
      "cache": false,
      "configPath": "/config/development/settings.cfm",
      "lastModified": "2024-01-10T14:23:45Z",
      "status": "valid"
    },
    {
      "name": "production",
      "type": "Production",
      "active": false,
      "database": "wheels_prod",
      "datasource": "wheels_production",
      "debug": false,
      "cache": true,
      "configPath": "/config/production/settings.cfm",
      "lastModified": "2024-01-12T16:45:00Z",
      "status": "valid"
    }
  ],
  "current": "development",
  "total": 5
}

Environment Status

Status Indicators

  • ✓ Valid - Configuration is valid and working

  • ✓ Active - Currently active environment

  • ⚠ Issues - Configuration issues detected

  • ✗ Invalid - Configuration errors

Validation Checks

When using --check:

  1. Configuration file exists

  2. Syntax is valid

  3. Database connection works

  4. Required settings present

Environment Types

Standard Types

  • Development: Local development

  • Testing: Automated testing

  • Staging: Pre-production

  • Production: Live environment

Custom Types

  • User-defined environments

  • Special purpose configs

  • Client-specific setups

Filtering Options

By Type

# Production environments only
wheels env list --filter=production

# Development environments
wheels env list --filter=development

By Status

# Valid environments only
wheels env list --filter=valid

# Environments with issues
wheels env list --filter=issues

By Pattern

# Environments containing "prod"
wheels env list --filter="*prod*"

Sorting Options

By Name

wheels env list --sort=name

By Type

wheels env list --sort=type

By Last Modified

wheels env list --sort=modified

Integration

Script Usage

# Get current environment
current=$(wheels env list --format=json | jq -r '.current')

# List all environment names
wheels env list --format=json | jq -r '.environments[].name'

CI/CD Usage

# Verify environment exists
if wheels env list | grep -q "staging"; then
    wheels env switch staging
fi

Environment Details

When using --verbose, shows:

  1. Configuration:

    • Config file path

    • Last modified date

    • File size

  2. Database:

    • Database name

    • Datasource name

    • Connection status

  3. Settings:

    • Debug mode

    • Cache settings

    • Custom configurations

  4. Validation:

    • Syntax check

    • Connection test

    • Dependencies

Troubleshooting

No Environments Listed

  • Check /config/ directory

  • Verify environment.cfm exists

  • Run wheels env setup to create

Invalid Environment

  • Check configuration syntax

  • Verify database credentials

  • Test database connection

Missing Current Environment

  • Check WHEELS_ENV variable

  • Verify environment.cfm logic

  • Set environment explicitly

Export Capabilities

Export Configuration

# Export all environments
wheels env list --format=json > environments.json

# Export for documentation
wheels env list --format=markdown > ENVIRONMENTS.md

Environment Comparison

# Compare environments
wheels env list --compare=development,production

Best Practices

  1. Regular Checks: Validate environments periodically

  2. Documentation: Keep environment purposes clear

  3. Consistency: Use consistent naming

  4. Cleanup: Remove unused environments

  5. Security: Don't expose production details

Notes

  • Current environment marked with asterisk (*)

  • Invalid environments shown but marked

  • Verbose mode may expose sensitive data

  • JSON format useful for automation

See Also

  • wheels env - Environment management overview

  • wheels env setup - Setup new environment

  • wheels env switch - Switch environments

  • wheels config list - List configuration

wheels runner

Execute a script file in the Wheels application context.

Synopsis

wheels runner <file> [options]

Description

The wheels runner command executes CFML script files with full access to your Wheels application context. This is useful for running data migrations, maintenance tasks, batch processing, and other scripts that need access to your models and application functionality.

Scripts are executed on the server via HTTP, ensuring they run with the proper application context, database connections, and Wheels framework features.

Arguments

file

  • Type: String

  • Required: Yes

  • Description: Path to the script file to execute (.cfm, .cfc, or .cfs)

  • Example: wheels runner scripts/migrate-data.cfm

Options

environment

  • Type: String

  • Default: development

  • Description: Environment to run in (development, testing, production)

  • Example: wheels runner script.cfm environment=production

--verbose

  • Type: Boolean flag

  • Default: false

  • Description: Show detailed output including execution time

  • Example: wheels runner script.cfm --verbose

params

  • Type: String (JSON format)

  • Description: Additional parameters to pass to the script

  • Example: wheels runner import.cfm params='{"source":"data.csv","limit":100}'

Examples

Basic Usage

# Run a migration script
wheels runner scripts/migrate-users.cfm

# Run in production environment
wheels runner scripts/cleanup.cfm environment=production

# Run with parameters
wheels runner scripts/import.cfm params='{"file":"products.csv","dryRun":true}'

# Run with verbose output
wheels runner scripts/process-orders.cfm --verbose

Script Examples

Data Migration Script

<!--- scripts/migrate-legacy-users.cfm --->
<cfscript>
// Access passed parameters
var dryRun = structKeyExists(request.scriptParams, "dryRun") ? request.scriptParams.dryRun : false;
var batchSize = structKeyExists(request.scriptParams, "batchSize") ? request.scriptParams.batchSize : 100;

writeOutput("<h3>Legacy User Migration</h3>");
writeOutput("Dry run: #dryRun#<br>");
writeOutput("Batch size: #batchSize#<br><br>");

// Query legacy database
var legacyUsers = request.query("
    SELECT * FROM legacy_users 
    WHERE migrated = 0 
    LIMIT #batchSize#
");

writeOutput("Found #legacyUsers.recordCount# users to migrate<br><br>");

var migrated = 0;
for (var legacyUser in legacyUsers) {
    try {
        if (!dryRun) {
            // Create user in new system
            var newUser = request.model("User").create(
                email: legacyUser.email_address,
                firstName: legacyUser.fname,
                lastName: legacyUser.lname,
                createdAt: legacyUser.created_date
            );
            
            // Mark as migrated
            request.query("
                UPDATE legacy_users 
                SET migrated = 1, new_id = #newUser.id# 
                WHERE id = #legacyUser.id#
            ");
        }
        
        migrated++;
        writeOutput("✓ Migrated: #legacyUser.email_address#<br>");
        
    } catch (any e) {
        writeOutput("✗ Failed: #legacyUser.email_address# - #e.message#<br>");
    }
}

writeOutput("<br><strong>Migration complete: #migrated# users processed</strong>");
</cfscript>

Maintenance Script

<!--- scripts/cleanup-temp-files.cfm --->
<cfscript>
var daysOld = structKeyExists(request.scriptParams, "days") ? request.scriptParams.days : 7;
var verbose = request.scriptVerbose;

writeOutput("Cleaning temporary files older than #daysOld# days...<br><br>");

// Clean up old session files
var cutoffDate = dateAdd('d', -daysOld, now());
var deletedCount = 0;

// Find and delete old upload records
var oldUploads = request.model("TempUpload").findAll(
    where="createdAt < '#cutoffDate#'"
);

for (var upload in oldUploads) {
    // Delete physical file
    if (fileExists(upload.filePath)) {
        fileDelete(upload.filePath);
        if (verbose) {
            writeOutput("Deleted file: #upload.filePath#<br>");
        }
    }
    
    // Delete database record
    request.model("TempUpload").deleteByKey(upload.id);
    deletedCount++;
}

writeOutput("<br>Deleted #deletedCount# temporary files<br>");

// Clean up orphaned cart items
var orphanedItems = request.query("
    SELECT COUNT(*) as total 
    FROM cart_items 
    WHERE updatedAt < '#cutoffDate#' 
    AND orderId IS NULL
").total;

if (orphanedItems > 0) {
    request.query("
        DELETE FROM cart_items 
        WHERE updatedAt < '#cutoffDate#' 
        AND orderId IS NULL
    ");
    writeOutput("Removed #orphanedItems# orphaned cart items<br>");
}

writeOutput("<br><strong>Cleanup complete!</strong>");
</cfscript>

Report Generation Script

<!--- scripts/generate-monthly-report.cfm --->
<cfscript>
// Get report parameters
var month = structKeyExists(request.scriptParams, "month") ? request.scriptParams.month : month(now());
var year = structKeyExists(request.scriptParams, "year") ? request.scriptParams.year : year(now());
var emailTo = structKeyExists(request.scriptParams, "emailTo") ? request.scriptParams.emailTo : "";

writeOutput("<h2>Monthly Report - #monthAsString(month)# #year#</h2>");

// Calculate date range
var startDate = createDate(year, month, 1);
var endDate = dateAdd('m', 1, startDate);

// Get statistics
var stats = {};

// New users
stats.newUsers = request.model("User").count(
    where="createdAt >= '#startDate#' AND createdAt < '#endDate#'"
);

// Orders
var orderData = request.query("
    SELECT 
        COUNT(*) as totalOrders,
        SUM(total) as revenue,
        AVG(total) as avgOrderValue
    FROM orders
    WHERE createdAt >= '#startDate#' 
    AND createdAt < '#endDate#'
    AND status = 'completed'
");

stats.orders = orderData.totalOrders;
stats.revenue = orderData.revenue ?: 0;
stats.avgOrderValue = orderData.avgOrderValue ?: 0;

// Display report
writeOutput("<h3>Summary</h3>");
writeOutput("<ul>");
writeOutput("<li>New Users: #stats.newUsers#</li>");
writeOutput("<li>Total Orders: #stats.orders#</li>");
writeOutput("<li>Revenue: #dollarFormat(stats.revenue)#</li>");
writeOutput("<li>Average Order Value: #dollarFormat(stats.avgOrderValue)#</li>");
writeOutput("</ul>");

// Send email if requested
if (len(emailTo)) {
    // Use Wheels email functionality
    writeOutput("<br>Sending report to: #emailTo#...<br>");
    // Email sending logic here
}

writeOutput("<br><em>Report generated on #dateTimeFormat(now())#</em>");
</cfscript>

Script Context

Scripts executed by the runner have access to:

Available Functions

  • request.model(name) - Access Wheels models

  • request.query(sql) - Execute SQL queries

  • request.scriptParams - Access passed parameters

  • request.scriptVerbose - Check if verbose mode is enabled

Available Variables

  • All Wheels helper functions

  • Application scope

  • Standard CFML functions

Example Accessing Script Context

<cfscript>
// Access parameters
var limit = structKeyExists(request.scriptParams, "limit") ? request.scriptParams.limit : 10;

// Use models
var users = request.model("User").findAll(maxRows=limit);

// Direct queries
var stats = request.query("SELECT COUNT(*) as total FROM posts");

// Use helpers
writeOutput("Found #pluralize(users.recordCount, 'user')#<br>");
</cfscript>

Best Practices

  1. Error Handling: Always include try/catch blocks for critical operations

  2. Dry Run Mode: Implement a dryRun parameter for destructive operations

  3. Progress Output: Use writeOutput() to show progress for long-running scripts

  4. Transactions: Use database transactions for data consistency

  5. Logging: Log important operations for audit trails

  6. Idempotency: Make scripts safe to run multiple times

Security Considerations

  • Scripts run with full application permissions

  • Validate and sanitize any external input

  • Be cautious with production environment scripts

  • Consider implementing confirmation prompts for destructive operations

Troubleshooting

"Script file not found"

  • Check the file path is correct

  • Use relative paths from the application root

  • Ensure file extension is .cfm, .cfc, or .cfs

"Server must be running"

  • Start the server: wheels server start

  • Check server status: wheels server status

Script errors

  • Check syntax in your script file

  • Verify model and table names

  • Test queries separately in console first

Related Commands

  • wheels console - Interactive REPL for testing code

  • wheels environment - Manage environment settings

  • wheels generate migration - Create database migrations

See Also

  • Wheels Model Documentation

  • CommandBox Task Runners

wheels analyze

Base command for code analysis and quality checks.

Synopsis

wheels analyze [subcommand] [options]

Description

The wheels analyze command provides comprehensive code analysis tools for Wheels applications. It helps identify code quality issues, performance bottlenecks, security vulnerabilities, and provides actionable insights for improvement.

Subcommands

Command
Description

code

Analyze code quality and patterns

performance

Analyze performance characteristics

security

Security vulnerability analysis (deprecated)

Options

Option
Description

--help

Show help information

--version

Show version information

Direct Usage

When called without subcommands, runs all analyses:

wheels analyze

This executes:

  1. Code quality analysis

  2. Performance analysis

  3. Security scanning (if not deprecated)

Examples

Run all analyses

wheels analyze

Quick analysis with summary

wheels analyze --summary

Analyze specific directory

wheels analyze --path=./models

Generate analysis report

wheels analyze --report=html --output=./analysis-report

Analysis Overview

The analyze command examines:

Code Quality

  • Coding standards compliance

  • Code complexity metrics

  • Duplication detection

  • Best practices adherence

Performance

  • N+1 query detection

  • Slow query identification

  • Memory usage patterns

  • Cache effectiveness

Security

  • SQL injection risks

  • XSS vulnerabilities

  • Insecure configurations

  • Outdated dependencies

Output Example

Wheels Code Analysis Report
==========================

Code Quality
------------
✓ Files analyzed: 234
✓ Total lines: 12,456
⚠ Issues found: 23
  - High priority: 3
  - Medium priority: 12
  - Low priority: 8

Performance
-----------
✓ Queries analyzed: 156
⚠ Potential N+1 queries: 4
⚠ Slow queries detected: 2
✓ Cache hit ratio: 87%

Security (Deprecated)
--------------------
! Security analysis has been deprecated
! Use 'wheels security scan' instead

Summary Score: B+ (82/100)

Analysis Configuration

Configure via .wheels-analysis.json:

{
  "analyze": {
    "exclude": [
      "vendor/**",
      "tests/**",
      "*.min.js"
    ],
    "rules": {
      "complexity": {
        "maxComplexity": 10,
        "maxDepth": 4
      },
      "duplication": {
        "minLines": 5,
        "threshold": 0.05
      }
    },
    "performance": {
      "slowQueryThreshold": 1000,
      "cacheTargetRatio": 0.8
    }
  }
}

Integration with CI/CD

GitHub Actions Example

- name: Run code analysis
  run: |
    wheels analyze --format=json --output=analysis.json
    wheels analyze --format=badge > analysis-badge.svg

Quality Gates

Set minimum scores:

wheels analyze --min-score=80 --fail-on-issues=high

Report Formats

HTML Report

wheels analyze --report=html
  • Interactive dashboard

  • Detailed issue breakdown

  • Code snippets with issues

JSON Report

wheels analyze --format=json
  • Machine-readable format

  • CI/CD integration

  • Custom processing

Markdown Report

wheels analyze --format=markdown
  • Documentation-friendly

  • Pull request comments

  • Wiki integration

Analysis Rules

Built-in Rules

  • CFScript best practices

  • SQL query optimization

  • Security patterns

  • Memory management

Custom Rules

Create custom rules in .wheels-analysis-rules/:

module.exports = {
  name: "custom-rule",
  check: function(file, content) {
    // Rule implementation
  }
};

Baseline

Track improvement over time:

# Create baseline
wheels analyze --save-baseline

# Compare with baseline
wheels analyze --compare-baseline

Ignoring Issues

Inline Comments

// wheels-analyze-ignore-next-line
complexQuery = ormExecuteQuery(sql, params);

/* wheels-analyze-ignore-start */
// Complex code block
/* wheels-analyze-ignore-end */

Configuration File

{
  "ignore": [
    {
      "rule": "sql-injection",
      "file": "legacy/*.cfc"
    }
  ]
}

Performance Tips

  1. Incremental Analysis: Analyze only changed files

  2. Parallel Processing: Use multiple cores

  3. Cache Results: Reuse analysis for unchanged files

  4. Focused Scans: Target specific directories

Use Cases

  1. Pre-commit Hooks: Catch issues before commit

  2. Pull Request Checks: Automated code review

  3. Technical Debt: Track and reduce over time

  4. Team Standards: Enforce coding guidelines

  5. Performance Monitoring: Identify bottlenecks

Best Practices

  1. Run analysis regularly

  2. Fix high-priority issues first

  3. Set realistic quality gates

  4. Track metrics over time

  5. Integrate with development workflow

Troubleshooting

Analysis Takes Too Long

  • Exclude vendor directories

  • Use incremental mode

  • Increase memory allocation

Too Many False Positives

  • Tune rule sensitivity

  • Add specific ignores

  • Update rule definitions

Notes

  • First run may take longer due to initial scanning

  • Results are cached for performance

  • Some rules require database connection

  • Memory usage scales with codebase size

See Also

  • wheels analyze code - Code quality analysis

  • wheels analyze performance - Performance analysis

  • wheels security scan - Security scanning

  • wheels test - Run tests

wheels dbmigrate create column

Generate a migration file for adding columns to an existing database table.

Synopsis

wheels dbmigrate create column name=<table_name> data-type=<type> column-name=<column> [options]

Alias: wheels db create column

Description

The dbmigrate create column command generates a migration file that adds a column to an existing database table. It supports standard column types and various options for column configuration.

Parameters

Parameter
Type
Required
Default
Description

name

string

Yes

-

The name of the database table to modify

data-type

string

Yes

-

The column type to add

column-name

string

No

-

The column name to add

default

any

No

-

The default value to set for the column

--null

boolean

No

true

Should the column allow nulls

limit

number

No

-

The character limit of the column

precision

number

No

-

The precision of the numeric column

scale

number

No

-

The scale of the numeric column

Column Types

  • string - VARCHAR(255)

  • text - TEXT/CLOB

  • integer - INTEGER

  • biginteger - BIGINT

  • float - FLOAT

  • decimal - DECIMAL

  • boolean - BOOLEAN/BIT

  • date - DATE

  • time - TIME

  • datetime - DATETIME/TIMESTAMP

  • timestamp - TIMESTAMP

  • binary - BLOB/BINARY

Migration File Naming

The generated migration file will be named with a timestamp and description:

[timestamp]_create_column_[columnname]_in_[tablename]_table.cfc

Example:

20240125160000_create_column_email_in_user_table.cfc

Examples

Add a simple column

wheels dbmigrate create column name=user data-type=string column-name=email

Add column with default value

wheels dbmigrate create column name=user data-type=boolean column-name=is_active default=true

Add nullable column with limit

wheels dbmigrate create column name=user data-type=string column-name=bio --null=true limit=500

Add decimal column with precision

wheels dbmigrate create column name=product data-type=decimal column-name=price precision=10 scale=2

Generated Migration Example

For the command:

wheels dbmigrate create column name=user data-type=string column-name=phone --null=true

Generates:

component extends="wheels.migrator.Migration" hint="create column phone in user table" {

    function up() {
        transaction {
            addColumn(table="user", columnType="string", columnName="phone", null=true);
        }
    }

    function down() {
        transaction {
            removeColumn(table="user", column="phone");
        }
    }

}

Use Cases

Adding User Preferences

Add preference column to user table:

# Create separate migrations for each column
wheels dbmigrate create column name=user data-type=boolean column-name=newsletter_subscribed default=true
wheels dbmigrate create column name=user data-type=string column-name=theme_preference default="light"

Adding Audit Fields

Add tracking column to any table:

wheels dbmigrate create column name=product data-type=integer column-name=last_modified_by --null=true
wheels dbmigrate create column name=product data-type=datetime column-name=last_modified_at --null=true

Adding Price Fields

Add decimal columns for pricing:

wheels dbmigrate create column name=product data-type=decimal column-name=price precision=10 scale=2 default=0
wheels dbmigrate create column name=product data-type=decimal column-name=cost precision=10 scale=2

Best Practices

1. Consider NULL Values

For existing tables with data, make new columns nullable or provide defaults:

# Good - nullable
wheels dbmigrate create column name=user data-type=text column-name=bio --null=true

# Good - with default
wheels dbmigrate create column name=user data-type=string column-name=status default="active"

# Bad - will fail if table has data (not nullable, no default)
wheels dbmigrate create column name=user data-type=string column-name=required_field --null=false

2. Use Appropriate Types

Choose the right column type for your data:

# For short text
wheels dbmigrate create column name=user data-type=string column-name=username limit=50

# For long text
wheels dbmigrate create column name=post data-type=text column-name=content

# For money
wheels dbmigrate create column name=invoice data-type=decimal column-name=amount precision=10 scale=2

3. One Column Per Migration

This command creates one column at a time:

# Create separate migrations for related columns
wheels dbmigrate create column name=customer data-type=string column-name=address_line1
wheels dbmigrate create column name=customer data-type=string column-name=city
wheels dbmigrate create column name=customer data-type=string column-name=state limit=2

4. Plan Your Schema

Think through column requirements before creating:

  • Data type and size

  • Null constraints

  • Default values

  • Index requirements

Advanced Scenarios

Adding Foreign Keys

Add foreign key columns with appropriate types:

# Add foreign key column
wheels dbmigrate create column name=order data-type=integer column-name=customer_id

# Then create index in separate migration
wheels dbmigrate create blank name=add_order_customer_id_index

Complex Column Types

For special column types, use blank migrations:

# Create blank migration for custom column types
wheels dbmigrate create blank name=add_user_preferences_json
# Then manually add the column with custom SQL

Common Pitfalls

1. Non-Nullable Without Default

# This will fail if table has data
wheels dbmigrate create column name=user data-type=string column-name=required_field --null=false

# Do this instead
wheels dbmigrate create column name=user data-type=string column-name=required_field default="pending"

2. Changing Column Types

This command adds columns, not modifies them:

# Wrong - trying to change existing column type
wheels dbmigrate create column name=user data-type=integer column-name=age

# Right - use blank migration for modifications
wheels dbmigrate create blank name=change_user_age_to_integer

Notes

  • The migration includes automatic rollback with removeColumn()

  • Column order in down() is reversed for proper rollback

  • Always test migrations with data in development

  • Consider the impact on existing queries and code

Related Commands

  • wheels dbmigrate create table - Create new tables

  • wheels dbmigrate create blank - Create custom migrations

  • wheels dbmigrate remove table - Remove tables

  • wheels dbmigrate up - Run migrations

  • wheels dbmigrate down - Rollback migrations

wheels generate controller

Generate a controller with actions and optional views.

Synopsis

Description

The wheels generate controller command creates a new controller CFC file with specified actions and optionally generates corresponding view files. It supports both traditional and RESTful controller patterns.

Arguments

Argument
Description
Default

Options

Option
Description
Default

Examples

Basic controller

Creates:

  • /controllers/Products.cfc with index action

  • /views/products/index.cfm

Controller with multiple actions

Creates controller with all CRUD actions and corresponding views.

RESTful controller

Automatically generates all RESTful actions:

  • index - List all products

  • show - Show single product

  • new - New product form

  • create - Create product

  • edit - Edit product form

  • update - Update product

  • delete - Delete product

API controller

Creates:

  • /controllers/api/Products.cfc with JSON responses

  • No view files

Custom actions

Generated Code

Basic Controller

RESTful Controller

API Controller

View Generation

Views are automatically generated for non-API controllers:

index.cfm

Naming Conventions

  • Controller names: PascalCase, typically plural (Products, Users)

  • Action names: camelCase (index, show, createProduct)

  • File locations:

    • Controllers: /controllers/

    • Nested: /controllers/admin/Products.cfc

    • Views: /views/{controller}/

Routes Configuration

Add routes in /config/routes.cfm:

Traditional Routes

RESTful Resources

Nested Resources

Testing

Generate tests alongside controllers:

Best Practices

  1. Use plural names for resource controllers

  2. Keep controllers focused on single resources

  3. Use --rest for standard CRUD operations

  4. Implement proper error handling

  5. Add authentication in init() method

  6. Use filters for common functionality

Common Patterns

Authentication Filter

Pagination

Search

See Also

  • - Generate models

  • - Generate views

  • - Generate complete CRUD

  • - Generate controller tests

wheels env setup

Setup a new environment configuration for your Wheels application.

Synopsis

Description

The wheels env setup command creates and configures new environments for your Wheels application. It generates environment-specific configuration files, database settings, and initializes the environment structure.

Arguments

Argument
Description
Default

Options

Option
Description
Default

Examples

Setup basic environment

Setup with custom database

Copy from production settings

Setup with specific options

Skip database setup

What It Does

  1. Creates Configuration Directory:

  2. Generates Settings File:

    • Database configuration

    • Environment-specific settings

    • Debug and cache options

    • Security settings

  3. Sets Up Database (unless skipped):

    • Creates database

    • Configures datasource

    • Tests connection

  4. Updates Environment Registry:

    • Adds to available environments

    • Sets up environment detection

Generated Configuration

Example config/staging/settings.cfm:

Environment Types

Development

  • Full debugging

  • No caching

  • Detailed errors

  • Hot reload

Testing

  • Test database

  • Debug enabled

  • Isolated data

  • Fast cleanup

Staging

  • Production-like

  • Some debugging

  • Performance testing

  • Pre-production validation

Production

  • No debugging

  • Full caching

  • Error handling

  • Optimized performance

Custom

Create specialized environments:

Database Configuration

Automatic Setup

Custom Database

Database URL

Environment Variables

The command sets up support for:

Configuration Inheritance

Environments can inherit settings:

Validation

After setup, the command validates:

  1. Configuration file syntax

  2. Database connectivity

  3. Directory permissions

  4. Environment detection

Environment Detection

Configure how environment is detected:

Best Practices

  1. Naming Convention: Use clear, consistent names

  2. Base Selection: Choose appropriate base environment

  3. Security: Use strong reload passwords

  4. Documentation: Document environment purposes

  5. Testing: Test configuration before use

Advanced Configuration

Multiple Databases

Load Balancing

Feature Flags

Troubleshooting

Database Creation Failed

  • Check database permissions

  • Verify connection settings

  • Use --skip-database and create manually

Configuration Errors

  • Check syntax in settings.cfm

  • Verify file permissions

  • Review error logs

Environment Not Detected

  • Check environment.cfm logic

  • Verify server variables

  • Test detection rules

Migration

From Existing Environment

Use Cases

  1. Multi-Stage Pipeline: dev → staging → production

  2. Feature Testing: Isolated feature environments

  3. Performance Testing: Dedicated performance environment

  4. Client Demos: Separate demo environments

  5. A/B Testing: Multiple production variants

Notes

  • Environment names should be lowercase

  • Avoid spaces in environment names

  • Each environment needs unique database

  • Restart application after setup

  • Test thoroughly before using

See Also

  • - Environment management overview

  • - List environments

  • - Switch environments

  • - Configuration management

wheels env switch

Switch to a different environment in your Wheels application.

Synopsis

Description

The wheels env switch command changes the active environment for your Wheels application. It updates configuration files, environment variables, and optionally restarts services to apply the new environment settings.

Arguments

Argument
Description
Default

Options

Option
Description
Default

Examples

Switch to staging

Switch with application restart

Force switch without validation

Switch with backup

Quiet switch for scripts

What It Does

  1. Validates Target Environment:

    • Checks if environment exists

    • Verifies configuration

    • Tests database connection

  2. Updates Configuration:

    • Sets WHEELS_ENV variable

    • Updates .wheels-env file

    • Modifies environment.cfm if needed

  3. Applies Changes:

    • Clears caches

    • Reloads configuration

    • Restarts services (if requested)

  4. Verifies Switch:

    • Confirms environment active

    • Checks application health

    • Reports status

Output Example

Environment File Updates

.wheels-env

Before:

After:

Environment Variables

Updates system environment:

Validation Process

Before switching, validates:

  1. Configuration:

    • File exists

    • Syntax valid

    • Required settings present

  2. Database:

    • Connection works

    • Tables accessible

    • Migrations current

  3. Dependencies:

    • Required services available

    • File permissions correct

    • Resources accessible

Switch Strategies

Safe Switch (Default)

  • Full validation

  • Graceful transition

  • Rollback on error

Fast Switch

  • Skip validation

  • Immediate switch

  • Use with caution

Zero-Downtime Switch

  • Prepare new environment

  • Switch load balancer

  • No service interruption

Backup and Restore

Create Backup

Restore from Backup

Manual Restore

Service Management

With Restart

Restarts:

  • Application server

  • Cache services

  • Background workers

Service-Specific

Pre/Post Hooks

Configure in .wheels-cli.json:

Environment-Specific Actions

Development → Production

Production → Development

Integration

CI/CD Pipeline

Deployment Scripts

Rollback

If switch fails or causes issues:

Troubleshooting

Switch Failed

  • Check validation errors

  • Verify target environment exists

  • Use --force if necessary

Application Not Responding

  • Check service status

  • Review error logs

  • Manually restart services

Database Connection Issues

  • Verify credentials

  • Check network access

  • Test connection manually

Best Practices

  1. Always Validate: Don't skip checks in production

  2. Use Backups: Enable backup for critical switches

  3. Test First: Switch in staging before production

  4. Monitor After: Check application health post-switch

  5. Document Changes: Log environment switches

Security Considerations

  • Production switches require confirmation

  • Sensitive configs protected

  • Audit trail maintained

  • Access controls enforced

Notes

  • Some changes require application restart

  • Database connections may need reset

  • Cached data cleared on switch

  • Background jobs may need restart

See Also

  • - Environment management overview

  • - List environments

  • - Setup environments

  • - Reload application

Requirements

What you need to know and have installed before you start programming in Wheels.

We can identify 3 different types of requirements that you should be aware of:

  • Project Requirements. Is Wheels a good fit for your project?

  • Developer Requirements. Do you have the knowledge and mindset to program effectively in Wheels?

  • System Requirements. Is your server ready for Wheels?

Project Requirements

Before you start learning Wheels and making sure all the necessary software is installed on your computer, you really need to take a moment and think about the project you intend to use Wheels on. Is it a ten page website that won't be updated very often? Is it a space flight simulator program for NASA? Is it something in between?

Most websites are, at their cores, simple data manipulation applications. You fetch a row, make some updates to it, stick it back in the database and so on. This is the "target market" for Wheels--simple CRUD (create, read, update, delete) website applications.

A simple ten page website won't do much data manipulation, so you don't need Wheels for that (or even ColdFusion in some cases). A flight simulator program will do so much more than simple CRUD work, so in that case, Wheels is a poor match for you (and so perhaps, is ColdFusion).

If your website falls somewhere in between these two extreme examples, then read on. If not, go look for another programming language and framework. ;)

Another thing worth noting right off the bat (and one that ties in with the simple CRUD reasoning above) is that Wheels takes a very data-centric approach to the development process. What we mean by that is that it should be possible to visualize and implement the database design early on in the project's life cycle. So, if you're about to embark on a project with an extensive period of object oriented analysis and design which, as a last step almost, looks at how to persist objects, then you should probably also look for another framework.

Still reading?

Good!

Moving on...

Developer Requirements

Yes, there are actually some things you should familiarize yourself with before starting to use Wheels. Don't worry though. You don't need to be an expert on any on of them. A basic understanding is good enough.

  • CFML. You should know CFML, the ColdFusion programming language. (Surprise!)

  • Object Oriented Programming. You should grasp the concept of object oriented programming and how it applies to CFML.

  • Model-View-Controller. You should know the theory behind the Model-View-Controller development pattern.

CFML

Simply the best web development language in the world! The best way to learn it, in our humble opinion, is to get the free developer edition of Adobe ColdFusion, buy Ben Forta's ColdFusion Web Application Construction Kit series, and start coding using your programming editor of choice. Remember it's not just the commercial Adobe offering that's available; offers an excellent open source alternative. Using is a great and simple way to get a local development environment of your choice up and running quickly.

Object Oriented Programming (OOP)

This is a programming methodology that uses constructs called objects to design applications. Objects model real world entities in your application. OOP is based on several techniques including inheritance, modularity, polymorphism, and encapsulation. Most of these techniques are supported in CFML, making it a fairly functional object oriented language. At the most basic level, a .cfc file in CFML is a class, and you create an instance of a class by using the CreateObject function or the <cfobject> tag.

Trying to squeeze an explanation of object oriented programming and how it's used in CFML into a few sentences is impossible, and a detailed overview of it is outside the scope of this chapter. There is lots of high quality information online, so go ahead and Google it.

Model-View-Controller

Model-View-Controller, or MVC for short, is a way to structure your code so that it is broken down into 3 easy-to-manage pieces:

  • Model. Just another name for the representation of data, usually a database table.

  • View. What the user sees and interacts with (a web page in our case).

  • Controller. The behind-the-scenes guy that's coordinating everything.

MVC is how Wheels structures your code for you. As you start working with Wheels applications, you'll see that most of the code you write is very nicely separated into one of these 3 categories.

System Requirements

Wheels requires that you use one of these CFML engines:

  • 2018 / 2021 / 2023

  • 5.2.1.9+ / 6

Operating Systems

Your ColdFusion or Lucee engine can be installed on Windows, Mac, UNIX, or Linux—they all work just fine.

Web Servers

You also need a web server. Wheels runs on all popular web servers, including Apache, Microsoft IIS, Jetty, and the JRun or Tomcat web server that ships with Adobe ColdFusion. Some web servers support URL rewriting out of the box, some support the cgi.PATH_INFO variable which is used to achieve partial rewriting, and some don't have support for either. For local development, we strongly encourage the use of .

Don't worry though. Wheels will adopt to your setup and run just fine, but the URLs that it creates might differ a bit. You can read more about this in the chapter.

Database Engines

Finally, to build any kind of meaningful website application, you will likely interact with a database. These are the currently supported databases:

  • SQL Server 7+

  • MySQL 5+ *

  • PostgreSQL 8.4+

  • H2 1.4+

MySQL

  • Wheels maybe incompatible with newer MySQL JDBC drivers. It is recommended you downgrade the driver to version 5.1.x for full ORM functionality.

  • If you're using MySQL 5.7.5+ you should be aware that the ONLY_FULL_GROUP_BY setting is enabled by default and it's currently not compatible with the Wheels ORM. However, you can work around this by either disabling the ONLY_FULL_GROUP_BY setting or using ANY_VALUE() in a calculated property. You can read more about it .

  • We also recommend using the InnoDB engine if you want to work.

  • MySQL 4 is not supported.

OK, hopefully this chapter didn't scare you too much. You can move on knowing that you have the basic knowledge needed, the software to run Wheels, and a suitable project to start with.

wheels generate controller [name] [actions] [options]
wheels g controller [name] [actions] [options]

name

Name of the controller to create (usually plural)

Required

actions

Actions to generate (comma-delimited, default: CRUD for REST)

--rest

Generate RESTful controller with CRUD actions

false

--api

Generate API controller (no view-related actions)

false

description

Controller description

--force

Overwrite existing files

false

wheels generate controller products
wheels generate controller products actions="index,show,new,create,edit,update,delete"
wheels generate controller products --rest
wheels generate controller api/products --api
wheels generate controller reports actions="dashboard,monthly,yearly,export"
component extends="Controller" {

    function init() {
        // Constructor
    }

    function index() {
        products = model("Product").findAll();
    }

}
component extends="Controller" {

    function init() {
        // Constructor
    }

    function index() {
        products = model("Product").findAll();
    }

    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 created successfully");
            redirectTo(action="index");
        } else {
            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 updated successfully");
            redirectTo(action="index");
        } else {
            renderView(action="edit");
        }
    }

    function delete() {
        product = model("Product").findByKey(params.key);
        if (IsObject(product) && product.delete()) {
            flashInsert(success="Product deleted successfully");
        } else {
            flashInsert(error="Could not delete product");
        }
        redirectTo(action="index");
    }

}
component extends="Controller" {

    function init() {
        provides("json");
    }

    function index() {
        products = model("Product").findAll();
        renderWith(products);
    }

    function show() {
        product = model("Product").findByKey(params.key);
        if (IsObject(product)) {
            renderWith(product);
        } else {
            renderWith({error: "Product not found"}, status=404);
        }
    }

    function create() {
        product = model("Product").new(params.product);
        if (product.save()) {
            renderWith(product, status=201);
        } else {
            renderWith({errors: product.allErrors()}, status=422);
        }
    }

    function update() {
        product = model("Product").findByKey(params.key);
        if (IsObject(product) && product.update(params.product)) {
            renderWith(product);
        } else {
            renderWith({errors: product.allErrors()}, status=422);
        }
    }

    function delete() {
        product = model("Product").findByKey(params.key);
        if (IsObject(product) && product.delete()) {
            renderWith({message: "Product deleted"});
        } else {
            renderWith({error: "Could not delete"}, status=400);
        }
    }

}
<h1>Products</h1>

<p>#linkTo(text="New Product", action="new")#</p>

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Actions</th>
        </tr>
    </thead>
    <tbody>
        <cfloop query="products">
            <tr>
                <td>#products.name#</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>
<cfset get(name="products", to="products##index")>
<cfset get(name="product", to="products##show")>
<cfset post(name="products", to="products##create")>
<cfset resources("products")>
<cfset namespace("api")>
    <cfset resources("products")>
</cfset>
wheels generate controller products --rest
wheels generate test controller products
function init() {
    filters(through="authenticate", except="index,show");
}

private function authenticate() {
    if (!session.isLoggedIn) {
        redirectTo(controller="sessions", action="new");
    }
}
function index() {
    products = model("Product").findAll(
        page=params.page ?: 1,
        perPage=25,
        order="createdAt DESC"
    );
}
function index() {
    if (StructKeyExists(params, "q")) {
        products = model("Product").findAll(
            where="name LIKE :search OR description LIKE :search",
            params={search: "%#params.q#%"}
        );
    } else {
        products = model("Product").findAll();
    }
}
wheels generate model
wheels generate view
wheels scaffold
wheels generate test
wheels env setup [name] [options]

name

Environment name (e.g., staging, qa, production)

Required

--base

Base environment to copy from

development

--database

Database name

wheels_[name]

--datasource

CF datasource name

wheels_[name]

--debug

Enable debug mode

false

--cache

Enable caching

Based on name

--reload-password

Password for reload

Random

--skip-database

Skip database creation

false

--force

Overwrite existing environment

false

--help

Show help information

wheels env setup staging
wheels env setup qa --database=wheels_qa_db --datasource=qa_datasource
wheels env setup staging --base=production
wheels env setup production --debug=false --cache=true --reload-password=secret123
wheels env setup testing --skip-database
/config/[environment]/
└── settings.cfm
<cfscript>
// Environment: staging
// Generated: 2024-01-15 10:30:45

// Database settings
set(dataSourceName="wheels_staging");

// Environment settings
set(environment="staging");
set(showDebugInformation=true);
set(showErrorInformation=true);

// Caching
set(cacheFileChecking=false);
set(cacheImages=false);
set(cacheModelInitialization=false);
set(cacheControllerInitialization=false);
set(cacheRoutes=false);
set(cacheActions=false);
set(cachePages=false);
set(cachePartials=false);
set(cacheQueries=false);

// Security
set(reloadPassword="generated_secure_password");

// URLs
set(urlRewriting="partial");

// Custom settings for staging
set(sendEmailOnError=true);
set(errorEmailAddress="[email protected]");
</cfscript>
wheels env setup performance-testing --base=production --cache=false
wheels env setup staging
# Creates: wheels_staging database
# Datasource: wheels_staging
wheels env setup staging \
  --database=staging_db \
  --datasource=myapp_staging
wheels env setup production \
  --database-url="mysql://user:pass@host:3306/db"
# .env.staging
WHEELS_ENV=staging
WHEELS_DATASOURCE=wheels_staging
WHEELS_DEBUG=true
WHEELS_CACHE=false
DATABASE_URL=mysql://localhost/wheels_staging
// config/staging/settings.cfm
<cfinclude template="../production/settings.cfm">

// Override specific settings
set(showDebugInformation=true);
set(cacheQueries=false);
// config/environment.cfm
if (cgi.server_name contains "staging") {
    set(environment="staging");
} else if (cgi.server_name contains "qa") {
    set(environment="qa");
} else {
    set(environment="production");
}
wheels env setup reporting \
  --database=wheels_reporting \
  --read-database=wheels_replica
wheels env setup production \
  --servers=web1,web2,web3 \
  --load-balancer=nginx
// In settings.cfm
set(features={
    newCheckout: true,
    betaAPI: false,
    debugToolbar: true
});
# Export existing config
wheels env export production > prod-config.json

# Import to new environment
wheels env setup staging --from-config=prod-config.json
wheels env
wheels env list
wheels env switch
wheels config
wheels env switch [name] [options]

name

Target environment name

Required

--check

Validate before switching

true

--restart

Restart application after switch

false

--backup

Backup current environment

false

--force

Force switch even with issues

false

--quiet

Suppress output

false

--help

Show help information

wheels env switch staging
wheels env switch production --restart
wheels env switch testing --force
wheels env switch production --backup
wheels env switch development --quiet
Switching environment...

Current: development
Target:  staging

✓ Validating staging environment
✓ Configuration valid
✓ Database connection successful
✓ Updating environment settings
✓ Clearing caches
✓ Environment switched successfully

New Environment: staging
Database: wheels_staging
Debug: Enabled
Cache: Partial
development
staging
export WHEELS_ENV=staging
export WHEELS_DATASOURCE=wheels_staging
wheels env switch production
wheels env switch staging --force --no-check
wheels env switch production --strategy=blue-green
wheels env switch production --backup
# Creates: .wheels-env-backup-20240115-103045
wheels env restore --from=.wheels-env-backup-20240115-103045
# If switch fails
cp .wheels-env-backup-20240115-103045 .wheels-env
wheels reload
wheels env switch production --restart
wheels env switch staging --restart-services=app,cache
{
  "env": {
    "switch": {
      "pre": [
        "wheels test run --quick",
        "git stash"
      ],
      "post": [
        "wheels dbmigrate latest",
        "wheels cache clear",
        "npm run build"
      ]
    }
  }
}
wheels env switch production
# Warning: Switching from development to production
# - Debug will be disabled
# - Caching will be enabled
# - Error details will be hidden
# Continue? (y/N)
wheels env switch development
# Warning: Switching from production to development
# - Debug will be enabled
# - Caching will be disabled
# - Sensitive data may be exposed
# Continue? (y/N)
- name: Switch to staging
  run: |
    wheels env switch staging --check
    wheels test run
    wheels deploy exec staging
#!/bin/bash
# deploy.sh

# Switch environment
wheels env switch $1 --backup

# Run migrations
wheels dbmigrate latest

# Clear caches
wheels cache clear

# Verify
wheels env | grep $1
# Automatic rollback
wheels env switch production --auto-rollback

# Manual rollback
wheels env switch:rollback

# Force previous environment
wheels env switch development --force
wheels env
wheels env list
wheels env setup
wheels reload
Lucee
CommandBox
Adobe ColdFusion
Lucee
CommandBox
URL Rewriting
here
Transactions

wheels generate app-wizard

Interactive wizard for creating a new Wheels application.

Synopsis

wheels generate app-wizard [options]
wheels g app-wizard [options]

Description

The wheels generate app-wizard command provides an interactive, step-by-step wizard for creating a new Wheels application. It guides you through all configuration options with helpful prompts and explanations, making it ideal for beginners or when you want to explore all available options.

Options

Option
Description
Default

--expert

Show advanced options

false

--skip-install

Skip dependency installation

false

--help

Show help information

Interactive Process

Step 1: Application Name

? What is the name of your application? › myapp
  • Must be alphanumeric with hyphens/underscores

  • Used for directory and configuration names

Step 2: Template Selection

? Which template would you like to use? › 
  ❯ Base - Minimal Wheels application
    Base@BE - Backend-only (no views)
    HelloWorld - Simple example application
    HelloDynamic - Database-driven example
    HelloPages - Static pages example

Step 3: Target Directory

? Where should the application be created? › ./myapp
  • Defaults to ./{app-name}

  • Can specify absolute or relative path

Step 4: Database Configuration

? Would you like to configure a database? (Y/n) › Y
? Database type? › 
  ❯ H2 (Embedded)
    MySQL
    PostgreSQL
    SQL Server
    Custom

Step 5: Additional Features

? Select additional features: › 
  ◯ Bootstrap CSS framework
  ◯ jQuery library
  ◯ Sample authentication
  ◯ API documentation
  ◯ Docker configuration

Step 6: CFML Engine

? Which CFML engine will you use? › 
  ❯ Lucee 5
    Lucee 6
    Adobe ColdFusion 2018
    Adobe ColdFusion 2021
    Adobe ColdFusion 2023

Step 7: Security Settings

? Set reload password (leave blank for 'wheels'): › ****
? Enable CSRF protection? (Y/n) › Y
? Enable secure cookies? (y/N) › N

Step 8: Review & Confirm

Application Configuration:
─────────────────────────
Name:       myapp
Template:   Base
Directory:  ./myapp
Database:   H2 (Embedded)
Features:   Bootstrap, jQuery
Engine:     Lucee 5
Reload PWD: ****

? Create application with these settings? (Y/n) › Y

Wizard Flow

graph TD
    A[Start Wizard] --> B[Enter App Name]
    B --> C[Select Template]
    C --> D[Choose Directory]
    D --> E[Configure Database]
    E --> F[Select Features]
    F --> G[Choose CFML Engine]
    G --> H[Security Settings]
    H --> I[Review Configuration]
    I --> J{Confirm?}
    J -->|Yes| K[Create Application]
    J -->|No| B
    K --> L[Install Dependencies]
    L --> M[Show Next Steps]

Expert Mode

Enable expert mode for additional options:

wheels generate app-wizard --expert

Additional prompts in expert mode:

  • Custom server ports

  • JVM settings

  • Environment-specific configurations

  • Advanced routing options

  • Custom plugin repositories

  • Build tool integration

Configuration Profiles

Save and reuse configurations:

Save Profile

? Save this configuration as a profile? (y/N) › Y
? Profile name: › enterprise-api

Use Profile

wheels generate app-wizard profile=enterprise-api

List Profiles

wheels generate app-wizard --list-profiles

Feature Descriptions

Bootstrap CSS

  • Includes Bootstrap 5.x

  • Responsive grid system

  • Pre-styled components

  • Example layouts

jQuery Library

  • Latest jQuery version

  • AJAX helpers configured

  • Example usage in views

Sample Authentication

  • User model with secure passwords

  • Login/logout controllers

  • Session management

  • Protected routes example

API Documentation

  • OpenAPI/Swagger setup

  • Auto-generated documentation

  • Interactive API explorer

Docker Configuration

  • Multi-stage Dockerfile

  • docker-compose.yml

  • Development & production configs

  • Database containers

Post-Creation Steps

After successful creation, the wizard displays:

✓ Application created successfully!

Next steps:
1. cd myapp
2. box install (or run manually if skipped)
3. box server start
4. Visit http://localhost:3000

Additional commands:
- wheels test          Run tests
- wheels dbmigrate up  Run migrations
- wheels generate      Generate code
- wheels help          Show all commands

Error Handling

The wizard handles common issues:

  • Invalid names: Suggests valid alternatives

  • Existing directories: Offers to overwrite or choose new location

  • Missing dependencies: Provides installation instructions

  • Configuration errors: Allows editing before creation

Validation Rules

Application Name

  • Start with letter

  • Alphanumeric plus - and _

  • No spaces or special characters

  • Not a reserved word

Directory Path

  • Must be writable

  • Cannot be system directory

  • Warns if not empty

Passwords

  • Minimum 6 characters

  • Strength indicator

  • Confirmation required

Customization

Custom Templates

Add templates to ~/.wheels/templates/:

~/.wheels/templates/
├── my-template/
│   ├── template.json
│   ├── config/
│   ├── controllers/
│   └── views/

Template Configuration

template.json:

{
  "name": "My Custom Template",
  "description": "Custom template for specific use case",
  "author": "Your Name",
  "version": "1.0.0",
  "prompts": [
    {
      "name": "apiVersion",
      "message": "API version?",
      "default": "v1"
    }
  ]
}

Integration

CI/CD Pipeline

Generate with CI configuration:

wheels generate app-wizard ci=github

Includes:

  • .github/workflows/test.yml

  • Build configuration

  • Deployment scripts

IDE Configuration

Generate with IDE files:

wheels generate app-wizard ide=vscode

Includes:

  • .vscode/settings.json

  • .vscode/launch.json

  • .editorconfig

Best Practices

  1. Run wizard in empty directory

  2. Choose descriptive application names

  3. Configure database early

  4. Enable security features for production

  5. Save profiles for team consistency

  6. Review all settings before confirming

Common Use Cases

API-Only Application

  • Choose Base@BE template

  • Skip Bootstrap/jQuery

  • Enable API documentation

  • Configure CORS settings

Traditional Web Application

  • Choose Base template

  • Include Bootstrap/jQuery

  • Add sample authentication

  • Configure session management

Microservice

  • Choose Base@BE template

  • Configure Docker

  • Set specific ports

  • Minimal dependencies

Troubleshooting

Wizard Freezes

  • Check terminal compatibility

  • Try --no-interactive mode

  • Check system resources

Installation Fails

  • Verify internet connection

  • Check CommandBox version

  • Try --skip-install and install manually

Configuration Issues

  • Review generated .wheels-cli.json

  • Check server.json settings

  • Verify file permissions

See Also

  • wheels generate app - Non-interactive app generation

  • wheels init - Initialize existing directory

  • wheels scaffold - Generate CRUD scaffolding

Database Management Overview

The Wheels CLI provides comprehensive database management commands that make it easy to create, manage, and maintain your application's database throughout the development lifecycle.

Overview

Database management in Wheels is divided into two main categories:

  1. Database Commands (wheels db) - High-level database operations

  2. Migration Commands (wheels dbmigrate) - Schema versioning and changes

This guide covers the database management commands. For migration-specific operations, see the migrations guide.

Database Lifecycle Commands

Creating a Database

The wheels db create command creates a new database based on your datasource configuration:

# Create database using default datasource
wheels db create

# Create database for specific datasource
wheels db create --datasource=myapp_dev

# Create database for specific environment
wheels db create --environment=production

Note: The datasource must already be configured in your CFML server admin. The command will create the database itself but not the datasource configuration.

Dropping a Database

The wheels db drop command removes an existing database:

# Drop database (with confirmation)
wheels db drop

# Drop database without confirmation
wheels db drop --force

# Drop specific datasource
wheels db drop --datasource=myapp_dev

Warning: This is a destructive operation. Always backup important data before dropping a database.

Database Setup

The wheels db setup command performs a complete database initialization:

# Full setup: create + migrate + seed
wheels db setup

# Setup without seeding
wheels db setup --skip-seed

# Setup with custom seed count
wheels db setup --seed-count=20

This is ideal for setting up a new development environment or initializing a test database.

Database Reset

The wheels db reset command completely rebuilds your database:

# Reset database (drop + create + migrate + seed)
wheels db reset

# Reset without confirmation
wheels db reset --force

# Reset without seeding
wheels db reset --skip-seed

# Reset specific environment
wheels db reset --environment=testing

Important: This command will destroy all existing data. Use with caution, especially in production environments.

Data Management

Seeding the Database

The wheels db seed command populates your database with test or sample data:

# Seed with default settings (5 records per model)
wheels db seed

# Seed with custom record count
wheels db seed --count=10

# Seed specific models only
wheels db seed --models=user,post,comment

# Seed from a JSON file
wheels db seed --dataFile=seeds/test-data.json

Example seed data file format:

{
  "users": [
    {
      "name": "John Doe",
      "email": "[email protected]",
      "role": "admin"
    },
    {
      "name": "Jane Smith",
      "email": "[email protected]",
      "role": "user"
    }
  ],
  "posts": [
    {
      "title": "Welcome Post",
      "content": "This is the first post",
      "userId": 1
    }
  ]
}

Status and Information Commands

Checking Migration Status

The wheels db status command shows the current state of your migrations:

# Show migration status in table format
wheels db status

# Show status in JSON format
wheels db status --format=json

# Show only pending migrations
wheels db status --pending

Output example:

| Version              | Description                      | Status   | Applied At         |
|---------------------|----------------------------------|----------|-------------------|
| 20231201120000      | CreateUsersTable                 | applied  | 2023-12-01 12:30  |
| 20231202140000      | AddEmailToUsers                  | applied  | 2023-12-02 14:15  |
| 20231203160000      | CreatePostsTable                 | pending  | Not applied       |

Checking Database Version

The wheels db version command shows the current schema version:

# Show current version
wheels db version

# Show detailed version information
wheels db version --detailed

Migration Management

Rolling Back Migrations

The wheels db rollback command reverses previously applied migrations:

# Rollback last migration
wheels db rollback

# Rollback multiple migrations
wheels db rollback --steps=3

# Rollback to specific version
wheels db rollback --target=20231201120000

# Force rollback without confirmation
wheels db rollback --steps=5 --force

Backup and Restore

Creating Database Backups

The wheels db dump command exports your database:

# Basic dump (auto-named with timestamp)
wheels db dump

# Dump to specific file
wheels db dump --output=backup.sql

# Dump schema only (no data)
wheels db dump --schema-only

# Dump data only (no schema)
wheels db dump --data-only

# Dump specific tables
wheels db dump --tables=users,posts,comments

# Dump with compression
wheels db dump --output=backup.sql.gz --compress

Restoring from Backups

The wheels db restore command imports a database dump:

# Restore from SQL file
wheels db restore backup.sql

# Restore compressed file
wheels db restore backup.sql.gz --compressed

# Clean restore (drop existing objects first)
wheels db restore backup.sql --clean

# Force restore without confirmation
wheels db restore backup.sql --force

Common Workflows

Setting Up a New Development Environment

# 1. Clone the repository
git clone https://github.com/myapp/repo.git
cd repo

# 2. Install dependencies
box install

# 3. Setup database
wheels db setup

# 4. Start the server
server start

Resetting Development Database

# Quick reset with fresh data
wheels db reset --force

# Or manually:
wheels db drop --force
wheels db create
wheels dbmigrate latest
wheels db seed --count=10

Production Database Backup

# Create timestamped backup
wheels db dump --compress

# Or with custom filename
wheels db dump --output=prod-backup-$(date +%Y%m%d).sql.gz --compress

Migrating Between Environments

# Export from development
wheels db dump --output=dev-data.sql --environment=development

# Import to staging
wheels db restore dev-data.sql --environment=staging

Interactive Database Shell

The wheels db shell command provides direct access to your database's interactive shell:

# Launch CLI shell for current datasource
wheels db shell

# Launch web-based console (H2 only)
wheels db shell --web

# Use specific datasource
wheels db shell --datasource=myapp_dev

# Execute single command
wheels db shell --command="SELECT COUNT(*) FROM users"

Database-Specific Shells

H2 Database:

# CLI shell
wheels db shell

# Web console (opens in browser)
wheels db shell --web

MySQL:

# Opens mysql client
wheels db shell
# Connects to: mysql -h host -P port -u user -p database

PostgreSQL:

# Opens psql client
wheels db shell
# Connects to: psql -h host -p port -U user -d database

SQL Server:

# Opens sqlcmd client
wheels db shell
# Connects to: sqlcmd -S server -d database -U user

Shell Requirements

The database shell commands require the appropriate database client tools to be installed:

  • H2: No additional installation needed (included with Lucee)

  • MySQL: Install mysql client

  • PostgreSQL: Install psql client

  • SQL Server: Install sqlcmd client

Database Support

The database commands support multiple database engines:

  • MySQL/MariaDB - Full support for all operations

  • PostgreSQL - Full support for all operations

  • SQL Server - Full support for most operations

  • H2 - Full support (auto-created databases)

  • Oracle - Limited support (basic operations)

Configuration

Database commands use the datasource configuration from your Wheels application. You can override settings using command parameters:

# Use specific datasource
wheels db create --datasource=myapp_test

# Use specific environment
wheels db setup --environment=testing

Safety Features

  1. Confirmation Prompts - Destructive operations require confirmation

  2. Force Flag - Use --force to skip confirmations in scripts

  3. Environment Detection - Extra warnings for production environments

  4. Transaction Support - Operations are wrapped in transactions where possible

Troubleshooting

Common Issues

Datasource Not Found

Error: Datasource 'myapp' not found in server configuration

Solution: Create the datasource in your CFML server admin first.

Database Already Exists

Error: Database already exists: myapp_dev

Solution: Use wheels db drop first or use wheels db reset instead.

Permission Denied

Error: Access denied for user 'myuser'@'localhost'

Solution: Ensure the database user has CREATE/DROP privileges.

Missing Database Tools

Error: mysqldump not found in PATH

Solution: Install database client tools for dump/restore operations.

Best Practices

  1. Always backup before destructive operations

    wheels db dump --output=backup-before-reset.sql
    wheels db reset
  2. Use environment-specific datasources

    • myapp_dev for development

    • myapp_test for testing

    • myapp_prod for production

  3. Automate with scripts

    #!/bin/bash
    # reset-db.sh
    wheels db dump --output=backups/pre-reset-$(date +%s).sql
    wheels db reset --force
    echo "Database reset complete"
  4. Version control your seeds

    • Keep seed files in db/seeds/

    • Use environment-specific seed files

    • Document seed data structure

Related Commands

  • wheels dbmigrate - Database migration commands

  • wheels test - Test database operations

  • wheels generate model - Generate models with migrations

wheels db seed

Generate and populate test data.

Synopsis

wheels db seed [options]

Description

The db seed command populates your database with test data. This is useful for development environments, testing scenarios, and demo installations. The command will generate sample data based on your models.

Parameters

Parameter
Type
Required
Default
Description

file

string

No

"seed"

Path to seed file (without .cfm extension)

environment

string

No

current

Environment to seed

Examples

Basic seeding (uses default seed file)

wheels db seed

Use custom seed file

wheels db seed file=demo

Seed specific environment

wheels db seed environment=testing

Use seed file from subdirectory

wheels db seed file=development/users

Seed File Location

Seed files should be placed in the db/ directory of your application:

app/
  db/
    seed.cfm         # Default seed file
    demo.cfm         # Custom seed file
    development/     # Environment-specific seeds
      users.cfm
      products.cfm

Seed File Structure

Basic Seed File (db/seed.cfm)

<cfscript>
// db/seed.cfm

// Create admin user
user = model("user").create(
    username = "admin",
    email = "[email protected]",
    password = "password123",
    role = "admin"
);

// Create sample categories  
categories = [
    {name: "Electronics", slug: "electronics"},
    {name: "Books", slug: "books"},
    {name: "Clothing", slug: "clothing"}
];

for (category in categories) {
    model("category").create(category);
}

// Create sample products
electronicsCategory = model("category").findOne(where="slug='electronics'");

products = [
    {
        name: "Laptop",
        price: 999.99,
        category_id: electronicsCategory.id,
        in_stock: true
    },
    {
        name: "Smartphone", 
        price: 699.99,
        category_id: electronicsCategory.id,
        in_stock: true
    }
];

for (product in products) {
    model("product").create(product);
}

writeOutput("Seed data created successfully!");
</cfscript>

Function-Based Seed Files

// db/development/users.cfm
<cfscript>
// Admin users
admins = [
    {username: "admin", email: "[email protected]", role: "admin"},
    {username: "moderator", email: "[email protected]", role: "moderator"}
];

for (admin in admins) {
    admin.password = hash("password123");
    model("user").create(admin);
}

// Regular users
for (i = 1; i <= 50; i++) {
    model("user").create(
        username = "user#i#",
        email = "user#i#@example.com",
        password = hash("password123"),
        created_at = dateAdd("d", -randRange(1, 365), now())
    );
}

writeOutput("Created admin and sample users");
</cfscript>

Use Cases

Development Environment Setup

Create consistent development data:

# Reset and seed development database
wheels dbmigrate reset
wheels dbmigrate latest
wheels db seed

Testing Data

Prepare test database:

# Seed test environment
wheels db seed environment=testing

# Run tests
wheels test run

Demo Data

Create demonstration data:

# Load demo dataset
wheels db seed file=demo

Multiple Seed Files

Organize seeds by purpose:

# Seed users
wheels db seed file=development/users

# Seed products
wheels db seed file=development/products

Advanced Seeding Patterns

Using Random Data

<cfscript>
// Generate random users
firstNames = ["John", "Jane", "Bob", "Alice", "Charlie", "Diana"];
lastNames = ["Smith", "Johnson", "Williams", "Brown", "Jones"];
cities = ["New York", "Los Angeles", "Chicago", "Houston", "Phoenix"];

for (i = 1; i <= 100; i++) {
    model("customer").create(
        first_name = firstNames[randRange(1, arrayLen(firstNames))],
        last_name = lastNames[randRange(1, arrayLen(lastNames))],
        email = "customer#i#@example.com",
        city = cities[randRange(1, arrayLen(cities))],
        created_at = dateAdd("d", -randRange(1, 365), now())
    );
}
</cfscript>

Relationship Seeding

<cfscript>
// Create users
users = [];
for (i = 1; i <= 10; i++) {
    users.append(model("user").create(
        username = "user#i#",
        email = "user#i#@example.com"
    ));
}

// Create posts for each user
for (user in users) {
    postCount = randRange(5, 15);
    for (j = 1; j <= postCount; j++) {
        post = model("post").create(
            user_id = user.id,
            title = "Post #j# by #user.username#",
            content = "This is sample content for the post.",
            published_at = dateAdd("d", -randRange(1, 30), now())
        );
        
        // Add comments
        commentCount = randRange(0, 10);
        for (k = 1; k <= commentCount; k++) {
            randomUser = users[randRange(1, arrayLen(users))];
            model("comment").create(
                post_id = post.id,
                user_id = randomUser.id,
                content = "Comment #k# on post",
                created_at = dateAdd("h", k, post.published_at)
            );
        }
    }
}
</cfscript>

Conditional Seeding

<cfscript>
// Only seed if empty
if (model("user").count() == 0) {
    // Create initial users
    model("user").create(
        username = "admin",
        email = "[email protected]",
        password = "password123"
    );
    writeOutput("Created initial users<br>");
} else {
    writeOutput("Users already exist, skipping<br>");
}

// Environment-specific seeding
if (get("environment") == "development") {
    // Add development-specific data
    for (i = 1; i <= 10; i++) {
        model("user").create(
            username = "testuser#i#",
            email = "test#i#@example.com"
        );
    }
    writeOutput("Added development test users<br>");
}
</cfscript>

Best Practices

1. Idempotent Seeds

Make seeds safe to run multiple times:

<cfscript>
// Check before creating
if (!model("user").exists(where="username='admin'")) {
    model("user").create(
        username = "admin",
        email = "[email protected]",
        password = "password123"
    );
    writeOutput("Created admin user<br>");
} else {
    writeOutput("Admin user already exists<br>");
}
</cfscript>

2. Use Transactions

Wrap seeds in transactions:

<cfscript>
transaction {
    try {
        // Create users
        for (i = 1; i <= 10; i++) {
            model("user").create(
                username = "user#i#",
                email = "user#i#@example.com"
            );
        }
        
        // Create related data
        // ...
        
        writeOutput("Seed completed successfully<br>");
    } catch (any e) {
        transaction action="rollback";
        writeOutput("Error: #e.message#<br>");
        rethrow;
    }
}
</cfscript>

3. Organize by Domain

Structure seeds logically:

app/db/
  ├── seed.cfm              # Default seed file
  ├── demo.cfm              # Demo data
  ├── development/          # Development seeds
  │   ├── users.cfm
  │   ├── products.cfm
  │   └── orders.cfm
  └── testing/              # Test-specific seeds
      └── test_data.cfm

4. Document Seeds

Add clear documentation:

<cfscript>
/**
 * Seed file: products.cfm
 * Creates: 5 categories, 50 products
 * Dependencies: None
 * Runtime: ~2 seconds
 */

// Create categories first
categories = [...]; 

// Then create products
// ...
</cfscript>

Error Handling

Validation Errors

<cfscript>
try {
    user = model("user").create(
        username = "testuser",
        email = "invalid-email" // Will fail validation
    );
    
    if (user.hasErrors()) {
        writeOutput("Failed to create user:<br>");
        for (error in user.allErrors()) {
            writeOutput("- #error.message#<br>");
        }
    }
} catch (any e) {
    writeOutput("Error: #e.message#<br>");
}
</cfscript>

Dependency Handling

<cfscript>
// Check dependencies
if (model("category").count() == 0) {
    writeOutput("ERROR: Categories must be seeded first!<br>");
    abort;
}

// Continue with product seeding
// ...
</cfscript>

Notes

  • Seed files must be placed in the app/db/ directory

  • The file parameter should not include the .cfm extension

  • Seed files are executed in the application context with access to all models

  • Output from seed files is displayed to the user

  • Consider performance when seeding large amounts of data

Related Commands

  • wheels dbmigrate latest - Run migrations before seeding

  • wheels db schema - Export/import database structure

  • wheels generate model - Generate models for seeding

  • wheels test run - Test with seeded data

wheels generate model

Generate a model with properties, validations, and associations.

Synopsis

Description

The wheels generate model command creates a new model CFC file with optional properties, associations, and database migrations. Models represent database tables and contain business logic, validations, and relationships.

Arguments

Argument
Description
Default

Options

Option
Description
Default

Examples

Basic model

Creates:

  • /models/User.cfc

  • Migration file (if enabled)

Model with properties

Model with associations

Model without migration

Complex model

Property Types

Type
Database Type
CFML Type

Generated Code

Basic Model

Model with Properties

Model with Associations

Validations

Common validation methods:

Associations

Belongs To

Has Many

Has One

Many to Many

Callbacks

Lifecycle callbacks:

Generated Migration

When --migration is enabled:

Best Practices

  1. Naming: Use singular names (User, not Users)

  2. Properties: Define all database columns

  3. Validations: Add comprehensive validations

  4. Associations: Define all relationships

  5. Callbacks: Use for automatic behaviors

  6. Indexes: Add to migration for performance

Common Patterns

Soft Deletes

Calculated Properties

Scopes

Default Values

Testing

Generate model tests:

See Also

  • - Create migrations

  • - Add properties to existing models

  • - Generate controllers

  • - Generate complete CRUD

wheels generate route

Generate route definitions for your application.

Synopsis

Description

The wheels generate route command helps you create route definitions in your Wheels application's /config/routes.cfm file. It can generate individual routes with different HTTP methods, RESTful resources, or root routes.

Arguments

Argument
Description
Default

Options

Option
Description
Default

Examples

Resources Route (default)

Generates in /config/routes.cfm:

GET Route

Generates:

POST Route

Generates:

Root Route

Generates:

RESTful Resource

Generates:

This creates all standard 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)

API Resource

Generates:

Nested Resources

Generates:

Creates routes like:

  • /posts/:postKey/comments

  • /posts/:postKey/comments/:key

Route Patterns

Dynamic Segments

Generates:

Optional Segments

Generates:

Wildcards

Generates:

Advanced Routing

With Constraints

Generates:

Namespace Routes

Generates:

Module Routes

Generates:

Shallow Nesting

Generates:

Custom Actions

Member Routes

Generates:

Collection Routes

Generates:

Route Files Organization

Main Routes File

/config/routes.cfm:

Route Helpers

Generated routes create URL helpers:

Basic Helpers

Resource Helpers

Nested Resource Helpers

Route Constraints

Pattern Constraints

Format Constraints

Generates:

Route Testing

Generate Route Tests

Route Test Example

Route Debugging

List All Routes

Test Specific Route

Output:

Best Practices

  1. Order matters: Place specific routes before generic ones

  2. Use RESTful routes: Prefer resources() over individual routes

  3. Name your routes: Always provide names for URL helpers

  4. Group related routes: Use namespaces and modules

  5. Add constraints: Validate dynamic segments

  6. Document complex routes: Add comments explaining purpose

  7. Test route resolution: Ensure routes work as expected

Common Patterns

Authentication Required

API Versioning

Subdomain Routing

Redirect Routes

Performance Considerations

  1. Route caching: Routes are cached in production

  2. Minimize regex: Complex patterns slow routing

  3. Avoid wildcards: Be specific when possible

  4. Order efficiently: Most-used routes first

Troubleshooting

Route Not Found

  • Check route order

  • Verify HTTP method

  • Test with wheels routes test

  • Check for typos in pattern

Naming Conflicts

  • Ensure unique route names

  • Check for duplicate patterns

  • Use namespaces to avoid conflicts

Parameter Issues

  • Verify parameter names match

  • Check constraint patterns

  • Test with various inputs

See Also

  • - Generate complete CRUD with routes

  • - Generate controllers

  • - Generate RESTful resources

  • - Generate API resources

wheels test

⚠️ DEPRECATED: This command is deprecated. Use wheels test run or the advanced testing commands (wheels test:all, wheels test:unit, etc.) instead.

Run Wheels framework tests (core, app, or plugin tests).

Synopsis

Description

The wheels test command runs the built-in Wheels framework test suite. This is different from wheels test run which runs your application's TestBox tests. Use this command to verify framework integrity or test Wheels plugins.

Arguments

Argument
Description
Default

Options

Option
Description
Default

Test Types

Core Tests

  • Tests Wheels framework functionality

  • Verifies framework integrity

  • Useful after framework updates

App Tests

  • Runs application-level framework tests

  • Tests Wheels configuration

  • Verifies app-specific framework features

Plugin Tests

  • Tests installed Wheels plugins

  • Verifies plugin compatibility

  • Checks plugin functionality

Examples

Run app tests (default)

Run core framework tests

Run tests on specific server

Run with debug output

Skip reload

Deprecation Notice

Output Example

Framework Test Categories

Model Tests

  • Validations

  • Associations

  • Callbacks

  • Properties

  • Calculations

Controller Tests

  • Filters

  • Caching

  • Provides/formats

  • Redirects

  • Rendering

View Tests

  • Helper functions

  • Form helpers

  • Asset helpers

  • Partials

  • Layouts

Dispatcher Tests

  • Routing

  • URL rewriting

  • Request handling

  • Parameter parsing

Configuration

Test Settings

In /config/settings.cfm:

Test Database

Create separate test database:

Debugging Failed Tests

Enable debug mode

Check specific test file

Common issues

  1. Database not configured: Check test datasource

  2. Reload password wrong: Verify settings

  3. Plugin conflicts: Disable plugins and retest

  4. Cache issues: Clear cache and retry

Continuous Integration

GitHub Actions

Jenkins

Custom Framework Tests

Add tests in /tests/framework/:

Performance Testing

Run with timing:

Monitor slow tests:

Test Isolation

Tests run in isolation:

  • Separate request for each test

  • Transaction rollback (if enabled)

  • Clean application state

Troubleshooting

Tests won't run

Reload issues

Memory issues

Best Practices

  1. Run before deployment

  2. Test after framework updates

  3. Verify plugin compatibility

  4. Use CI/CD integration

  5. Keep test database clean

Migration to New Command

Old Command (Deprecated)

New Command

Difference from TestBox Tests

See Also

  • - Run TestBox application tests

  • - Generate coverage reports

  • - Debug test execution

  • - Reload application

wheels security

Base command for security management and vulnerability scanning.

Synopsis

Description

The wheels security command provides comprehensive security tools for Wheels applications. It scans for vulnerabilities, checks security configurations, and helps implement security best practices.

Subcommands

Command
Description

Options

Option
Description

Direct Usage

When called without subcommands, performs a quick security check:

Output:

Examples

Quick security check

Check security status

Generate security report

Check specific area

Security Areas

Code Security

  • SQL injection detection

  • XSS vulnerability scanning

  • Path traversal checks

  • Command injection risks

Configuration

  • Security headers

  • CORS settings

  • Authentication config

  • Session management

Dependencies

  • Vulnerable packages

  • Outdated libraries

  • License compliance

  • Supply chain risks

Infrastructure

  • SSL/TLS configuration

  • Port exposure

  • File permissions

  • Environment secrets

Security Configuration

Configure via .wheels-security.json:

Security Policies

Define Policies

Create .wheels-security-policy.yml:

Policy Enforcement

Integration

Git Hooks

.git/hooks/pre-commit:

CI/CD Pipeline

IDE Integration

Security Headers

Check Headers

Configure Headers

Dependency Scanning

Check Dependencies

Update Vulnerable Dependencies

License Compliance

Security Fixes

Automatic Fixes

Manual Fixes

The command provides guidance:

Security Reports

Generate Reports

Report Contents

  • Executive summary

  • Detailed findings

  • Remediation steps

  • Compliance status

  • Trend analysis

Compliance

Standards

Check compliance with standards:

Security Monitoring

Continuous Monitoring

Alert Configuration

Security Best Practices

  1. Regular Scans: Schedule automated scans

  2. Fix Quickly: Address high-severity issues immediately

  3. Update Dependencies: Keep libraries current

  4. Security Training: Educate development team

  5. Defense in Depth: Layer security measures

Common Vulnerabilities

SQL Injection

XSS

Emergency Response

Incident Detection

Lockdown Mode

Notes

  • Security scans may take time on large codebases

  • Some checks require running application

  • False positives should be documented

  • Regular updates improve detection accuracy

See Also

  • - Detailed security scanning

  • - Security analysis (deprecated)

wheels plugins

Base command for plugin management in Wheels applications.

Synopsis

Description

The wheels plugins command provides comprehensive plugin management for Wheels applications. It handles plugin discovery, installation, configuration, and lifecycle management.

Subcommands

Command
Description

Options

Option
Description

Direct Usage

When called without subcommands, displays plugin overview:

Output:

Examples

Show plugin overview

Quick plugin check

Update all plugins

Plugin system info

Plugin System

Plugin Structure

Plugin Metadata

Each plugin contains plugin.json:

Plugin Registry

Official Registry

Default source for plugins:

Custom Registries

Configure additional sources:

Plugin Lifecycle

Discovery

Installation

Configuration

Updates

Plugin Development

Create Plugin

Plugin API

Environment Support

Environment-Specific Plugins

Conditional Loading

Plugin Commands

Plugins can register custom commands:

Usage:

Dependency Management

Automatic Resolution

Conflict Resolution

Options:

  • prompt: Ask for each conflict

  • newest: Use newest version

  • oldest: Keep existing version

Plugin Storage

Global Plugins

Shared across projects:

Location: ~/.wheels/plugins/

Project Plugins

Project-specific:

Location: /plugins/

Security

Plugin Verification

Permission Control

Troubleshooting

Common Issues

  1. Plugin Not Loading

  2. Dependency Conflicts

  3. Version Incompatibility

Best Practices

  1. Version Lock: Lock plugin versions for production

  2. Test Updates: Test in development first

  3. Backup: Backup before major updates

  4. Documentation: Document custom plugins

  5. Security: Verify plugin sources

Plugin Cache

Clear Cache

Rebuild Cache

Notes

  • Plugins are loaded in dependency order

  • Some plugins require application restart

  • Global plugins override project plugins

  • Plugin conflicts are resolved by load order

See Also

  • - List plugins

  • - Search for plugins

  • - Show plugin details

  • - Install plugins

  • - Update plugins

  • - Update all plugins

  • - Check for updates

  • - Remove plugins

  • - Create new plugin

wheels generate model [name] [options]
wheels g model [name] [options]

name

Model name (singular)

Required

--migration

Generate database migration

true

properties

Model properties (format: name:type,name2:type2)

belongs-to

Parent model relationships (comma-separated)

has-many

Child model relationships (comma-separated)

has-one

One-to-one relationships (comma-separated)

primary-key

Primary key column name(s)

id

table-name

Custom database table name

description

Model description

--force

Overwrite existing files

false

wheels generate model user
wheels generate model user properties="firstName:string,lastName:string,email:string,age:integer"
wheels generate model post belongs-to="user" has-many="comments"
wheels generate model setting --migration=false
wheels generate model product \
  properties="name:string,price:decimal,stock:integer,active:boolean" \
  belongs-to="category,brand" \
  has-many="reviews,orderItems"

string

VARCHAR(255)

string

text

TEXT

string

integer

INTEGER

numeric

biginteger

BIGINT

numeric

float

FLOAT

numeric

decimal

DECIMAL(10,2)

numeric

boolean

BOOLEAN

boolean

date

DATE

date

datetime

DATETIME

date

timestamp

TIMESTAMP

date

binary

BLOB

binary

uuid

VARCHAR(35)

string

component extends="Model" {

    function init() {
        // Table name (optional if following conventions)
        table("users");
        
        // Validations
        validatesPresenceOf("email");
        validatesUniquenessOf("email");
        validatesFormatOf("email", regex="^[^@]+@[^@]+\.[^@]+$");
        
        // Callbacks
        beforeCreate("setDefaultValues");
    }
    
    private function setDefaultValues() {
        if (!StructKeyExists(this, "createdAt")) {
            this.createdAt = Now();
        }
    }

}
component extends="Model" {

    function init() {
        // Properties
        property(name="firstName", label="First Name");
        property(name="lastName", label="Last Name");
        property(name="email", label="Email Address");
        property(name="age", label="Age");
        
        // Validations
        validatesPresenceOf("firstName,lastName,email");
        validatesUniquenessOf("email");
        validatesFormatOf("email", regex="^[^@]+@[^@]+\.[^@]+$");
        validatesNumericalityOf("age", onlyInteger=true, greaterThan=0, lessThan=150);
    }

}
component extends="Model" {

    function init() {
        // Associations
        belongsTo("user");
        hasMany("comments", dependent="deleteAll");
        
        // Nested properties
        nestedProperties(associations="comments", allowDelete=true);
        
        // Validations
        validatesPresenceOf("title,content,userId");
        validatesLengthOf("title", maximum=255);
    }

}
// Presence
validatesPresenceOf("name,email");

// Uniqueness
validatesUniquenessOf("email,username");

// Format
validatesFormatOf("email", regex="^[^@]+@[^@]+\.[^@]+$");
validatesFormatOf("phone", regex="^\d{3}-\d{3}-\d{4}$");

// Length
validatesLengthOf("username", minimum=3, maximum=20);
validatesLengthOf("bio", maximum=500);

// Numerical
validatesNumericalityOf("age", onlyInteger=true, greaterThan=0);
validatesNumericalityOf("price", greaterThan=0);

// Inclusion/Exclusion
validatesInclusionOf("status", list="active,inactive,pending");
validatesExclusionOf("username", list="admin,root,system");

// Confirmation
validatesConfirmationOf("password");

// Custom
validate("customValidation");
belongsTo("user");
belongsTo(name="author", modelName="user", foreignKey="authorId");
hasMany("comments");
hasMany(name="posts", dependent="deleteAll", orderBy="createdAt DESC");
hasOne("profile");
hasOne(name="address", dependent="delete");
hasMany("categorizations");
hasMany(name="categories", through="categorizations");
// Before callbacks
beforeCreate("method1,method2");
beforeUpdate("method3");
beforeSave("method4");
beforeDelete("method5");
beforeValidation("method6");

// After callbacks
afterCreate("method7");
afterUpdate("method8");
afterSave("method9");
afterDelete("method10");
afterValidation("method11");
afterFind("method12");
afterInitialization("method13");
component extends="wheels.migrator.Migration" {

    function up() {
        transaction {
            t = createTable("users");
            t.string("firstName");
            t.string("lastName");
            t.string("email");
            t.integer("age");
            t.timestamps();
            t.create();
            
            addIndex(table="users", columns="email", unique=true);
        }
    }

    function down() {
        transaction {
            dropTable("users");
        }
    }

}
function init() {
    softDeletes();
}
function init() {
    property(name="fullName", sql="firstName + ' ' + lastName");
}
function scopeActive() {
    return where("active = ?", [true]);
}

function scopeRecent(required numeric days=7) {
    return where("createdAt >= ?", [DateAdd("d", -arguments.days, Now())]);
}
function init() {
    beforeCreate("setDefaults");
}

private function setDefaults() {
    if (!StructKeyExists(this, "status")) {
        this.status = "pending";
    }
    if (!StructKeyExists(this, "priority")) {
        this.priority = 5;
    }
}
wheels generate model user properties="email:string,name:string"
wheels generate test model user
wheels dbmigrate create table
wheels generate property
wheels generate controller
wheels scaffold
wheels generate route [objectname] [options]
wheels g route [objectname] [options]

objectname

The name of the resource/route to add

Optional

get

Create a GET route (pattern,handler format)

post

Create a POST route (pattern,handler format)

put

Create a PUT route (pattern,handler format)

patch

Create a PATCH route (pattern,handler format)

delete

Create a DELETE route (pattern,handler format)

--resources

Create a resources route

false

root

Create a root route with handler

wheels generate route products
.resources("products")
wheels generate route get="/about,pages#about"
.get(pattern="/about", to="pages#about")
wheels generate route post="/contact,contact#send"
.post(pattern="/contact", to="contact#send")
wheels generate route root="pages#home"
.root(to="pages#home")
wheels generate route products --resource
<cfset resources("products")>
wheels generate route products --api
<cfset resources(name="products", nested=false, except="new,edit")>
wheels generate route comments --resource nested="posts"
<cfset resources("posts")>
    <cfset resources("comments")>
</cfset>
wheels generate route "/users/[key]/profile" to="users#profile" name="userProfile"
<cfset get(name="userProfile", pattern="/users/[key]/profile", to="users##profile")>
wheels generate route "/blog/[year]/[month?]/[day?]" to="blog#archive" name="blogArchive"
<cfset get(name="blogArchive", pattern="/blog/[year]/[month?]/[day?]", to="blog##archive")>
wheels generate route "/docs/*" to="documentation#show" name="docs"
<cfset get(name="docs", pattern="/docs/*", to="documentation##show")>
wheels generate route "/users/[id]" to="users#show" constraints="id=[0-9]+"
<cfset get(pattern="/users/[id]", to="users##show", constraints={id="[0-9]+"})>
wheels generate route users --resource namespace="admin"
<cfset namespace("admin")>
    <cfset resources("users")>
</cfset>
wheels generate route dashboard --resource namespace="admin" module="backend"
<cfset module("backend")>
    <cfset namespace("admin")>
        <cfset resources("dashboard")>
    </cfset>
</cfset>
wheels generate route comments --resource nested="posts" --shallow
<cfset resources("posts")>
    <cfset resources(name="comments", shallow=true)>
</cfset>
wheels generate route "products/[key]/activate" to="products#activate" method="PUT" --member
<cfset resources("products")>
    <cfset put(pattern="[key]/activate", to="products##activate", on="member")>
</cfset>
wheels generate route "products/search" to="products#search" --collection
<cfset resources("products")>
    <cfset get(pattern="search", to="products##search", on="collection")>
</cfset>
<!--- 
    Routes Configuration
    Define your application routes below
--->

<!--- Public routes --->
<cfset get(name="home", pattern="/", to="main##index")>
<cfset get(name="about", pattern="/about", to="pages##about")>
<cfset get(name="contact", pattern="/contact", to="pages##contact")>
<cfset post(name="sendContact", pattern="/contact", to="pages##sendContact")>

<!--- Authentication --->
<cfset get(name="login", pattern="/login", to="sessions##new")>
<cfset post(name="createSession", pattern="/login", to="sessions##create")>
<cfset delete(name="logout", pattern="/logout", to="sessions##delete")>

<!--- Resources --->
<cfset resources("products")>
<cfset resources("categories")>

<!--- API routes --->
<cfset namespace("api")>
    <cfset namespace("v1")>
        <cfset resources(name="products", nested=false, except="new,edit")>
        <cfset resources(name="users", nested=false, except="new,edit")>
    </cfset>
</cfset>

<!--- Admin routes --->
<cfset namespace("admin")>
    <cfset get(name="adminDashboard", pattern="/", to="dashboard##index")>
    <cfset resources("users")>
    <cfset resources("products")>
    <cfset resources("orders")>
</cfset>

<!--- Catch-all route --->
<cfset get(pattern="*", to="errors##notFound")>
<!--- For route: get(name="about", pattern="/about", to="pages##about") --->
#linkTo(route="about", text="About Us")#
#urlFor(route="about")#
#redirectTo(route="about")#
<!--- For route: resources("products") --->
#linkTo(route="products", text="All Products")# <!--- /products --->
#linkTo(route="product", key=product.id, text="View")# <!--- /products/123 --->
#linkTo(route="newProduct", text="Add Product")# <!--- /products/new --->
#linkTo(route="editProduct", key=product.id, text="Edit")# <!--- /products/123/edit --->

#urlFor(route="products")# <!--- /products --->
#urlFor(route="product", key=123)# <!--- /products/123 --->
<!--- For nested resources("posts") > resources("comments") --->
#linkTo(route="postComments", postKey=post.id, text="Comments")# <!--- /posts/1/comments --->
#linkTo(route="postComment", postKey=post.id, key=comment.id, text="View")# <!--- /posts/1/comments/5 --->
wheels generate route "/posts/[year]/[month]" to="posts#archive" constraints="year=[0-9]{4},month=[0-9]{2}"
wheels generate route "/api/users" to="api/users#index" format="json"
<cfset get(pattern="/api/users", to="api/users##index", format="json")>
wheels generate route products --resource
wheels generate test routes products
component extends="wheels.Test" {
    
    function test_products_routes() {
        // Test index route
        result = $resolve(path="/products", method="GET");
        assert(result.controller == "products");
        assert(result.action == "index");
        
        // Test show route
        result = $resolve(path="/products/123", method="GET");
        assert(result.controller == "products");
        assert(result.action == "show");
        assert(result.params.key == "123");
        
        // Test create route
        result = $resolve(path="/products", method="POST");
        assert(result.controller == "products");
        assert(result.action == "create");
    }
    
}
wheels routes list
wheels routes test "/products/123" --method=GET
Route resolved:
  Controller: products
  Action: show
  Params: {key: "123"}
  Name: product
<!--- Public routes --->
<cfset get(name="home", pattern="/", to="main##index")>

<!--- Authenticated routes --->
<cfset namespace(name="authenticated", path="/app")>
    <!--- All routes here require authentication --->
    <cfset resources("projects")>
    <cfset resources("tasks")>
</cfset>
<cfset namespace("api")>
    <cfset namespace(name="v1", path="/v1")>
        <cfset resources(name="users", except="new,edit")>
    </cfset>
    <cfset namespace(name="v2", path="/v2")>
        <cfset resources(name="users", except="new,edit")>
    </cfset>
</cfset>
<cfset subdomain("api")>
    <cfset resources("products")>
</cfset>

<cfset subdomain("admin")>
    <cfset resources("users")>
</cfset>
<cfset get(pattern="/old-about", redirect="/about")>
<cfset get(pattern="/products/category/[name]", redirect="/categories/[name]")>
wheels scaffold
wheels generate controller
wheels generate resource
wheels generate api-resource
wheels test [type] [servername] [options]

type

Test type: core, app, or plugin

app

serverName

CommandBox server name

Current server

reload

Reload before running tests

true

debug

Show debug output

false

format

Output format

json

adapter

Test adapter

"" (empty)

--help

Show help information

wheels test core
wheels test app
wheels test plugin
wheels test
wheels test core
wheels test type=app serverName=myserver
wheels test debug=true
wheels test reload=false
⚠️  WARNING: The 'wheels test' command is deprecated.
   Please use 'wheels test run' instead.
   See: wheels help test run
╔═══════════════════════════════════════════════╗
║           Running Wheels Tests                ║
╚═══════════════════════════════════════════════╝

Test Type: app
Server: default
Reloading: Yes

Initializing test environment...
✓ Environment ready

Running tests...

Model Tests
  ✓ validations work correctly (15ms)
  ✓ associations load properly (23ms)
  ✓ callbacks execute in order (8ms)

Controller Tests
  ✓ filters apply correctly (12ms)
  ✓ caching works as expected (45ms)
  ✓ provides correct formats (5ms)

View Tests
  ✓ helpers render correctly (18ms)
  ✓ partials include properly (9ms)
  ✓ layouts apply correctly (11ms)

Plugin Tests
  ✓ DBMigrate plugin loads (7ms)
  ✓ Scaffold plugin works (22ms)

╔═══════════════════════════════════════════════╗
║              Test Summary                     ║
╚═══════════════════════════════════════════════╝

Total Tests: 11
Passed: 11
Failed: 0
Errors: 0
Time: 173ms

✓ All tests passed!
<cfset set(testEnvironment=true)>
<cfset set(testDataSource="myapp_test")>
CREATE DATABASE myapp_test;
wheels test debug=true
Failed: Model Tests > validations work correctly
File: /tests/framework/model/validations.cfc
Line: 45
Expected: true
Actual: false
- name: Run Wheels tests
  run: |
    box install
    box server start
    wheels test core
    wheels test app
stage('Framework Tests') {
    steps {
        sh 'wheels test core'
        sh 'wheels test app'
    }
}
component extends="wheels.Test" {

    function test_custom_framework_feature() {
        // Test custom framework modification
        actual = customFrameworkMethod();
        assert(actual == expected);
    }

}
wheels test --debug | grep "Time:"
✓ complex query test (523ms) ⚠️ SLOW
✓ simple validation (8ms)
# Check server is running
box server status

# Verify test URL
curl http://localhost:3000/wheels/tests
# Manual reload first
wheels reload

# Then run tests
wheels test reload=false
# Increase heap size
box server set jvm.heapSize=512
box server restart
wheels test app
wheels test core myserver
wheels test debug=true
wheels test run
wheels test run --group=core
wheels test run --verbose

Feature

wheels test (deprecated)

wheels test run

Purpose

Framework tests

Application tests

Framework

Wheels Test

TestBox

Location

/wheels/tests/

/tests/

Use Case

Framework integrity

App functionality

Status

Deprecated

Current

wheels test run
wheels test coverage
wheels test debug
wheels reload
wheels security [subcommand] [options]

scan

Scan for security vulnerabilities

--help

Show help information

--version

Show version information

wheels security
Wheels Security Overview
=======================

Last Scan: 2024-01-15 10:30:45
Status: 3 issues found

Critical: 0
High:     1 
Medium:   2
Low:      0

Vulnerabilities:
- [HIGH] SQL Injection risk in UserModel.cfc:45
- [MEDIUM] Missing CSRF protection on /admin routes
- [MEDIUM] Outdated dependency: cfml-jwt (2.1.0 → 3.0.0)

Run 'wheels security scan' for detailed analysis
wheels security
wheels security --status
wheels security --report
wheels security --check=dependencies
{
  "security": {
    "scanOnCommit": true,
    "autoFix": false,
    "severity": "medium",
    "ignore": [
      {
        "rule": "sql-injection",
        "file": "legacy/*.cfc",
        "reason": "Legacy code, sandboxed"
      }
    ],
    "checks": {
      "dependencies": true,
      "code": true,
      "configuration": true,
      "infrastructure": true
    }
  }
}
policies:
  - name: "No Direct SQL"
    description: "Prevent direct SQL execution"
    severity: "high"
    rules:
      - pattern: "queryExecute\\(.*\\$.*\\)"
        message: "Use parameterized queries"
  
  - name: "Secure Headers"
    description: "Require security headers"
    severity: "medium"
    headers:
      - "X-Frame-Options"
      - "X-Content-Type-Options"
      - "Content-Security-Policy"
# Check policy compliance
wheels security --check-policy

# Enforce policies (fail on violation)
wheels security --enforce-policy
#!/bin/bash
wheels security scan --severity=high --fail-on-issues
- name: Security scan
  run: |
    wheels security scan --format=sarif
    wheels security --upload-results
{
  "wheels.security": {
    "realTimeScan": true,
    "showInlineWarnings": true
  }
}
wheels security headers --check
// Application.cfc
this.securityHeaders = {
    "X-Frame-Options": "DENY",
    "X-Content-Type-Options": "nosniff",
    "Strict-Transport-Security": "max-age=31536000",
    "Content-Security-Policy": "default-src 'self'"
};
wheels security deps
wheels security deps --fix
wheels security licenses --allowed=MIT,Apache-2.0
# Fix auto-fixable issues
wheels security fix

# Fix specific issue types
wheels security fix --type=headers,csrf
Issue: SQL Injection Risk
File: /models/User.cfc:45
Fix: Replace direct SQL with parameterized query

Current:
query = "SELECT * FROM users WHERE id = #arguments.id#";

Suggested:
queryExecute(
    "SELECT * FROM users WHERE id = :id",
    { id: arguments.id }
);
# HTML report
wheels security scan --report=html

# JSON report for tools
wheels security scan --format=json

# SARIF for GitHub
wheels security scan --format=sarif
# OWASP Top 10
wheels security compliance --standard=owasp-top10

# PCI DSS
wheels security compliance --standard=pci-dss

# Custom standard
wheels security compliance --standard=./company-standard.yml
# Start monitoring
wheels security monitor --start

# Check monitor status
wheels security monitor --status

# View alerts
wheels security monitor --alerts
{
  "monitoring": {
    "alerts": {
      "email": "[email protected]",
      "slack": "https://hooks.slack.com/...",
      "severity": "high"
    }
  }
}
// Vulnerable
query = "SELECT * FROM users WHERE id = #url.id#";

// Secure
queryExecute(
    "SELECT * FROM users WHERE id = :id",
    { id: { value: url.id, cfsqltype: "integer" } }
);
// Vulnerable
<cfoutput>#form.userInput#</cfoutput>

// Secure
<cfoutput>#encodeForHTML(form.userInput)#</cfoutput>
# Check for compromise indicators
wheels security incident --check

# Generate incident report
wheels security incident --report
# Enable security lockdown
wheels security lockdown --enable

# Disable after resolution
wheels security lockdown --disable
wheels security scan
wheels analyze security
Security Best Practices
OWASP Guidelines
wheels plugins [subcommand] [options]

list

List installed plugins

search

Search for plugins on ForgeBox

info

Show detailed plugin information

install

Install a plugin

update

Update a specific plugin

update:all

Update all installed plugins

outdated

List plugins with available updates

remove

Remove a plugin

init

Initialize a new plugin project

--help

Show help information

--version

Show version information

wheels plugins
Wheels Plugin Manager
====================

Installed Plugins: 5
├── authentication (v2.1.0) - User authentication system
├── pagination (v1.5.2) - Advanced pagination helpers
├── validation (v3.0.1) - Extended validation rules
├── caching (v2.2.0) - Enhanced caching strategies
└── api-tools (v1.8.3) - RESTful API utilities

Available Updates: 2
- validation: v3.0.1 → v3.1.0
- api-tools: v1.8.3 → v2.0.0

Run 'wheels plugins list' for detailed information
wheels plugins
wheels plugins --check
wheels plugins --update-all
wheels plugins --info
/plugins/
├── authentication/
│   ├── Authentication.cfc
│   ├── config/
│   ├── models/
│   ├── views/
│   └── plugin.json
├── pagination/
└── ...
{
  "name": "authentication",
  "version": "2.1.0",
  "description": "User authentication system",
  "author": "Wheels Community",
  "wheels": ">=2.0.0",
  "dependencies": {
    "validation": ">=3.0.0"
  }
}
https://www.forgebox.io/type/cfwheels-plugins/
{
  "pluginRegistries": [
    "https://www.forgebox.io/type/cfwheels-plugins/",
    "https://company.com/wheels-plugins/"
  ]
}
# Search for plugins
wheels plugins search authentication

# Browse categories
wheels plugins browse --category=security
# Install from registry
wheels plugins install authentication

# Install from GitHub
wheels plugins install github:user/wheels-plugin

# Install from file
wheels plugins install ./my-plugin.zip
# Configure plugin
wheels plugins configure authentication

# View configuration
wheels plugins config authentication
# Check for updates
wheels plugins outdated

# Update specific plugin
wheels plugins update authentication

# Update all plugins
wheels plugins update --all
# Generate plugin scaffold
wheels generate plugin my-plugin

# Plugin structure created:
# /plugins/my-plugin/
#   ├── MyPlugin.cfc
#   ├── plugin.json
#   ├── config/
#   ├── tests/
#   └── README.md
component extends="wheels.Plugin" {
    
    function init() {
        this.version = "1.0.0";
        this.author = "Your Name";
        this.description = "Plugin description";
    }
    
    function setup() {
        // Plugin initialization
    }
    
    function teardown() {
        // Plugin cleanup
    }
}
{
  "plugins": {
    "production": ["caching", "monitoring"],
    "development": ["debug-toolbar", "profiler"],
    "all": ["authentication", "validation"]
  }
}
// In environment.cfm
if (get("environment") == "development") {
    addPlugin("debug-toolbar");
}
// In plugin
this.commands = {
    "auth:create-user": "commands/CreateUser.cfc",
    "auth:reset-password": "commands/ResetPassword.cfc"
};
wheels auth:create-user [email protected]
wheels auth:reset-password user123
# Installs plugin and dependencies
wheels plugins install api-tools
# Also installs: validation, serialization
# When conflicts exist
wheels plugins install authentication --resolve=prompt
wheels plugins install authentication --global
wheels plugins install authentication
# Verify plugin signatures
wheels plugins verify authentication

# Install only verified plugins
wheels plugins install authentication --verified-only
{
  "pluginPermissions": {
    "fileSystem": ["read", "write"],
    "network": ["http", "https"],
    "database": ["read", "write"]
  }
}
wheels plugins diagnose authentication
wheels plugins deps --tree
wheels plugins check-compatibility
wheels plugins cache clear
wheels plugins cache rebuild
wheels plugin list
wheels plugin search
wheels plugin info
wheels plugin install
wheels plugin update
wheels plugin update:all
wheels plugin outdated
wheels plugin remove
wheels plugin init
Plugin Development Guide

Upgrading

Instructions for upgrading Wheels applications

Wheels follows Semantic Versioning (http://semver.org/) so large version changes (e.g, 1.x.x -> 2.x.x) will most likely contain breaking changes which will require evaluation of your codebase. Minor version changes (e.g, 1.3.x->1.4.x) will often contain new functionality, but in a backwards-compatible manner, and maintenance releases (e.g 1.4.4 -> 1.4.5) will just be trying to fix bugs.

Generally speaking, upgrading Wheels is as easy as replacing the wheels folder, especially for those small maintenance releases: however, there are usually exceptions in minor point releases (i.e, 1.1 to 1.3 required replacing other files outside the wheels folder). The notes below detail those changes.

Upgrading to 3.0.0

Compatibility Changes

Adobe Coldfusion 2016 and below are no longer compatible with Wheels going forward. Consequently, these versions have been removed from the Wheels Internal Test Suites.

Code changes

  • Migrate your tests from the tests directory which are written with rocketUnit and rewrite them into Testbox in the tests/Testbox directory. Starting with Wheels 3.x, Testbox will replace RocketUnit as the default testing framework.

  • Starting with Wheels 3.x, Wirebox will be used as the default dependency injector.

  • After installing Wheels 3.x, you'll have to run box install to intall testbox and wirebox in your application as they are not shipped with Wheels but are rather listed in box.json file as dependencies to be installed.

  • Added Mappings for the app, vendor, wheels, wirebox, testbox and tests directories.

  • root.cfm and rewrite.cfm have been removed. All the requests are now being redirected only through public/index.cfm.

  • A .env file has been added in the root of the application which adds the H2 database extension for lucee and sets the cfadmin password to commandbox for both Lucee and Adobe ColdFusion.

Changes to the wheels folder

  • Replace the wheels folder with the new one from the 3.0.0 download.

  • Move the wheels folder inside the vendor folder.

Changes outside the wheels folder

  • Moved the config, controllers, events, global, lib, migrator, models, plugins, snippets and views directories inside the app directory.

  • Moved the files, images, javascripts, miscellaneous, stylesheets directories and Application.cfc, index.cfm and urlrewrite.xml files into the public folder.

Upgrading to 2.3.x

Replace the wheels folder with the new one from the 2.3.0 download.

Upgrading to 2.2.x

Replace the wheels folder with the new one from the 2.2.0 download.

Upgrading to 2.1.x

Replace the wheels folder with the new one from the 2.1.0 download.

Code changes

  • Rename any instances of findLast() to findLastOne()

Changes outside the wheels folder

  • Create /events/onabort.cfm to support the onAbort method

Upgrading to 2.0.x

As always, the first step is to replace the wheels folder with the new one from the 2.0 download.

Other major changes required to upgrade your application are listed in the following sections.

Supported CFML Engines

Wheels 2.0 requires one of these CFML engines:

  • Lucee 4.5.5.006 + / 5.2.1.9+

  • Adobe ColdFusion 10.0.23 / 11.0.12+ / 2016.0.4+

We've updated our minimum requirements to match officially supported versions from the vendors. (For example, Adobe discontinued support for ColdFusion 10 in May 2017, which causes it to be exposed to security exploits in the future. We've included it in 2.0 but it may be discontinued in a future version)

Changes outside the wheels folder

  • The events/functions.cfm file has been moved to global/functions.cfm.

  • The models/Model.cfc file should extend wheels.Model instead of Wheels (models/Wheels.cfc can be deleted).

  • The controllers/Controller.cfc file should extend wheels.Controller instead of Wheels(controllers/Wheels.cfc can be deleted).

  • The init function of controllers and models must be renamed to config.

  • The global setting modelRequireInit has been renamed to modelRequireConfig.

  • The global setting cacheControllerInitialization has been renamed to cacheControllerConfig.

  • The global setting cacheModelInitialization has been renamed to cacheModelConfig.

  • The global setting clearServerCache has been renamed to clearTemplateCache.

  • The updateProperties() method has been removed, use update() instead.

  • JavaScript arguments like confirm and disable have been removed from the link and form helper functions (use the JS Confirm and JS Disable plugins to reinstate the old behavior).

  • The renderPage function has been renamed to renderView

  • includePartial() now requires the partial and query arguments to be set (if using a query)

Routing

The addRoute() function has been removed in Wheels 2.0 in favor of a new routing API. See the Routing chapter for information about the new RESTful routing system.

A limited version of the "wildcard" route ([controller]/[action]/[key]) is available as [controller]/[action]) if you use the new wildcard() mapper method:

/config/routes.cfm
mapper()
    .wildcard()
.end();

By default, this is limited to GET requests for security reasons.

Cross-Site Request Forgery (CSRF) Protection

It is strongly recommended that you enable Wheels 2.0's built-in CSRF protection.

For many applications, you need to follow these steps:

  1. In controllers/Controller.cfc, add a call to protectsFromForgery() to the config method.

  2. Add a call to the csrfMetaTags() helper in your layouts' <head> sections.

  3. Configure any AJAX calls that POST data to your application to pass the authenticityToken from the <meta>tags generated by csrfMetaTags() as an X-CSRF-TOKEN HTTP header.

  4. Update your route definitions to enforce HTTP verbs on actions that manipulate data (get, post, patch, delete, etc.)

  5. Make sure that forms within the application are POSTing data to the actions that require post, patch, and delete verbs.

See documentation for the CSRF Protection Plugin for more information.

Note: If you had previously installed the CSRF Protection plugin, you may remove it and rely on the functionality included in the Wheels 2 core.

Database Migrations

If you have previously been using the dbmigrate plugin, you can now use the inbuilt version within the Wheels 2 core.

Database Migration files in /db/migrate/ should now be moved to /app/migrator/migrations/ and extend wheels.migrator.Migration, not plugins.dbmigrate.Migration which can be changed with a simple find and replace. Note: Oracle is not currently supported for Migrator.

Upgrading to 1.4.x

  1. Replace the wheels folder with the new one from the 1.4 download.

  2. Replace URL rewriting rule files – i.e, .htaccess, web.config, IsapiRewrite.ini

In addition, if you're upgrading from an earlier version of Wheels, we recommend reviewing the instructions from earlier reference guides below.

Upgrading to 1.3.x

If you are upgrading from Wheels 1.1.0 or newer, follow these steps:

  1. Replace the wheels folder with the new one from the 1.3 download.

  2. Replace the root root.cfm file with the new one from the 1.3 download.

  3. Remove the <cfheader> calls from the following files:

    • events/onerror.cfm

    • events/onmaintenance.cfm

    • events/onmissingtemplate.cfm

In addition, if you're upgrading from an earlier version of Wheels, we recommend reviewing the instructions from earlier reference guides below.

Note: To accompany the newest 1.1.x releases, we've highlighted the changes that are affected by each release in this cycle.

Upgrading to 1.1.x

If you are upgrading from Wheels 1.0 or newer, the easiest way to upgrade is to replace the wheels folder with the new one from the 1.1 download. If you are upgrading from an earlier version, we recommend reviewing the steps outlined in Upgrading to Wheels 1.0.

Note: To accompany the newest 1.1.x releases, we've highlighted the changes that are affected by each release in this cycle.

Plugin Compatibility

Be sure to review your plugins and their compatibility with your newly-updated version of Wheels. Some plugins may stop working, throw errors, or cause unexpected behavior in your application.

Supported System Changes

  • 1.1: The minimum Adobe ColdFusion version required is now 8.0.1.

  • 1.1: The minimum Railo version required is now 3.1.2.020.

  • 1.1: The H2 database engine is now supported.

File System Changes

  • 1.1: The .htaccess file has been changed. Be sure to copy over the new one from the new version 1.1 download and copy any addition changes that you may have also made to the original version.

Database Structure Changes

  • 1.1: By default, Wheels 1.1 will wrap database queries in transactions. This requires that your database engine supports transactions. For MySQL in particular, you can convert your MyISAM tables to InnoDB to be compatible with this new functionality. Otherwise, to turn off automatic transactions, place a call to set(transactionMode="none").

  • 1.1: Binary data types are now supported.

CFML Code Changes

Model Code

  • 1.1: Validations will be applied to some model properties automatically. This may cause unintended behavior with your validations. To turn this setting off, call set(automaticValidations=false) in config/settings.cfm.

  • 1.1: The class argument in hasOne(), hasMany(), and belongsTo() has been deprecated. Use the modelName argument instead.

  • 1.1: afterFind() callbacks no longer require special logic to handle the setting of properties in objects and queries. (The "query way" works for both cases now.) Because arguments will always be passed in to the method, you can't rely on StructIsEmpty() to determine if you're dealing with an object or not. In the rare cases that you need to know, you can now call isInstance() or isClass() instead.

  • 1.1: On create, a model will now set the updatedAt auto-timestamp to the same value as the createdAt timestamp. To override this behavior, call set(setUpdatedAtOnCreate=false) in config/settings.cfm.

View Code

  • 1.1: Object form helpers (e.g. textField() and radioButton()) now automatically display a label based on the property name. If you left the label argument blank while using an earlier version of Wheels, some labels may start appearing automatically, leaving you with unintended results. To stop a label from appearing, use label=false instead.

  • 1.1: The contentForLayout() helper to be used in your layout files has been deprecated. Use the includeContent() helper instead.

  • 1.1: In production mode, query strings will automatically be added to the end of all asset URLs (which includes JavaScript includes, stylesheet links, and images). To turn off this setting, call set(assetQueryString=false) in config/settings.cfm.

  • 1.1: stylesheetLinkTag() and javaScriptIncludeTag() now accept external URLs for the source/sources argument. If you manually typed out these tags in previous releases, you can now use these helpers instead.

  • 1.1: flashMessages(), errorMessageOn(), and errorMessagesFor() now create camelCased class attributes instead (for example error-messages is now errorMessages). The same goes for the class attribute on the tag that wraps form elements with errors: it is now fieldWithErrors.

Controller Code

  • 1.1.1: The if argument in all validation functions is now deprecated. Use the condition argument instead.

Upgrading to 1.0.x

Our listing of steps to take while upgrading your Wheels application from earlier versions to 1.0.x.

Upgrading from an earlier version of 1.x? Then the upgrade path is simple. All you need to do is replace the wheels folder with the new wheels folder from the download.

The easiest way to upgrade is to setup an empty website, deploy a fresh copy of Wheels 1.0, and then transfer your application code to it. When transferring, please make note of the following changes and make the appropriate changes to your code.

Note: To accompany the newest 1.0 release, we've highlighted the changes that are affected by that release.

Supported System Changes

  • 1.0: URL rewriting with IIS 7 is now supported.

  • 1.0: URL rewriting in a sub folder on Apache is now supported.

  • ColdFusion 9 is now supported.

  • Oracle 10g or later is now supported.

  • PostgreSQL is now supported.

  • Railo 3.1 is now supported.

File System Changes

  • 1.0: There is now an app.cfm file in the config folder. Use it to set variables that you'd normally set in Application.cfc (i.e., this.name, this.sessionManagement, this.customTagPaths, etc.)

  • 1.0: There is now a web.config file in the root.

  • 1.0: There is now a Wheels.cfc file in the models folder.

  • 1.0: The Wheels.cfc file in the controllers folder has been updated.

  • 1.0: The IsapiRewrite4.ini and .htaccess files in the root have both been updated.

  • The controller folder has been changed to controllers.

  • The model folder has been changed to models.

  • The view folder has been changed to views.

  • Rename all of your CFCs in models and controllers to UpperCamelCase. So controller.cfc will become Controller.cfc, adminUser.cfc will become AdminUser.cfc, and so on.

  • All images must now be stored in the images folder, files in the files folder, JavaScript files in the javascripts folder, and CSS files in the stylesheets folder off of the root.

Database Structure Changes

  • deletedOn, updatedOn, and createdOn are no longer available as auto-generated fields. Please change the names to deletedAt, updatedAt, and createdAt instead to get similar functionality, and make sure that they are of type datetime, timestamp, or equivalent.

CFML Code Changes

Config Code

  • 1.0: The action of the default route (home) has changed to wheels. The way configuration settings are done has changed quite a bit. To change a Wheels application setting, use the new set() function with the name of the Wheels property to change. (For example, <cfset set(dataSourceName="mydatasource")>.) To see a list of available Wheels settings, refer to the Configuration and Defaults chapter. Model Code

  • 1.0: The extends attribute in models/Model.cfc should now be Wheels.

  • findById() is now called findByKey(). Additionally, its id argument is now named key instead. For composite keys, this argument will accept a comma-delimited list.

  • When using a model's findByKey() or findOne() functions, the found property is no longer available. Instead, the functions return false if the record was not found.

  • A model's errorsOn() function now always returns an array, even if there are no errors on the field. When there are errors for the field, the array elements will contain a struct with name, fieldName, and message elements.

  • The way callbacks are created has changed. There is now a method for each callback event ( beforeValidation(), beforeValidationOnCreate(), etc.) that should be called from your model's init() method. These methods take a single argument: the method within your model that should be invoked during the callback event. See the chapter on Object Callbacks for an example.

View Code

  • 1.0: The contents of the views/wheels folder have been changed.

  • The wrapLabel argument in form helpers is now replaced with labelPlacement. Valid values for labelPlacement are before, after, and around.

  • The first argument for includePartial() has changed from name to partial. If you're referring to it through a named argument, you'll need to replace all instances with partial.

  • The variable that keeps a counter of the current record when using includePartial() with a query has been renamed from currentRow to current.

  • There is now an included wheels view folder in views. Be sure to copy that into your existing Wheels application if you're upgrading.

  • The location of the default layout has changed. It is now stored at /views/layout.cfm. Now controller-specific layouts are stored in their respective view folder as layout.cfm. For example, a custom layout for www.domain.com/about would be stored at /views/about/layout.cfm.

  • In linkTo(), the id argument is now called key. It now accepts a comma-delimited list in the case of composite keys.

  • The linkTo() function also accepts an object for the key argument, in which case it will automatically extract the keys from it for use in the hyperlink.

  • The linkTo() function can be used only for controller-, action-, and route-driven links now. * The url argument has been removed, so now all static links should be coded using a standard "a" tag.

Controller Code

  • 1.0: The extends attribute in controllers/Controller.cfc should now be Wheels. Multiple-word controllers and actions are now word-delimited by hyphens in the URL. For example, if your controller is called SiteAdmin and the action is called editLayout, the URL to access that action would be http://www.domain.com/site-admin/edit-layout.

URL/Routing

  • The default route for Wheels has changed from [controller]/[action]/[id] to [controller]/[action]/[key]. This is to support composite keys. The params.id value will now only be available as params.key.

  • You can now pass along composite keys in the URL. Delimit multiple keys with a comma. (If you want to use this feature, then you can't have a comma in the key value itself).

wheels test debug

Debug test execution with detailed diagnostics and troubleshooting tools.

Synopsis

wheels test debug [spec] [options]

Description

The wheels test debug command provides advanced debugging capabilities for your test suite. It helps identify why tests are failing, diagnose test environment issues, and provides detailed execution traces for troubleshooting complex test problems.

Options

Option
Description
Default

type

Type of tests to run: app, core, or plugin

app

spec

Specific test spec to run (e.g., models.user)

servername

Name of server to reload

(current server)

--reload

Force a reload of wheels (boolean flag)

false

--break-on-failure

Stop test execution on first failure (boolean flag)

true

output-level

Output verbosity: 1=minimal, 2=normal, 3=verbose

3

Examples

Debug all app tests

wheels test debug

Debug specific test spec

wheels test debug spec=models.user

Debug with minimal output

wheels test debug output-level=1

Debug without stopping on failure

wheels test debug --break-on-failure=false

Debug core framework tests

wheels test debug type=core --reload

Enable remote debugging

wheels test debug --inspect port=9229

Debug slow tests

wheels test debug slow=500 verbose=2

Debug Output

Basic Debug Info

🔍 Test Debug Session Started
================================

Environment: testing
Debug Level: 1
Test Framework: TestBox 5.0.0
CFML Engine: Lucee 5.3.9.141

Running: UserModelTest.testValidation
Status: RUNNING

[DEBUG] Setting up test case...
[DEBUG] Creating test user instance
[DEBUG] Validating empty user
[DEBUG] Assertion: user.hasErrors() = true ✓
[DEBUG] Test completed in 45ms

Verbose Trace Output

With --trace verbose=3:

🔍 Test Execution Trace
======================

▶ UserModelTest.setup()
  └─ [0.5ms] Creating test database transaction
  └─ [1.2ms] Loading test fixtures
  └─ [0.3ms] Initializing test context

▶ UserModelTest.testValidation()
  ├─ [0.1ms] var user = model("User").new()
  │   └─ [2.1ms] Model instantiation
  │   └─ [0.5ms] Property initialization
  ├─ [0.2ms] user.validate()
  │   └─ [5.3ms] Running validations
  │   ├─ [1.2ms] Checking required fields
  │   ├─ [2.1ms] Email format validation
  │   └─ [2.0ms] Custom validations
  ├─ [0.1ms] expect(user.hasErrors()).toBe(true)
  │   └─ [0.3ms] Assertion passed ✓
  └─ [0.1ms] Test completed

Total Time: 10.2ms
Memory Used: 2.3MB

Interactive Debugging

Step Mode

With --step:

▶ Entering step mode for UserModelTest.testLogin

[1] user = model("User").findOne(where="email='[email protected]'")
    > (n)ext, (s)tep into, (c)ontinue, (v)ariables, (q)uit: v

Variables:
- arguments: {}
- local: { user: [undefined] }
- this: UserModelTest instance

    > n

[2] expect(user.authenticate("password123")).toBe(true)
    > v

Variables:
- arguments: {}
- local: { user: User instance {id: 1, email: "[email protected]"} }

    > s

  [2.1] Entering: user.authenticate("password123")
       Parameters: { password: "password123" }

Breakpoints

Set breakpoints in code:

// In test file
function testComplexLogic() {
    var result = complexCalculation(data);
    
    debugBreak(); // Execution pauses here
    
    expect(result).toBe(expectedValue);
}

Or via command line:

wheels test debug breakpoint=OrderTest.testCalculateTotal:25

Test Context Inspection

Dump Test Context

With --dump-context:

Test Context Dump
================

Test: UserModelTest.testPermissions
Phase: Execution

Application Scope:
- wheels.version: 2.5.0
- wheels.environment: testing
- Custom settings: { ... }

Request Scope:
- cgi.request_method: "GET"
- url: { testMethod: "testPermissions" }

Test Data:
- Fixtures loaded: users, roles, permissions
- Test user: { id: 999, email: "[email protected]" }
- Database state: Transaction active

Component State:
- UserModelTest properties: { ... }
- Inherited properties: { ... }

Performance Analysis

Slow Test Detection

With slow=500:

⚠️ Slow Tests Detected
=====================

1. OrderModelTest.testLargeOrderProcessing - 2,345ms 🐌
   - Database queries: 45 (1,234ms)
   - Model operations: 892ms
   - Assertions: 219ms

2. UserControllerTest.testBulkImport - 1,567ms 🐌
   - File I/O: 623ms
   - Validation: 512ms
   - Database inserts: 432ms

3. ReportTest.testGenerateYearlyReport - 987ms ⚠️
   - Data aggregation: 654ms
   - PDF generation: 333ms

Remote Debugging

Enable Inspector

wheels test debug --inspect

Connect with Chrome DevTools:

  1. Open Chrome/Edge

  2. Navigate to chrome://inspect

  3. Click "Configure" and add localhost:9229

  4. Click "inspect" on the target

Debug Protocol

wheels test debug --inspect-brk port=9230
  • --inspect: Enable debugging

  • --inspect-brk: Break on first line

  • Custom port for multiple sessions

Failure Analysis

Pause on Failure

With --pause-on-failure:

✗ Test Failed: UserModelTest.testUniqueEmail

Test paused at failure point.

Failure Details:
- Expected: true
- Actual: false
- Location: UserModelTest.cfc:45

Debug Options:
(i) Inspect variables
(s) Show stack trace
(d) Dump database state
(r) Retry test
(c) Continue
(q) Quit

> i

Local Variables:
- user1: User { email: "[email protected]", id: 1 }
- user2: User { email: "[email protected]", errors: ["Email already exists"] }

Stack Trace Analysis

Stack Trace:
-----------
1. TestBox.expectation.toBe() at TestBox/system/Expectation.cfc:123
2. UserModelTest.testUniqueEmail() at tests/models/UserModelTest.cfc:45
3. TestBox.runTest() at TestBox/system/BaseSpec.cfc:456
4. Model.validate() at wheels/Model.cfc:789
5. Model.validatesUniquenessOf() at wheels/Model.cfc:1234

Test Replay

Replay Failed Tests

wheels test debug --replay

Replays last failed tests with debug info:

Replaying 3 failed tests from last run...

1/3 UserModelTest.testValidation
    - Original failure: Assertion failed at line 23
    - Replay status: PASSED ✓
    - Possible flaky test

2/3 OrderControllerTest.testCheckout
    - Original failure: Database connection timeout
    - Replay status: FAILED ✗
    - Consistent failure

Configuration

Debug Configuration

.wheels-test-debug.json:

{
  "debug": {
    "defaultLevel": 1,
    "slowThreshold": 1000,
    "breakpoints": [
      "UserModelTest.testComplexScenario:45",
      "OrderTest.testEdgeCase:78"
    ],
    "trace": {
      "includeFramework": false,
      "maxDepth": 10
    },
    "output": {
      "colors": true,
      "timestamps": true,
      "saveToFile": "./debug.log"
    }
  }
}

Debugging Strategies

1. Isolate Failing Test

# Run only the failing test
wheels test debug UserModelTest.testValidation --trace

2. Check Test Environment

# Dump environment and context
wheels test debug --dump-context > test-context.txt

3. Step Through Execution

# Interactive debugging
wheels test debug FailingTest --step --pause-on-failure

4. Compare Working vs Failing

# Debug working test
wheels test debug WorkingTest --trace > working.log

# Debug failing test
wheels test debug FailingTest --trace > failing.log

# Compare outputs
diff working.log failing.log

Common Issues

Test Pollution

Debug test isolation:

wheels test debug --trace verbose=3 | grep -E "(setup|teardown|transaction)"

Race Conditions

Debug timing issues:

wheels test debug slow=100 --trace

Database State

wheels test debug --dump-context | grep -A 20 "Database state"

Best Practices

  1. Start Simple: Use basic debug before advanced options

  2. Isolate Issues: Debug one test at a time

  3. Use Breakpoints: Strategic breakpoints save time

  4. Check Environment: Ensure test environment is correct

  5. Save Debug Logs: Keep logs for complex issues

Notes

  • Debug mode affects test performance

  • Some features require specific CFML engine support

  • Remote debugging requires network access

  • Verbose output can be overwhelming - filter as needed

See Also

  • wheels test - Run tests normally

  • wheels test run - Run specific tests

  • wheels test coverage - Coverage analysis

  • Debugging Guide

wheels generate view

Generate view files for controllers.

Synopsis

Description

The wheels generate view command creates view files for controllers. It can generate individual views using templates or create blank view files.

Arguments

Argument
Description
Default

Template Options

Available templates:

  • crud/_form - Form partial for new/edit views

  • crud/edit - Edit form view

  • crud/index - List/index view

  • crud/new - New form view

  • crud/show - Show/detail view

Examples

Basic view (no template)

Creates: /views/users/show.cfm with empty content

View with CRUD template

Creates: /views/users/show.cfm using the show template

Edit form with template

Creates: /views/users/edit.cfm using the edit template

Form partial

Creates: /views/users/_form.cfm using the form partial template

Index view

Creates: /views/products/index.cfm using the index template

Generated Code Examples

Without Template (blank file)

With CRUD Index Template

Form View (new.cfm)

Form Partial (_form.cfm)

Show View (show.cfm)

View Templates

Available Templates

Template
Description
Use Case

Template Structure

Templates are located in:

Partial Views

Naming Convention

Partials start with underscore:

  • _form.cfm - Form partial

  • _item.cfm - List item partial

  • _sidebar.cfm - Sidebar partial

Generate Partials

Using Partials

Layout Integration

With Layout (default)

Without Layout

Custom Formats

HTML Format

Creates: /views/products/index.html

Custom Extensions

Creates: /views/emails/welcome.txt

Ajax Views

Generate AJAX View

AJAX Template Example

Form Helpers

Standard Form

File Upload Form

Responsive Design

Mobile-First Template

Localization

Generate Localized Views

Creates: /views/products/index_es.cfm

Localized Content

Testing Views

Generate View Tests

View Test Example

Performance Optimization

Caching Views

Lazy Loading

Best Practices

  1. Keep views simple and focused on presentation

  2. Use partials for reusable components

  3. Move complex logic to helpers or controllers

  4. Follow naming conventions consistently

  5. Use semantic HTML markup

  6. Include accessibility attributes

  7. Optimize for performance with caching

  8. Test views with various data states

Common Patterns

Empty State

Loading State

Error State

See Also

  • - Generate controllers

  • - Generate complete CRUD

  • - Generate view tests

wheels generate property

Add properties to existing model files.

Synopsis

Description

The wheels generate property command generates a database migration to add a property to an existing model and scaffolds it into _form.cfm and show.cfm views.

Arguments

Argument
Description
Default

Options

Option
Description
Default

Property Syntax

Basic Format

Data Type Options

  • biginteger - Large integer

  • binary - Binary data

  • boolean - Boolean (true/false)

  • date - Date only

  • datetime - Date and time

  • decimal - Decimal numbers

  • float - Floating point

  • integer - Integer

  • string - Variable character (VARCHAR)

  • text - Long text

  • time - Time only

  • timestamp - Timestamp

  • uuid - UUID/GUID

Examples

String property

Creates a string/textField property called firstname on the User model.

Boolean property with default

Creates a boolean/checkbox property with default value of 0 (false).

Datetime property

Creates a datetime property on the User model.

Decimal property with precision

Add calculated property

Generated Code Examples

Basic Property Addition

Before:

After:

Multiple Properties

Command:

Generated:

Association Property

Command:

Generated:

Calculated Property

Command:

Generated:

Migration Generation

When --migrate=true (default), generates migration:

Migration File

app/migrator/migrations/[timestamp]_add_properties_to_[model].cfc:

Validation Rules

Automatic Validations

Based on property type and options:

Type
Validations Applied

Custom Validations

Add custom validation rules:

Generated:

Property Callbacks

Generate with callbacks:

Generated:

Complex Properties

Enum-like Property

Generated:

File Upload Property

Generated:

JSON Property

Generated:

Property Modifiers

Encrypted Property

Generated:

Slugged Property

Generated:

Batch Operations

Add Multiple Related Properties

Add Timestamped Properties

Integration with Existing Code

Preserve Existing Structure

The command intelligently adds properties without disrupting:

  • Existing properties

  • Current validations

  • Defined associations

  • Custom methods

  • Comments and formatting

Conflict Resolution

Best Practices

  1. Add properties incrementally

  2. Always generate migrations

  3. Include appropriate validations

  4. Use semantic property names

  5. Add indexes for query performance

  6. Consider default values carefully

  7. Document complex properties

Common Patterns

Soft Delete

Versioning

Status Tracking

Audit Fields

Testing

After adding properties:

See Also

  • - Generate models

  • - Create columns

  • - Generate tests

wheels test run

Run TestBox tests for your application with advanced features.

Synopsis

Description

The wheels test run command executes your application's TestBox test suite with support for watching, filtering, and various output formats. This is the primary command for running your application tests (as opposed to framework tests).

Options

Option
Description
Default

Examples

Run all tests

Filter tests by pattern

Watch mode

Run specific test group

Generate coverage report

Use different reporter

Stop on first failure

Verbose output with coverage

Test Structure

Standard test directory layout:

Writing Tests

Model Test Example

Controller Test Example

Test Configuration

/tests/Application.cfc

Watch Mode

Watch mode reruns tests on file changes:

Output:

Reporters

Simple (Default)

  • Colored console output

  • Shows progress dots

  • Summary at end

Text

  • Plain text output

  • Good for CI systems

  • No colors

JSON

JUnit

  • JUnit XML format

  • For CI integration

  • Jenkins compatible

TAP

  • Test Anything Protocol

  • Cross-language format

Filtering Tests

By Bundle

By Label

By Name Filter

Exclude Patterns

Parallel Execution

Run tests in parallel threads:

Benefits:

  • Faster execution

  • Better CPU utilization

  • Finds concurrency issues

Code Coverage

Generate coverage reports:

View report:

Test Helpers

Create reusable test utilities:

Database Strategies

Transaction Rollback

Database Cleaner

Fixtures

CI/CD Integration

GitHub Actions

Pre-commit Hook

Performance Tips

  1. Use labels for fast feedback

  2. Parallel execution

  3. Watch specific directories

  4. Skip slow tests during development

Common Issues

Out of Memory

Test Pollution

  • Use beforeEach/afterEach

  • Reset global state

  • Use transactions

Flaky Tests

  • Avoid time-dependent tests

  • Mock external services

  • Use fixed test data

See Also

  • - Run framework tests

  • - Generate coverage

  • - Debug tests

  • - Generate test files

wheels generate view [objectName] [name] [template]
wheels g view [objectName] [name] [template]

objectName

View path folder (e.g., user)

Required

name

Name of the file to create (e.g., edit)

Required

template

Optional template to use

wheels generate view user show
wheels generate view user show crud/show
wheels generate view user edit crud/edit
wheels generate view user _form crud/_form
wheels generate view product index crud/index
<!--- View file created by wheels generate view --->
<h1>Products</h1>

<p>
    #linkTo(text="New Product", action="new", class="btn btn-primary")#
</p>

<cfif products.recordCount>
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Created</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            <cfoutput query="products">
                <tr>
                    <td>#products.id#</td>
                    <td>#products.name#</td>
                    <td>#dateFormat(products.createdAt, "mm/dd/yyyy")#</td>
                    <td>
                        #linkTo(text="View", action="show", key=products.id, class="btn btn-sm btn-info")#
                        #linkTo(text="Edit", action="edit", key=products.id, class="btn btn-sm btn-warning")#
                        #linkTo(text="Delete", action="delete", key=products.id, 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.</p>
</cfif>
<h1>New Product</h1>

#includePartial("/products/form")#
#startFormTag(action=formAction)#

    <cfif product.hasErrors()>
        <div class="alert alert-danger">
            <h4>Please correct the following errors:</h4>
            #errorMessagesFor("product")#
        </div>
    </cfif>

    <div class="form-group">
        #textFieldTag(name="product[name]", value=product.name, label="Name", class="form-control")#
    </div>

    <div class="form-group">
        #textAreaTag(name="product[description]", value=product.description, label="Description", class="form-control", rows=5)#
    </div>

    <div class="form-group">
        #numberFieldTag(name="product[price]", value=product.price, label="Price", class="form-control", step="0.01")#
    </div>

    <div class="form-group">
        #selectTag(name="product[categoryId]", options=categories, selected=product.categoryId, label="Category", class="form-control", includeBlank="-- Select Category --")#
    </div>

    <div class="form-group">
        #checkBoxTag(name="product[isActive]", checked=product.isActive, label="Active", value=1)#
    </div>

    <div class="form-actions">
        #submitTag(value=submitLabel, class="btn btn-primary")#
        #linkTo(text="Cancel", action="index", class="btn btn-secondary")#
    </div>

#endFormTag()#
<h1>Product Details</h1>

<div class="card">
    <div class="card-body">
        <h2 class="card-title">#product.name#</h2>
        
        <dl class="row">
            <dt class="col-sm-3">Description</dt>
            <dd class="col-sm-9">#product.description#</dd>
            
            <dt class="col-sm-3">Price</dt>
            <dd class="col-sm-9">#dollarFormat(product.price)#</dd>
            
            <dt class="col-sm-3">Category</dt>
            <dd class="col-sm-9">#product.category.name#</dd>
            
            <dt class="col-sm-3">Status</dt>
            <dd class="col-sm-9">
                <cfif product.isActive>
                    <span class="badge badge-success">Active</span>
                <cfelse>
                    <span class="badge badge-secondary">Inactive</span>
                </cfif>
            </dd>
            
            <dt class="col-sm-3">Created</dt>
            <dd class="col-sm-9">#dateTimeFormat(product.createdAt, "mmm dd, yyyy h:nn tt")#</dd>
            
            <dt class="col-sm-3">Updated</dt>
            <dd class="col-sm-9">#dateTimeFormat(product.updatedAt, "mmm dd, yyyy h:nn tt")#</dd>
        </dl>
    </div>
    <div class="card-footer">
        #linkTo(text="Edit", action="edit", key=product.id, class="btn btn-primary")#
        #linkTo(text="Delete", action="delete", key=product.id, method="delete", confirm="Are you sure?", class="btn btn-danger")#
        #linkTo(text="Back to List", action="index", class="btn btn-secondary")#
    </div>
</div>

default

Standard HTML structure

General purpose

bootstrap

Bootstrap 5 components

Modern web apps

tailwind

Tailwind CSS classes

Utility-first design

ajax

AJAX-enabled views

Dynamic updates

mobile

Mobile-optimized

Responsive design

print

Print-friendly layout

Reports

email

Email template

Notifications

~/.commandbox/cfml/modules/wheels-cli/templates/views/
├── default/
│   ├── index.cfm
│   ├── show.cfm
│   ├── new.cfm
│   ├── edit.cfm
│   └── _form.cfm
├── bootstrap/
└── custom/
wheels generate view shared header,footer,navigation --partial
<!--- In layout or view --->
#includePartial("/shared/header")#
#includePartial("/products/form", product=product)#
#includePartial(partial="item", query=products)#
<!--- Generated view assumes layout wrapper --->
<h1>Page Title</h1>
<p>Content here</p>
wheels generate view products standalone --layout=false
<!DOCTYPE html>
<html>
<head>
    <title>Standalone View</title>
</head>
<body>
    <h1>Products</h1>
    <!-- Complete HTML structure -->
</body>
</html>
wheels generate view products index --format=html
wheels generate view emails welcome --format=txt
wheels generate view products search --template=ajax
<cfif isAjax()>
    <!--- Return just the content --->
    <cfoutput query="products">
        <div class="search-result">
            <h3>#products.name#</h3>
            <p>#products.description#</p>
        </div>
    </cfoutput>
<cfelse>
    <!--- Include full page structure --->
    <div id="search-results">
        <cfinclude template="_results.cfm">
    </div>
</cfif>
#startFormTag(action="create", method="post", class="needs-validation")#
    #textField(objectName="product", property="name", label="Product Name", class="form-control", required=true)#
    #textArea(objectName="product", property="description", label="Description", class="form-control", rows=5)#
    #select(objectName="product", property="categoryId", options=categories, label="Category", class="form-control")#
    #submitTag(value="Save Product", class="btn btn-primary")#
#endFormTag()#
#startFormTag(action="upload", multipart=true)#
    #fileFieldTag(name="productImage", label="Product Image", accept="image/*", class="form-control")#
    #submitTag(value="Upload", class="btn btn-primary")#
#endFormTag()#
wheels generate view products index --template=mobile
<div class="container-fluid">
    <div class="row">
        <div class="col-12">
            <h1 class="h3">Products</h1>
        </div>
    </div>
    
    <div class="row">
        <cfoutput query="products">
            <div class="col-12 col-md-6 col-lg-4 mb-3">
                <div class="card h-100">
                    <div class="card-body">
                        <h5 class="card-title">#products.name#</h5>
                        <p class="card-text">#products.description#</p>
                        #linkTo(text="View", action="show", key=products.id, class="btn btn-primary btn-sm")#
                    </div>
                </div>
            </div>
        </cfoutput>
    </div>
</div>
wheels generate view products index --locale=es
<h1>#l("products.title")#</h1>
<p>#l("products.description")#</p>

#linkTo(text=l("buttons.new"), action="new", class="btn btn-primary")#
wheels generate view products index
wheels generate test view products/index
component extends="wheels.Test" {
    
    function test_index_displays_products() {
        products = model("Product").findAll(maxRows=5);
        result = renderView(view="/products/index", products=products, layout=false);
        
        assert(Find("<h1>Products</h1>", result));
        assert(Find("New Product", result));
        assertEquals(products.recordCount, ListLen(result, "<tr>") - 1);
    }
    
}
<cfcache action="cache" timespan="#CreateTimeSpan(0,1,0,0)#">
    <!--- Expensive view content --->
    #includePartial("products/list", products=products)#
</cfcache>
<div class="products-container" data-lazy-load="/products/more">
    <!--- Initial content --->
</div>

<script>
// Implement lazy loading
</script>
<cfif products.recordCount>
    <!--- Show products --->
<cfelse>
    <div class="empty-state">
        <h2>No products found</h2>
        <p>Get started by adding your first product.</p>
        #linkTo(text="Add Product", action="new", class="btn btn-primary")#
    </div>
</cfif>
<div class="loading-spinner" style="display: none;">
    <i class="fa fa-spinner fa-spin"></i> Loading...
</div>
<cfif structKeyExists(variables, "error")>
    <div class="alert alert-danger">
        <strong>Error:</strong> #error.message#
    </div>
</cfif>
wheels generate controller
wheels scaffold
wheels generate test
wheels generate property [name] [options]
wheels g property [name] [options]

name

Table name

Required

column-name

Name of column

Required

data-type

Type of column

string

default

Default value for column

--null

Whether to allow null values

limit

Character or integer size limit for column

precision

Precision value for decimal columns

scale

Scale value for decimal columns

propertyName:type:option1:option2
wheels generate property user column-name=firstname
wheels generate property user column-name=isActive data-type=boolean default=0
wheels generate property user column-name=lastloggedin data-type=datetime
wheels generate property product column-name=price data-type=decimal precision=10 scale=2
wheels generate property user "fullName:calculated"
component extends="Model" {
    
    function init() {
        // Existing code
    }
    
}
component extends="Model" {
    
    function init() {
        // Existing code
        
        // Properties
        property(name="email", sql="email");
        
        // Validations
        validatesPresenceOf(properties="email");
        validatesUniquenessOf(properties="email");
        validatesFormatOf(property="email", regEx="^[^@\s]+@[^@\s]+\.[^@\s]+$");
    }
    
}
wheels generate property product "name:string:required description:text price:float:required:default=0.00 inStock:boolean:default=true"
component extends="Model" {
    
    function init() {
        // Properties
        property(name="name", sql="name");
        property(name="description", sql="description");
        property(name="price", sql="price", default=0.00);
        property(name="inStock", sql="in_stock", default=true);
        
        // Validations
        validatesPresenceOf(properties="name,price");
        validatesNumericalityOf(property="price", allowBlank=false, greaterThanOrEqualTo=0);
    }
    
}
wheels generate property comment "userId:integer:required:belongsTo=user postId:integer:required:belongsTo=post"
component extends="Model" {
    
    function init() {
        // Associations
        belongsTo(name="user", foreignKey="userId");
        belongsTo(name="post", foreignKey="postId");
        
        // Properties
        property(name="userId", sql="user_id");
        property(name="postId", sql="post_id");
        
        // Validations
        validatesPresenceOf(properties="userId,postId");
    }
    
}
wheels generate property user fullName:calculated --callbacks
component extends="Model" {
    
    function init() {
        // Properties
        property(name="fullName", sql="", calculated=true);
    }
    
    // Calculated property getter
    function getFullName() {
        return this.firstName & " " & this.lastName;
    }
    
}
component extends="wheels.migrator.Migration" hint="Add properties to product" {

    function up() {
        transaction {
            addColumn(table="products", columnName="sku", columnType="string", limit=50, null=false);
            addColumn(table="products", columnName="price", columnType="decimal", precision=10, scale=2, null=false, default=0.00);
            addColumn(table="products", columnName="stock", columnType="integer", null=true, default=0);
            
            addIndex(table="products", columnNames="sku", unique=true);
        }
    }
    
    function down() {
        transaction {
            removeIndex(table="products", columnNames="sku");
            removeColumn(table="products", columnName="stock");
            removeColumn(table="products", columnName="price");
            removeColumn(table="products", columnName="sku");
        }
    }

}

string:required

validatesPresenceOf, validatesLengthOf

string:unique

validatesUniquenessOf

email

validatesFormatOf with email regex

integer

validatesNumericalityOf(onlyInteger=true)

float

validatesNumericalityOf

boolean

validatesInclusionOf(list="true,false,0,1")

date

validatesFormatOf with date pattern

wheels generate property user "age:integer:min=18:max=120"
validatesNumericalityOf(property="age", greaterThanOrEqualTo=18, lessThanOrEqualTo=120);
wheels generate property user lastLoginAt:datetime --callbacks
function init() {
    // Properties
    property(name="lastLoginAt", sql="last_login_at");
    
    // Callbacks
    beforeUpdate("updateLastLoginAt");
}

private function updateLastLoginAt() {
    if (hasChanged("lastLoginAt")) {
        // Custom logic here
    }
}
wheels generate property order "status:string:default=pending:inclusion=pending,processing,shipped,delivered"
property(name="status", sql="status", default="pending");
validatesInclusionOf(property="status", list="pending,processing,shipped,delivered");
wheels generate property user "avatar:string:fileField"
property(name="avatar", sql="avatar");

// In the init() method
afterSave("processAvatarUpload");
beforeDelete("deleteAvatarFile");

private function processAvatarUpload() {
    if (hasChanged("avatar") && isUploadedFile("avatar")) {
        // Handle file upload
    }
}
wheels generate property user "preferences:text:json"
property(name="preferences", sql="preferences");

function getPreferences() {
    if (isJSON(this.preferences)) {
        return deserializeJSON(this.preferences);
    }
    return {};
}

function setPreferences(required struct value) {
    this.preferences = serializeJSON(arguments.value);
}
wheels generate property user "ssn:string:encrypted"
property(name="ssn", sql="ssn");

beforeSave("encryptSSN");
afterFind("decryptSSN");

private function encryptSSN() {
    if (hasChanged("ssn") && Len(this.ssn)) {
        this.ssn = encrypt(this.ssn, application.encryptionKey);
    }
}

private function decryptSSN() {
    if (Len(this.ssn)) {
        this.ssn = decrypt(this.ssn, application.encryptionKey);
    }
}
wheels generate property post "slug:string:unique:fromProperty=title"
property(name="slug", sql="slug");
validatesUniquenessOf(property="slug");

beforeValidation("generateSlug");

private function generateSlug() {
    if (!Len(this.slug) && Len(this.title)) {
        this.slug = createSlug(this.title);
    }
}

private function createSlug(required string text) {
    return reReplace(
        lCase(trim(arguments.text)),
        "[^a-z0-9]+",
        "-",
        "all"
    );
}
wheels generate property user "
    profile.bio:text
    profile.website:string
    profile.twitter:string
    profile.github:string
" --nested
wheels generate property post "publishedAt:timestamp deletedAt:timestamp:nullable"
wheels generate property user email:string
> Property 'email' already exists. Options:
> 1. Skip this property
> 2. Update existing property
> 3. Add with different name
> Choice:
wheels generate property model deletedAt:timestamp:nullable
wheels generate property document "version:integer:default=1 versionedAt:timestamp"
wheels generate property order "status:string:default=pending statusChangedAt:timestamp"
wheels generate property model "createdBy:integer:belongsTo=user updatedBy:integer:belongsTo=user"
# Run migration
wheels dbmigrate latest

# Generate property tests
wheels generate test model user

# Run tests
wheels test
wheels generate model
wheels dbmigrate create column
wheels generate test
wheels test run [spec] [options]

filter

Filter tests by pattern or name

group

Run specific test group

--coverage

Generate coverage report (boolean flag)

false

reporter

Test reporter format: console, junit, json, tap

console

--watch

Watch for file changes and rerun tests (boolean flag)

false

--verbose

Verbose output (boolean flag)

false

--fail-fast

Stop on first test failure (boolean flag)

false

wheels test run
wheels test run filter="User"
wheels test run filter="test_user_validation"
wheels test run --watch
wheels test run group="unit"
wheels test run group="integration"
wheels test run --coverage
wheels test run reporter=json
wheels test run reporter=junit
wheels test run reporter=tap
wheels test run --fail-fast
wheels test run --verbose --coverage reporter=console
/tests/
├── Application.cfc      # Test configuration
├── models/             # Model tests
│   ├── UserTest.cfc
│   └── ProductTest.cfc
├── controllers/        # Controller tests
│   ├── UsersTest.cfc
│   └── ProductsTest.cfc
├── views/             # View tests
├── integration/       # Integration tests
└── helpers/          # Test helpers
component extends="testbox.system.BaseSpec" {

    function run() {
        describe("User Model", function() {

            beforeEach(function() {
                // Reset test data
                application.wirebox.getInstance("User").deleteAll();
            });

            it("validates required fields", function() {
                var user = model("User").new();
                expect(user.valid()).toBeFalse();
                expect(user.errors).toHaveKey("email");
                expect(user.errors).toHaveKey("username");
            });

            it("saves with valid data", function() {
                var user = model("User").new(
                    email="[email protected]",
                    username="testuser",
                    password="secret123"
                );
                expect(user.save()).toBeTrue();
                expect(user.id).toBeGT(0);
            });

            it("prevents duplicate emails", function() {
                var user1 = model("User").create(
                    email="[email protected]",
                    username="user1"
                );

                var user2 = model("User").new(
                    email="[email protected]",
                    username="user2"
                );

                expect(user2.valid()).toBeFalse();
                expect(user2.errors.email).toContain("already exists");
            });

        });
    }

}
component extends="testbox.system.BaseSpec" {

    function run() {
        describe("Products Controller", function() {

            it("lists all products", function() {
                // Create test data
                var product = model("Product").create(name="Test Product");

                // Make request
                var event = execute(
                    event="products.index",
                    renderResults=true
                );

                // Assert response
                expect(event.getRenderedContent()).toInclude("Test Product");
                expect(event.getValue("products")).toBeArray();
            });

            it("requires auth for create", function() {
                var event = execute(
                    event="products.create",
                    renderResults=false
                );

                expect(event.getValue("relocate_URI")).toBe("/login");
            });

        });
    }

}
component {
    this.name = "WheelsTestingSuite" & Hash(GetCurrentTemplatePath());

    // Use test datasource
    this.datasources["wheelstestdb"] = {
        url = "jdbc:h2:mem:wheelstestdb;MODE=MySQL"
    };
    this.datasource = "wheelstestdb";

    // Test settings
    this.testbox = {
        testBundles = "tests",
        recurse = true,
        reporter = "simple",
        labels = "",
        options = {}
    };
}
wheels test run --watch
[TestBox Watch] Monitoring for changes...
[TestBox Watch] Watching: /tests, /models, /controllers

[14:23:45] Change detected: models/User.cfc
[14:23:45] Running tests...

✓ User Model > validates required fields (12ms)
✓ User Model > saves with valid data (45ms)
✓ User Model > prevents duplicate emails (23ms)

Tests: 3 passed, 0 failed
Time: 80ms

[TestBox Watch] Waiting for changes...
wheels test run reporter=simple
wheels test run reporter=text
wheels test run reporter=json
{
    "totalDuration": 523,
    "totalSpecs": 25,
    "totalPass": 24,
    "totalFail": 1,
    "totalError": 0,
    "totalSkipped": 0
}
wheels test run reporter=junit outputFile=results.xml
wheels test run reporter=tap
# Run only model tests
wheels test run bundles=models

# Run multiple bundles
wheels test run bundles=models,controllers
it("can authenticate", function() {
    // test code
}).labels("auth,critical");
# Run only critical tests
wheels test run labels=critical

# Run auth OR api tests
wheels test run labels=auth,api
# Run tests matching pattern
wheels test run filter="user"
wheels test run filter="validate*"
# Skip slow tests
wheels test run excludes="*slow*,*integration*"
wheels test run threads=4
wheels test run --coverage coverageOutputDir=coverage/
open coverage/index.html
// /tests/helpers/TestHelper.cfc
component {

    function createTestUser(struct overrides={}) {
        var defaults = {
            email: "test#CreateUUID()#@example.com",
            username: "user#CreateUUID()#",
            password: "testpass123"
        };

        return model("User").create(
            argumentCollection = defaults.append(arguments.overrides)
        );
    }

    function loginAs(required user) {
        session.userId = arguments.user.id;
        session.isAuthenticated = true;
    }

}
function beforeAll() {
    transaction action="begin";
}

function afterAll() {
    transaction action="rollback";
}
function beforeEach() {
    queryExecute("DELETE FROM users");
    queryExecute("DELETE FROM products");
}
function loadFixtures() {
    var users = deserializeJSON(
        fileRead("/tests/fixtures/users.json")
    );

    for (var userData in users) {
        model("User").create(userData);
    }
}
- name: Run tests
  run: |
    wheels test run reporter=junit outputFile=test-results.xml

- name: Upload results
  uses: actions/upload-artifact@v4
  with:
    name: test-results
    path: test-results.xml
#!/bin/bash
# .git/hooks/pre-commit

echo "Running tests..."
wheels test run labels=unit

if [ $? -ne 0 ]; then
    echo "Tests failed. Commit aborted."
    exit 1
fi
wheels test run labels=unit  # Fast
wheels test run labels=integration  # Slow
wheels test run threads=4
wheels test run tests/models --watch
wheels test run excludes="*integration*"
# Increase memory
box server set jvm.heapSize=1024
box server restart
wheels test
wheels test coverage
wheels test debug
wheels generate test

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

Argument
Description
Default

name

Resource name (singular)

Required

Options

Option
Description
Default

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

  1. Model (/models/Product.cfc)

    • Properties and validations

    • Associations

    • Business logic

  2. Controller (/controllers/Products.cfc)

    • All CRUD actions

    • Flash messages

    • Error handling

  3. 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

  4. Migration (/app/migrator/migrations/[timestamp]_create_products.cfc)

    • Create table

    • Add indexes

    • Define columns

  5. Tests (if enabled)

    • Model tests

    • Controller tests

    • Integration tests

API Scaffold

  1. Model - Same as standard

  2. API Controller - JSON responses only

  3. Migration - Same as standard

  4. API Tests - JSON response tests

  5. 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

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

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

<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

#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

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

  1. Run migration (if not using --migrate):

    wheels dbmigrate latest
  2. Add routes to /config/routes.cfm:

    <cfset resources("products")>
  3. Restart application:

    wheels reload
  4. Test the scaffold:

    • Visit /products to see the index

    • Create, 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:

  1. CLI Templates - Default templates are located in the CLI module at /cli/templates/

  2. 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:

  1. First checks /app/snippets/[template-name]

  2. Falls back to /cli/templates/[template-name] if not found in app

Customizing Templates

To customize scaffold output:

  1. Copy the template you want to customize from /cli/templates/ to /app/snippets/

  2. Modify the template to match your project's needs

  3. 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 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

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

  1. Properties: Define all needed properties upfront

  2. Associations: Include relationships in initial scaffold

  3. Validation: Add custom validations after generation

  4. Testing: Always generate and run tests

  5. Routes: Use RESTful resources when possible

  6. Security: Add authentication/authorization

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

wheels dbmigrate create table

Generate a migration file for creating a new database table.

Synopsis

wheels dbmigrate create table name=<table_name> [--force] [--id] primary-key=<key_name>

Alias: wheels db create table

Description

The dbmigrate create table command generates a migration file that creates a new database table. The generated migration includes the table structure following Wheels conventions.

Parameters

Parameter
Type
Required
Default
Description

name

string

Yes

-

The name of the table to create

--force

boolean

No

false

Force the creation of the table

--id

boolean

No

true

Auto create ID column as autoincrement ID

primary-key

string

No

"id"

Overrides the default primary key column name

Notes About Column Definition

The generated migration file will contain a basic table structure. You'll need to manually edit the migration file to add columns with their types and options. The migration template includes comments showing how to add columns.

Examples

Create a basic table

wheels dbmigrate create table name=user

Create table without ID column

wheels dbmigrate create table name=user_roles --id=false

Create table with custom primary key

wheels dbmigrate create table name=products primary-key=productCode

Force creation (overwrite existing)

wheels dbmigrate create table name=users --force

Generated Migration Example

For the command:

wheels dbmigrate create table name=users

Generates a migration file that you can customize:

component extends="wheels.migrator.Migration" hint="create users table" {

    function up() {
        transaction {
            t = createTable(name="users", force=false, id=true, primaryKey="id");
            // Add your columns here
            // t.string(columnName="name");
            // t.integer(columnName="age");
            t.timestamps();
            t.create();
        }
    }

    function down() {
        transaction {
            dropTable("users");
        }
    }

}

Use Cases

Standard Entity Table

Create a typical entity table:

# Generate the migration
wheels dbmigrate create table name=customer

# Then edit the migration file to add columns

Join Table for Many-to-Many

Create a join table without primary key:

wheels dbmigrate create table name=products_categories --id=false

Table with Custom Primary Key

Create a table with non-standard primary key:

wheels dbmigrate create table name=legacy_customer primary-key=customer_code

Best Practices

1. Use Singular Table Names

Wheels conventions expect singular table names:

# Good
wheels dbmigrate create table name=user
wheels dbmigrate create table name=product

# Avoid
wheels dbmigrate create table name=users
wheels dbmigrate create table name=products

2. Edit Migration Files

After generating the migration, edit it to add columns:

// In the generated migration file
t = createTable(name="orders", force=false, id=true, primaryKey="id");
t.integer(columnName="customer_id");
t.decimal(columnName="total", precision=10, scale=2);
t.string(columnName="status", default="pending");
t.timestamps();
t.create();

3. Plan Your Schema

Think through your table structure before creating:

  • Primary key strategy

  • Required columns and their types

  • Foreign key relationships

  • Indexes needed for performance

Working with the Generated Migration

The command generates a basic migration template. You'll need to edit it to add columns:

component extends="wheels.migrator.Migration" {
    function up() {
        transaction {
            t = createTable(name="tableName", force=false, id=true, primaryKey="id");
            // Add your columns here:
            t.string(columnName="name");
            t.integer(columnName="age");
            t.boolean(columnName="active", default=true);
            t.text(columnName="description");
            // MySQL only: use size parameter for larger text fields
            t.text(columnName="content", size="mediumtext"); // 16MB
            t.text(columnName="largeContent", size="longtext"); // 4GB
            t.timestamps();
            t.create();
        }
    }
    
    function down() {
        transaction {
            dropTable("tableName");
        }
    }
}

Notes

  • Table names should follow your database naming conventions

  • The migration automatically handles rollback with dropTable()

  • Column order in the command is preserved in the migration

  • Use wheels dbmigrate up to run the generated migration

Related Commands

  • wheels dbmigrate create column - Add columns to existing table

  • wheels dbmigrate create blank - Create custom migration

  • wheels dbmigrate remove table - Create table removal migration

  • wheels dbmigrate up - Run migrations

  • wheels dbmigrate info - View migration status

Beginner Tutorial: Hello Database

A quick tutorial that demonstrates how quickly you can get database connectivity up and running with Wheels.

Wheels's built in model provides your application with some simple and powerful functionality for interacting with databases. To get started, you will make some simple configurations, call some functions within your controllers, and that's it. Best yet, you will rarely ever need to write SQL code to get those redundant CRUD tasks out of the way.

Our Sample Application: User Management

We'll learn by building part of a sample user management application. This tutorial will teach you the basics of setting up a resource that interacts with the Wheels ORM.

Download source code

You can download all the source code for this sample application from https://github.com/dhgassoc/Wheels-Beginner-Tutorial-Hello-Database

Setting up the Data Source

By default, Wheels will connect to a data source wheels.dev. To change this default behavior, open the file at /config/settings.cfm. In a fresh install of Wheels, you'll see the follwing code:

/config/settings.cfm
<cfscript>
	/*
		Use this file to configure your application.
		You can also use the environment specific files (e.g. /config/production/settings.cfm) to override settings set here.
		Don't forget to issue a reload request (e.g. reload=true) after making changes.
		See https://guides.wheels.dev/2.5.0/v/3.0.0-snapshot/working-with-wheels/configuration-and-defaults for more info.
	*/

	/*
		You can change the "wheels.dev" value from the two functions below to set your datasource.
		You can change the the value for the "dataSourceName" to set a default datasource to be used throughout your application.
		You can also change the value for the "coreTestDataSourceName" to set your testing datasource.
	*/
	set(coreTestDataSourceName="wheels.dev");
	set(dataSourceName="wheels.dev");
    // set(dataSourceUserName="");
    // set(dataSourcePassword="");

	/*
		If you comment out the following line, Wheels will try to determine the URL rewrite capabilities automatically.
		The "URLRewriting" setting can bet set to "on", "partial" or "off".
		To run with "partial" rewriting, the "cgi.path_info" variable needs to be supported by the web server.
		To run with rewriting set to "on", you need to apply the necessary rewrite rules on the web server first.
	*/
	set(URLRewriting="On");

	// Reload your application with ?reload=true&password=wheels.dev
	set(reloadPassword="wheels.dev");

	// CLI-Appends-Here
</cfscript>

These lines provide Wheels with the necessary information about the data source, URL rewriting, and reload password for your application, and include the appropriate values. This may include values for dataSourceName, dataSourceUserName, and dataSourcePassword. More on URL rewriting and reload password later.

/config/settings.cfm
set(dataSourceName="back2thefuture");
// set(dataSourceUserName="marty");
// set(dataSourcePassword="mcfly");

Our Sample Data Structure

Wheels supports MySQL, SQL Server, PostgreSQL, and H2. It doesn't matter which DBMS you use for this tutorial; we will all be writing the same CFML code to interact with the database. Wheels does everything behind the scenes that needs to be done to work with each DBMS.

That said, here's a quick look at a table that you'll need in your database, named users:

Column Name
Data Type
Extra

id

int

auto increment

username

varchar(100)

email

varchar(255)

passwd

varchar(15)

Note a couple things about this users table:

  1. The table name is plural.

  2. The table has an auto-incrementing primary key named id.

These are database conventions used by Wheels. This framework strongly encourages that everyone follow convention over configuration. That way everyone is doing things mostly the same way, leading to less maintenance and training headaches down the road.

Fortunately, there are ways of going outside of these conventions when you really need it. But let's learn the conventional way first. Sometimes you need to learn the rules before you can know how to break them.

Creating Routes for the users Resource

Next, open the file at /config/routes.cfm. You will see contents similar to this:

/config/routes.cfm
mapper()
    .wildcard()
    .root(method = "get")
.end();

We are going to create a section of our application for listing, creating, updating, and deleting user records. In Wheels routing, this requires a plural resource, which we'll name users.

Because a users resource is more specific than the "generic" routes provided by Wheels, we'll list it first in the chain of mapper method calls:

/config/routes.cfm
mapper()
    .resources("users")
    .wildcard()
    .root(method = "get")
.end();

This will create URL endpoints for creating, reading, updating, and deleting user records:

Name
Method
URL Path
Description

users

GET

/users

Lists users

newUsers

GET

/users/new

Display a form for creating a user record

users

POST

/users

Form posts a new user record to be created

editUser

GET

/users/[id]/edit

Displays a form for editing a user record

user

PATCH

/users/[id]

Form posts an existing user record to be updated

user

DELETE

/users/[id]

Deletes a user record

  • Name is referenced in your code to tell Wheels where to point forms and links.

  • Method is the HTTP verb that Wheels listens for to match up the request.

  • URL Path is the URL that Wheels listens for to match up the request.

Don't forget to reload

You will need to reload your application after adding new routes!

Creating Users

First, let's create a simple form for adding a new user to the users table. To do this, we will use Wheels's form helper functions. Wheels includes a whole range of functions that simplifies all of the tasks that you need to display forms and communicate errors to the user.

Creating the Form

Now create a new file in app/views/users called new.cfm. This will contain the view code for our simple form.

Next, add these lines of code to the new file:

app/views/users/new.cfm
<cfoutput>

<h1>New User</h1>

#startFormTag(route="users")#
    <div>
        #textField(objectName="user", property="username", label="Username")#
    </div>

    <div>
        #textField(objectName="user", property="email", label="Email")#
    </div>

    <div>
        #passwordField(
            objectName="user",
            property="passwd",
            label="Password"
        )#
    </div>

    <div>#submitTag()#</div>
#endFormTag()#

</cfoutput>

Form Helpers

What we've done here is use form helpers to generate all of the form fields necessary for creating a new user in our database. It may feel a little strange using functions to generate form elements, but it will soon become clear why we're doing this. Trust us on this one… you'll love it!

To generate the form tag's action attribute, the startFormTag() function takes parameters similar to the linkTo()function that we introduced in the Beginner Tutorial: Hello World tutorial. We can pass in controller, action, key, and other route- and parameter-defined URLs just like we do with linkTo().

To end the form, we use the endFormTag() function. Easy enough.

The textField() and passwordField() helpers are similar. As you probably guessed, they create <input> elements with type="text" and type="password", respectively. And the submitTag() function creates an <input type="submit" /> element.

One thing you'll notice is the textField() and passwordField() functions accept arguments called objectName and property. As it turns out, this particular view code will throw an error because these functions are expecting an object named user. Let's fix that.

Supplying the Form with Data

All of the form helper calls in our view specify an objectName argument with a reference to a variable named user. That means that we need to supply our view code with an object called user. Because the controller is responsible for providing the views with data, we'll set it there.

Create a new ColdFusion component at app/controllers/Users.cfc.

As it turns out, our controller needs to provide the view with a blank user object (whose instance variable will also be called user in this case). In our new action, we will use the model() function to generate a new instance of the user model.

To get a blank set of properties in the model, we'll also call the generated model's new() method.

app/controllers/Users.cfc
component extends="Controller" {
    function config(){}

    function new() {
        user = model("user").new();
    }
}

Wheels will automatically know that we're talking about the users database table when we instantiate a user model. The convention: database tables are plural and their corresponding Wheels models are singular.

Why is our model name singular instead of plural? When we're talking about a single record in the users database, we represent that with an individual model object. So the users table contains many user objects. It just works better in conversation.

The Generated Form

Now when we run the URL at http://localhost/users/new, we'll see the form with the fields that we defined.

The HTML generated by your application will look something like this:

/users/new
<h1>New User</h1>

<form action="/users" method="post">
    <div>
        <label for="user-username">
            Username
            <input id="user-username" type="text" value="" name="user&#x5b;username&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-email">
            Email
            <input id="user-email" type="text" value="" name="user&#x5b;email&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-passwd">
            Password
            <input id="user-passwd" type="password" value="" name="user&#x5b;passwd&#x5d;">
        </label>
    </div>

    <div><input value="Save&#x20;changes" type="submit"></div>
</form>

So far we have a fairly well-formed, accessible form, without writing a bunch of repetitive markup.

Handling the Form Submission

Next, we'll code the create action in the controller to handle the form submission and save the new user to the database.

A basic way of doing this is using the model object's create() method:

app/controllers/Users.cfc
function create() {
    user = model("user").create(params.user);

    redirectTo(
        route="users",
        success="User created successfully."
    );
}

Because we used the objectName argument in the fields of our form, we can access the user data as a struct in the params struct.

There are more things that we can do in the create action to handle validation, but let's keep it simple in this tutorial.

Listing Users

Notice that our create action above redirects the user to the users index route using the redirectTo() function. We'll use this action to list all users in the system with "Edit" links. We'll also provide a link to the "New User" form that we just coded.

First, let's get the data that the listing needs. Create an action named index in the users controller like so:

app/controllers/Users.cfc
function index() {
    users = model("user").findAll(order="username");
}

This call to the model's findAll() method will return a query object of all users in the system. By using the method's order argument, we're also telling the database to order the records by username.

In the view at app/views/users/index.cfm, it's as simple as looping through the query and outputting the data

app/views/users/index.cfm
<cfoutput>

<h1>Users</h1>

<p>#linkTo(text="New User", route="newUser")#</p>

<table>
    <thead>
        <tr>
            <th>Username</th>
            <th>Email</th>
            <th colspan="2"></th>
        </tr>
    </thead>
    <tbody>
        <cfloop query="users">
            <tr>
                <td>
                    #EncodeForHtml(users.username)#
                </td>
                <td>
                    #EncodeForHtml(users.email)#
                </td>
                <td>
                    #linkTo(
                        text="Edit",
                        route="editUser",
                        key=users.id,
                        title="Edit #users.username#"
                    )#
                </td>
                <td>
                    #buttonTo(
                        text="Delete",
                        route="user",
                        key=users.id,
                        method="delete",
                        title="Delete #users.username#"
                    )#
                </td>
            </tr>
        </cfloop>
    </tbody>
</table>

</cfoutput>

When to use EncodeForHtml

You'll see references to EncodeForHtml in some of our examples that output data. This helps escape HTML code in data that attackers could use to embed inject harmful JavaScript. (This is commonly referred to as an "XSS attack," short for "Cross-site Scripting attack.")

A rule of thumb: you do not need to use EncodeForHtml when passing values into Wheels helpers like linkTo, buttonTo, startFormTag, textField, etc. However, you need to escape data that is displayed directly onto the page without a Wheels helper.

Editing Users

We'll now show another cool aspect of form helpers by creating a screen for editing users.

Coding the Edit Form

You probably noticed in the code listed above that we'll have an action for editing a single users record. We used the linkTo() form helper function to add an "Edit" button to the form. This action expects a key as well.

Because in the linkTo() form helper function we specified the parameter key, Wheels adds this parameter into the URL when generating the route.

Wheels will automatically add the provided 'key' from the URL to the params struct in the controllers edit() function.

Given the provided key, we'll have the action load the appropriate user object to pass on to the view:

app/controllers/Users.cfc
function edit() {
    user = model("user").findByKey(params.key);
}

The view at app/views/users/edit.cfm looks almost exactly the same as the view for creating a user:

app/views/users/edit.cfm
<cfoutput>

<h1>Edit User #EncodeForHtml(user.username)#</h1>

#startFormTag(route="user", key=user.key(), method="patch")#
    <div>
        #textField(objectName="user", property="username", label="Username")#
    </div>

    <div>
        #textField(objectName="user", property="email", label="Email")#
    </div>

    <div>
        #passwordField(
            objectName="user",
            property="passwd",
            label="Password"
        )#
    </div>

    <div>#submitTag()#</div>
#endFormTag()#

</cfoutput>

But an interesting thing happens. Because the form fields are bound to the user object via the form helpers' objectName arguments, the form will automatically provide default values based on the object's properties.

With the user model populated, we'll end up seeing code similar to this:

app/views/users/edit.cfm
<h1>Edit User Homer Simpson</h1>

<form action="/users/1" method="post">
    <input type="hidden" name="_method" value="patch">

    <div>
        <input type="hidden" name="user&#x5b;id&#x5d;" value="15">
    </div>

    <div>
        <label for="user-username">
            Name
            <input
                id="user-username"
                type="text"
                value="Homer Simpson"
                name="user&#x5b;username&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-email">
            Email
            <input
                id="user-email"
                type="text"
                value="[email protected]"
                name="user&#x5b;email&#x5d;">
        </label>
    </div>

    <div>
        <label for="user-passwd">
            Password
            <input
                id="user-passwd"
                type="password"
                value="donuts.mmm"
                name="user&#x5b;passwd&#x5d;">
        </label>
    </div>

    <div><input value="Save&#x20;changes" type="submit"></div>
</form>

Pretty cool, huh?

Opportunities for Refactoring

There's a lot of repetition in the new and edit forms. You'd imagine that we could factor out most of this code into a single view file. To keep this tutorial from becoming a book, we'll just continue on knowing that this could be better.

Handing the Edit Form Submission

Now we'll create the update action. This will be similar to the create action, except it will be updating the user object:

app/controllers/Users.cfc
function update() {
    user = model("user").findByKey(params.key);
    user.update(params.user);

    redirectTo(
        route="editUser",
        key=user.id,
        success="User updated successfully."
    );
}

To update the user, simply call its update() method with the user struct passed from the form via params. It's that simple.

After the update, we'll add a success message using the Flash and send the end user back to the edit form in case they want to make more changes.

Deleting Users

Notice in our listing above that we have a delete action. Here's what it would look like:

app/controllers/Users.cfc
function delete() {
    user = model("user").findByKey(params.key);
    user.delete();

    redirectTo(
        route="users",
        success="User deleted successfully."
    );
}

We simply load the user using the model's findByKey() method and then call the object's delete() method. That's all there is to it.

Database Says Hello

We've shown you quite a few of the basics in getting a simple user database up and running. We hope that this has whet your appetite to see some of the power packed into the Wheels framework. There's plenty more.

Be sure to read on to some of the related chapters listed below to learn more about working with Wheels's ORM.

wheels generate resource

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

  1. Use singular names: product not products

  2. Define all properties: Include types and validations

  3. Add indexes: For frequently queried fields

  4. Include tests: Don't skip test generation

  5. Use namespaces: For admin or API resources

  6. 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

wheels generate api-resource

Generate a complete API resource with model, API controller, and routes.

⚠️ Note: This command is currently marked as broken/disabled in the codebase. The documentation below represents the intended functionality when the command is restored.

Synopsis

Description

The wheels generate api-resource command creates a complete RESTful API resource including model, API-specific controller (no views), routes, and optionally database migrations and tests. It's optimized for building JSON APIs following REST conventions.

Current Status

This command is temporarily disabled. Use alternative approaches:

Arguments (When Enabled)

Argument
Description
Default

Options (When Enabled)

Option
Description
Default

Intended Functionality

Basic API Resource

Would generate:

  • Model: /models/Product.cfc

  • Controller: /controllers/api/v1/Products.cfc

  • Route: API namespace with versioning

  • Migration: Database migration file

  • Tests: API integration tests

Generated API Controller

/controllers/api/v1/Products.cfc:

API Routes

Generated in /config/routes.cfm:

API Documentation

Would generate OpenAPI/Swagger documentation:

Workaround Implementation

Until the command is fixed, implement API resources manually:

1. Generate Model

2. Create API Controller

Create /controllers/api/v1/Products.cfc manually with the code above.

3. Add Routes

4. Create Tests

API Features

Authentication

Rate Limiting

CORS Headers

Testing API Resources

Integration Tests

Best Practices

  1. Version your API: Use URL versioning (/api/v1/)

  2. Use consistent formats: JSON API or custom format

  3. Include pagination: Limit response sizes

  4. Add filtering: Allow query parameters

  5. Implement sorting: Support field sorting

  6. Handle errors consistently: Standard error format

  7. Document thoroughly: OpenAPI/Swagger specs

  8. Add authentication: Secure endpoints

  9. Rate limit: Prevent abuse

  10. Test extensively: Integration tests

See Also

  • - Generate full resources

  • - Generate controllers

  • - Generate models

  • - Generate routes

wheels generate api-resource [name] [properties] [options]
wheels g api-resource [name] [properties] [options]
# Option 1: Use regular resource with --api flag
wheels generate resource product name:string price:float --api

# Option 2: Generate components separately
wheels generate model product name:string price:float
wheels generate controller api/products --api
wheels generate route products --api --namespace=api

name

Resource name (typically singular)

Required

properties

Property definitions (name:type)

--version

API version (v1, v2, etc.)

v1

--format

Response format (json, xml)

json

--auth

Include authentication

false

--pagination

Include pagination

true

--filtering

Include filtering

true

--sorting

Include sorting

true

--skip-model

Skip model generation

false

--skip-migration

Skip migration generation

false

--skip-tests

Skip test generation

false

--namespace

API namespace

api

--force

Overwrite existing files

false

--help

Show help information

wheels generate api-resource product name:string price:float description:text
component extends="Controller" {
    
    function init() {
        provides("json");
        
        // Filters
        filters(through="authenticate", except="index,show");
        filters(through="findProduct", only="show,update,delete");
        filters(through="parseApiParams", only="index");
    }
    
    function index() {
        // Pagination
        page = params.page ?: 1;
        perPage = Min(params.perPage ?: 25, 100);
        
        // Filtering
        where = [];
        if (StructKeyExists(params, "filter")) {
            if (StructKeyExists(params.filter, "name")) {
                ArrayAppend(where, "name LIKE :name");
                params.name = "%#params.filter.name#%";
            }
            if (StructKeyExists(params.filter, "minPrice")) {
                ArrayAppend(where, "price >= :minPrice");
                params.minPrice = params.filter.minPrice;
            }
        }
        
        // Sorting
        order = "createdAt DESC";
        if (StructKeyExists(params, "sort")) {
            order = parseSort(params.sort);
        }
        
        // Query
        products = model("Product").findAll(
            where=ArrayToList(where, " AND "),
            order=order,
            page=page,
            perPage=perPage,
            returnAs="objects"
        );
        
        // Response
        renderWith({
            data: serializeProducts(products),
            meta: {
                pagination: {
                    page: products.currentPage,
                    perPage: products.perPage,
                    total: products.totalRecords,
                    pages: products.totalPages
                }
            },
            links: {
                self: urlFor(route="apiV1Products", params=params),
                first: urlFor(route="apiV1Products", params=params, page=1),
                last: urlFor(route="apiV1Products", params=params, page=products.totalPages),
                prev: products.currentPage > 1 ? urlFor(route="apiV1Products", params=params, page=products.currentPage-1) : "",
                next: products.currentPage < products.totalPages ? urlFor(route="apiV1Products", params=params, page=products.currentPage+1) : ""
            }
        });
    }
    
    function show() {
        renderWith({
            data: serializeProduct(product),
            links: {
                self: urlFor(route="apiV1Product", key=product.id)
            }
        });
    }
    
    function create() {
        product = model("Product").new(deserializeProduct(params));
        
        if (product.save()) {
            renderWith(
                data={
                    data: serializeProduct(product),
                    links: {
                        self: urlFor(route="apiV1Product", key=product.id)
                    }
                },
                status=201,
                headers={"Location": urlFor(route="apiV1Product", key=product.id)}
            );
        } else {
            renderWith(
                data={
                    errors: formatErrors(product.allErrors())
                },
                status=422
            );
        }
    }
    
    function update() {
        if (product.update(deserializeProduct(params))) {
            renderWith({
                data: serializeProduct(product),
                links: {
                    self: urlFor(route="apiV1Product", key=product.id)
                }
            });
        } else {
            renderWith(
                data={
                    errors: formatErrors(product.allErrors())
                },
                status=422
            );
        }
    }
    
    function delete() {
        if (product.delete()) {
            renderWith(data={}, status=204);
        } else {
            renderWith(
                data={
                    errors: [{
                        status: "400",
                        title: "Bad Request",
                        detail: "Could not delete product"
                    }]
                },
                status=400
            );
        }
    }
    
    // Private methods
    
    private function findProduct() {
        product = model("Product").findByKey(params.key);
        if (!IsObject(product)) {
            renderWith(
                data={
                    errors: [{
                        status: "404",
                        title: "Not Found",
                        detail: "Product not found"
                    }]
                },
                status=404
            );
        }
    }
    
    private function authenticate() {
        if (!StructKeyExists(headers, "Authorization")) {
            renderWith(
                data={
                    errors: [{
                        status: "401",
                        title: "Unauthorized",
                        detail: "Missing authentication"
                    }]
                },
                status=401
            );
        }
        // Implement authentication logic
    }
    
    private function parseApiParams() {
        // Parse JSON API params
        if (StructKeyExists(params, "_json")) {
            StructAppend(params, params._json, true);
        }
    }
    
    private function parseSort(required string sort) {
        local.allowedFields = ["name", "price", "createdAt"];
        local.parts = ListToArray(arguments.sort);
        local.order = [];
        
        for (local.part in local.parts) {
            local.desc = Left(local.part, 1) == "-";
            local.field = local.desc ? Right(local.part, Len(local.part)-1) : local.part;
            
            if (ArrayFindNoCase(local.allowedFields, local.field)) {
                ArrayAppend(local.order, local.field & (local.desc ? " DESC" : " ASC"));
            }
        }
        
        return ArrayToList(local.order);
    }
    
    private function serializeProducts(required array products) {
        local.result = [];
        for (local.product in arguments.products) {
            ArrayAppend(local.result, serializeProduct(local.product));
        }
        return local.result;
    }
    
    private function serializeProduct(required any product) {
        return {
            type: "products",
            id: arguments.product.id,
            attributes: {
                name: arguments.product.name,
                price: arguments.product.price,
                description: arguments.product.description,
                createdAt: DateTimeFormat(arguments.product.createdAt, "iso"),
                updatedAt: DateTimeFormat(arguments.product.updatedAt, "iso")
            },
            links: {
                self: urlFor(route="apiV1Product", key=arguments.product.id)
            }
        };
    }
    
    private function deserializeProduct(required struct data) {
        if (StructKeyExists(arguments.data, "data")) {
            return arguments.data.data.attributes;
        }
        return arguments.data;
    }
    
    private function formatErrors(required array errors) {
        local.result = [];
        for (local.error in arguments.errors) {
            ArrayAppend(local.result, {
                status: "422",
                source: {pointer: "/data/attributes/#local.error.property#"},
                title: "Validation Error",
                detail: local.error.message
            });
        }
        return local.result;
    }
    
}
<cfset namespace("api")>
    <cfset namespace("v1")>
        <cfset resources(name="products", except="new,edit")>
        
        <!--- Additional API routes --->
        <cfset post(pattern="products/[key]/activate", to="products##activate", on="member")>
        <cfset get(pattern="products/search", to="products##search", on="collection")>
    </cfset>
</cfset>
openapi: 3.0.0
info:
  title: Products API
  version: 1.0.0
  
paths:
  /api/v1/products:
    get:
      summary: List products
      parameters:
        - name: page
          in: query
          schema:
            type: integer
        - name: perPage
          in: query
          schema:
            type: integer
        - name: filter[name]
          in: query
          schema:
            type: string
        - name: sort
          in: query
          schema:
            type: string
      responses:
        200:
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductList'
    
    post:
      summary: Create product
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductInput'
      responses:
        201:
          description: Created
        422:
          description: Validation error
wheels generate model product name:string price:float description:text
<!--- In /config/routes.cfm --->
<cfset namespace("api")>
    <cfset namespace("v1")>
        <cfset resources(name="products", except="new,edit")>
    </cfset>
</cfset>
wheels generate test controller api/v1/products
// Bearer token authentication
private function authenticate() {
    local.token = GetHttpRequestData().headers["Authorization"] ?: "";
    local.token = ReReplace(local.token, "^Bearer\s+", "");
    
    if (!Len(local.token) || !isValidToken(local.token)) {
        renderWith(
            data={error: "Unauthorized"},
            status=401
        );
    }
}
// In controller init()
filters(through="rateLimit");

private function rateLimit() {
    local.key = "api_rate_limit_" & request.remoteAddress;
    local.limit = 100; // requests per hour
    
    if (!StructKeyExists(application, local.key)) {
        application[local.key] = {
            count: 0,
            reset: DateAdd("h", 1, Now())
        };
    }
    
    if (application[local.key].reset < Now()) {
        application[local.key] = {
            count: 0,
            reset: DateAdd("h", 1, Now())
        };
    }
    
    application[local.key].count++;
    
    if (application[local.key].count > local.limit) {
        renderWith(
            data={error: "Rate limit exceeded"},
            status=429,
            headers={
                "X-RateLimit-Limit": local.limit,
                "X-RateLimit-Remaining": 0,
                "X-RateLimit-Reset": DateTimeFormat(application[local.key].reset, "iso")
            }
        );
    }
}
// In controller init()
filters(through="setCorsHeaders");

private function setCorsHeaders() {
    header name="Access-Control-Allow-Origin" value="*";
    header name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS";
    header name="Access-Control-Allow-Headers" value="Content-Type, Authorization";
    
    if (request.method == "OPTIONS") {
        renderWith(data={}, status=200);
    }
}
component extends="wheels.Test" {
    
    function setup() {
        super.setup();
        model("Product").deleteAll();
    }
    
    function test_get_products_returns_json() {
        products = createProducts(3);
        
        result = $request(
            route="apiV1Products",
            method="GET",
            headers={"Accept": "application/json"}
        );
        
        assert(result.status == 200);
        data = DeserializeJSON(result.body);
        assert(ArrayLen(data.data) == 3);
        assert(data.meta.pagination.total == 3);
    }
    
    function test_create_product_with_valid_data() {
        params = {
            data: {
                type: "products",
                attributes: {
                    name: "Test Product",
                    price: 29.99,
                    description: "Test description"
                }
            }
        };
        
        result = $request(
            route="apiV1Products",
            method="POST",
            params=params,
            headers={
                "Content-Type": "application/json",
                "Accept": "application/json"
            }
        );
        
        assert(result.status == 201);
        assert(StructKeyExists(result.headers, "Location"));
        data = DeserializeJSON(result.body);
        assert(data.data.attributes.name == "Test Product");
    }
    
    function test_authentication_required() {
        result = $request(
            route="apiV1Products",
            method="POST",
            params={},
            headers={"Accept": "application/json"}
        );
        
        assert(result.status == 401);
    }
    
}
wheels generate resource
wheels generate controller
wheels generate model
wheels generate route

wheels generate snippets

Generate code snippets and boilerplate code for common patterns.

Synopsis

wheels generate snippets [pattern] [options]
wheels g snippets [pattern] [options]

Description

The wheels generate snippets command creates code snippets for common Wheels patterns and best practices. It provides ready-to-use code blocks that can be customized for your specific needs, helping you implement standard patterns quickly and consistently.

Arguments

Argument
Description
Default

pattern

Snippet pattern to generate

Shows available patterns

Options

Option
Description
Default

--list

List all available snippets

false

--category

Filter by category

All categories

--output

Output format (console, file, clipboard)

console

--customize

Interactive customization

false

--force

Overwrite existing files

false

--help

Show help information

Available Snippets

List All Snippets

wheels generate snippets --list

Output:

Available Snippets:
━━━━━━━━━━━━━━━━━━━

Authentication:
  - login-form          Login form with remember me
  - auth-filter         Authentication filter
  - password-reset      Password reset flow
  - user-registration   User registration with validation

Model Patterns:
  - soft-delete         Soft delete implementation
  - audit-trail         Audit trail with timestamps
  - sluggable          URL-friendly slugs
  - versionable        Version tracking
  - searchable         Full-text search

Controller Patterns:
  - crud-actions       Complete CRUD actions
  - api-controller     JSON API controller
  - nested-resource    Nested resource controller
  - admin-controller   Admin area controller

View Patterns:
  - form-with-errors   Form with error handling
  - pagination-links   Pagination navigation
  - search-form        Search form with filters
  - ajax-form          AJAX form submission

Database:
  - migration-indexes  Common index patterns
  - seed-data         Database seeding
  - constraints       Foreign key constraints

Authentication Snippets

Login Form

wheels generate snippets login-form

Generates:

<!--- views/sessions/new.cfm --->
<h1>Login</h1>

#errorMessagesFor("user")#

#startFormTag(action="create", class="login-form")#
    
    <div class="form-group">
        #textField(
            objectName="user",
            property="email",
            label="Email",
            class="form-control",
            placeholder="[email protected]",
            required=true,
            autofocus=true
        )#
    </div>
    
    <div class="form-group">
        #passwordField(
            objectName="user",
            property="password",
            label="Password",
            class="form-control",
            required=true
        )#
    </div>
    
    <div class="form-group">
        #checkBox(
            objectName="user",
            property="rememberMe",
            label="Remember me",
            value="1"
        )#
    </div>
    
    <div class="form-group">
        #submitTag(value="Login", class="btn btn-primary")#
        #linkTo(text="Forgot password?", route="forgotPassword", class="btn btn-link")#
    </div>
    
#endFormTag()#

<!--- controllers/Sessions.cfc --->
component extends="Controller" {
    
    function new() {
        user = model("User").new();
    }
    
    function create() {
        user = model("User").findOne(where="email='#params.user.email#'");
        
        if (IsObject(user) && user.authenticate(params.user.password)) {
            session.userId = user.id;
            
            if (params.user.rememberMe == 1) {
                cookie.rememberToken = user.generateRememberToken();
                cookie.userId = encrypt(user.id, application.encryptionKey);
            }
            
            flashInsert(success="Welcome back, #user.firstName#!");
            redirectTo(route="dashboard");
        } else {
            user = model("User").new(email=params.user.email);
            flashInsert(error="Invalid email or password.");
            renderView(action="new");
        }
    }
    
    function delete() {
        StructDelete(session, "userId");
        StructDelete(cookie, "rememberToken");
        StructDelete(cookie, "userId");
        
        flashInsert(success="You have been logged out.");
        redirectTo(route="home");
    }
    
}

Authentication Filter

wheels generate snippets auth-filter

Generates:

// In Controller.cfc or specific controller

function init() {
    filters(through="authenticate", except="new,create");
    filters(through="rememberUser", only="new");
}

private function authenticate() {
    if (!isLoggedIn()) {
        storeLocation();
        flashInsert(notice="Please log in to continue.");
        redirectTo(route="login");
    }
}

private function isLoggedIn() {
    return StructKeyExists(session, "userId") && session.userId > 0;
}

private function currentUser() {
    if (!StructKeyExists(request, "currentUser")) {
        if (isLoggedIn()) {
            request.currentUser = model("User").findByKey(session.userId);
        } else {
            request.currentUser = false;
        }
    }
    return request.currentUser;
}

private function rememberUser() {
    if (!isLoggedIn() && StructKeyExists(cookie, "rememberToken")) {
        user = model("User").findOne(
            where="rememberToken='#cookie.rememberToken#' AND rememberTokenExpiresAt > NOW()"
        );
        
        if (IsObject(user)) {
            session.userId = user.id;
            user.updateRememberToken();
        }
    }
}

private function storeLocation() {
    if (request.method == "GET") {
        session.returnTo = request.cgi.path_info;
        if (Len(request.cgi.query_string)) {
            session.returnTo &= "?" & request.cgi.query_string;
        }
    }
}

private function redirectBackOrTo(required string route) {
    if (StructKeyExists(session, "returnTo")) {
        local.returnTo = session.returnTo;
        StructDelete(session, "returnTo");
        redirectTo(location=local.returnTo);
    } else {
        redirectTo(route=arguments.route);
    }
}

Model Patterns

Soft Delete

wheels generate snippets soft-delete

Generates:

// In Model init()
property(name="deletedAt", sql="deleted_at");

// Soft delete callbacks
beforeDelete("softDelete");
afterFind("excludeDeleted");

// Default scope
function excludeDeleted() {
    if (!StructKeyExists(arguments, "includeSoftDeleted") || !arguments.includeSoftDeleted) {
        if (StructKeyExists(this, "deletedAt") && !IsNull(this.deletedAt)) {
            return false; // Exclude from results
        }
    }
}

// Soft delete implementation
private function softDelete() {
    this.deletedAt = Now();
    this.save(validate=false, callbacks=false);
    return false; // Prevent actual deletion
}

// Scopes
function active() {
    return this.findAll(where="deleted_at IS NULL", argumentCollection=arguments);
}

function deleted() {
    return this.findAll(where="deleted_at IS NOT NULL", argumentCollection=arguments);
}

function withDeleted() {
    return this.findAll(includeSoftDeleted=true, argumentCollection=arguments);
}

// Restore method
function restore() {
    this.deletedAt = "";
    return this.save(validate=false);
}

// Permanent delete
function forceDelete() {
    return this.delete(callbacks=false);
}

Audit Trail

wheels generate snippets audit-trail --customize

Interactive customization:

? Include user tracking? (Y/n) › Y
? Track IP address? (y/N) › Y
? Track changes in JSON? (Y/n) › Y

Generates:

// models/AuditLog.cfc
component extends="Model" {
    
    function init() {
        belongsTo("user");
        
        property(name="modelName", sql="model_name");
        property(name="recordId", sql="record_id");
        property(name="action", sql="action");
        property(name="changes", sql="changes");
        property(name="userId", sql="user_id");
        property(name="ipAddress", sql="ip_address");
        property(name="userAgent", sql="user_agent");
        
        validatesPresenceOf("modelName,recordId,action");
    }
    
}

// In audited model
function init() {
    afterCreate("logCreate");
    afterUpdate("logUpdate");
    afterDelete("logDelete");
}

private function logCreate() {
    createAuditLog("create", this.properties());
}

private function logUpdate() {
    if (hasChanged()) {
        createAuditLog("update", this.changedProperties());
    }
}

private function logDelete() {
    createAuditLog("delete", {id: this.id});
}

private function createAuditLog(required string action, required struct data) {
    model("AuditLog").create({
        modelName: ListLast(GetMetaData(this).name, "."),
        recordId: this.id,
        action: arguments.action,
        changes: SerializeJSON(arguments.data),
        userId: request.currentUser.id ?: "",
        ipAddress: request.remoteAddress,
        userAgent: request.userAgent
    });
}

private function changedProperties() {
    local.changes = {};
    local.properties = this.properties();
    
    for (local.key in local.properties) {
        if (hasChanged(local.key)) {
            local.changes[local.key] = {
                from: this.changedFrom(local.key),
                to: local.properties[local.key]
            };
        }
    }
    
    return local.changes;
}

// Audit log migration
component extends="wheels.migrator.Migration" {
    
    function up() {
        createTable(name="audit_logs") {
            t.increments("id");
            t.string("model_name", null=false);
            t.integer("record_id", null=false);
            t.string("action", null=false);
            t.text("changes");
            t.integer("user_id");
            t.string("ip_address");
            t.string("user_agent");
            t.timestamps();
            
            t.index(["model_name", "record_id"]);
            t.index("user_id");
            t.index("created_at");
        };
    }
    
    function down() {
        dropTable("audit_logs");
    }
    
}

Controller Patterns

CRUD Actions

wheels generate snippets crud-actions

Generates complete CRUD controller with error handling, pagination, and filters.

API Controller

wheels generate snippets api-controller

Generates:

component extends="Controller" {
    
    function init() {
        provides("json");
        filters(through="setApiHeaders");
        filters(through="authenticateApi");
        filters(through="logApiRequest", except="index,show");
    }
    
    private function setApiHeaders() {
        header name="X-API-Version" value="1.0";
        header name="X-RateLimit-Limit" value="1000";
        header name="X-RateLimit-Remaining" value=getRateLimitRemaining();
    }
    
    private function authenticateApi() {
        local.token = getAuthToken();
        
        if (!Len(local.token)) {
            renderUnauthorized("Missing authentication token");
        }
        
        request.apiUser = model("ApiKey").authenticate(local.token);
        
        if (!IsObject(request.apiUser)) {
            renderUnauthorized("Invalid authentication token");
        }
    }
    
    private function getAuthToken() {
        // Check Authorization header
        if (StructKeyExists(getHttpRequestData().headers, "Authorization")) {
            local.auth = getHttpRequestData().headers.Authorization;
            if (Left(local.auth, 7) == "Bearer ") {
                return Mid(local.auth, 8, Len(local.auth));
            }
        }
        
        // Check X-API-Key header
        if (StructKeyExists(getHttpRequestData().headers, "X-API-Key")) {
            return getHttpRequestData().headers["X-API-Key"];
        }
        
        // Check query parameter
        if (StructKeyExists(params, "api_key")) {
            return params.api_key;
        }
        
        return "";
    }
    
    private function renderUnauthorized(required string message) {
        renderWith(
            data={
                error: {
                    code: 401,
                    message: arguments.message
                }
            },
            status=401
        );
    }
    
    private function renderError(required string message, numeric status = 400) {
        renderWith(
            data={
                error: {
                    code: arguments.status,
                    message: arguments.message
                }
            },
            status=arguments.status
        );
    }
    
    private function renderSuccess(required any data, numeric status = 200) {
        renderWith(
            data={
                success: true,
                data: arguments.data
            },
            status=arguments.status
        );
    }
    
    private function renderPaginated(required any query) {
        renderWith(
            data={
                success: true,
                data: arguments.query,
                pagination: {
                    page: arguments.query.currentPage,
                    perPage: arguments.query.perPage,
                    total: arguments.query.totalRecords,
                    pages: arguments.query.totalPages
                }
            }
        );
    }
    
}

View Patterns

Form with Errors

wheels generate snippets form-with-errors

AJAX Form

wheels generate snippets ajax-form

Generates:

<!--- View file --->
<div id="contact-form-container">
    #startFormTag(
        action="send",
        id="contact-form",
        class="ajax-form",
        data={
            remote: true,
            method: "post",
            success: "handleFormSuccess",
            error: "handleFormError"
        }
    )#
        
        <div class="form-messages" style="display: none;"></div>
        
        <div class="form-group">
            #textField(
                name="name",
                label="Name",
                class="form-control",
                required=true
            )#
        </div>
        
        <div class="form-group">
            #emailField(
                name="email",
                label="Email",
                class="form-control",
                required=true
            )#
        </div>
        
        <div class="form-group">
            #textArea(
                name="message",
                label="Message",
                class="form-control",
                rows=5,
                required=true
            )#
        </div>
        
        <div class="form-group">
            #submitTag(
                value="Send Message",
                class="btn btn-primary",
                data={loading: "Sending..."}
            )#
        </div>
        
    #endFormTag()#
</div>

<script>
// AJAX form handler
document.addEventListener('DOMContentLoaded', function() {
    const form = document.getElementById('contact-form');
    
    form.addEventListener('submit', function(e) {
        e.preventDefault();
        
        const submitBtn = form.querySelector('[type="submit"]');
        const originalText = submitBtn.value;
        const loadingText = submitBtn.dataset.loading;
        
        // Disable form
        submitBtn.disabled = true;
        submitBtn.value = loadingText;
        
        // Send AJAX request
        fetch(form.action, {
            method: form.method,
            body: new FormData(form),
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            }
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                handleFormSuccess(data);
            } else {
                handleFormError(data);
            }
        })
        .catch(error => {
            handleFormError({message: 'Network error. Please try again.'});
        })
        .finally(() => {
            submitBtn.disabled = false;
            submitBtn.value = originalText;
        });
    });
});

function handleFormSuccess(data) {
    const form = document.getElementById('contact-form');
    const messages = form.querySelector('.form-messages');
    
    // Show success message
    messages.className = 'form-messages alert alert-success';
    messages.textContent = data.message || 'Message sent successfully!';
    messages.style.display = 'block';
    
    // Reset form
    form.reset();
    
    // Hide message after 5 seconds
    setTimeout(() => {
        messages.style.display = 'none';
    }, 5000);
}

function handleFormError(data) {
    const form = document.getElementById('contact-form');
    const messages = form.querySelector('.form-messages');
    
    // Show error message
    messages.className = 'form-messages alert alert-danger';
    messages.textContent = data.message || 'An error occurred. Please try again.';
    messages.style.display = 'block';
    
    // Show field errors
    if (data.errors) {
        Object.keys(data.errors).forEach(field => {
            const input = form.querySelector(`[name="${field}"]`);
            if (input) {
                input.classList.add('is-invalid');
                const error = document.createElement('div');
                error.className = 'invalid-feedback';
                error.textContent = data.errors[field].join(', ');
                input.parentNode.appendChild(error);
            }
        });
    }
}
</script>

<!--- Controller action --->
function send() {
    contact = model("Contact").new(params);
    
    if (contact.save()) {
        if (isAjax()) {
            renderWith(data={
                success: true,
                message: "Thank you! We'll be in touch soon."
            });
        } else {
            flashInsert(success="Thank you! We'll be in touch soon.");
            redirectTo(route="home");
        }
    } else {
        if (isAjax()) {
            renderWith(data={
                success: false,
                message: "Please correct the errors below.",
                errors: contact.allErrors()
            }, status=422);
        } else {
            renderView(action="new");
        }
    }
}

Database Snippets

Migration Indexes

wheels generate snippets migration-indexes

Generates common index patterns:

// Performance indexes
t.index("email"); // Single column
t.index(["last_name", "first_name"]); // Composite
t.index("created_at"); // Timestamp queries

// Unique constraints
t.index("email", unique=true);
t.index(["user_id", "role_id"], unique=true);

// Foreign key indexes
t.index("user_id");
t.index("category_id");

// Full-text search
t.index("title", type="fulltext");
t.index(["title", "content"], type="fulltext");

// Partial indexes (PostgreSQL)
t.index("email", where="deleted_at IS NULL");

// Expression indexes
t.index("LOWER(email)", name="idx_email_lower");

Seed Data

wheels generate snippets seed-data

Custom Snippets

Create Custom Snippet

wheels generate snippets --create=my-pattern

Creates template in ~/.wheels/snippets/my-pattern/:

my-pattern/
├── snippet.json
├── files/
│   ├── controller.cfc
│   ├── model.cfc
│   └── view.cfm
└── README.md

snippet.json:

{
    "name": "my-pattern",
    "description": "Custom pattern description",
    "category": "custom",
    "author": "Your Name",
    "version": "1.0.0",
    "variables": [
        {
            "name": "modelName",
            "prompt": "Model name?",
            "default": "MyModel"
        }
    ],
    "files": [
        {
            "source": "files/controller.cfc",
            "destination": "controllers/${controllerName}.cfc"
        }
    ]
}

Output Options

Copy to Clipboard

wheels generate snippets login-form --output=clipboard

Save to File

wheels generate snippets api-controller --output=file --path=./controllers/Api.cfc

Interactive Mode

wheels generate snippets --customize

Best Practices

  1. Review generated code: Customize for your needs

  2. Understand the patterns: Don't blindly copy

  3. Keep snippets updated: Maintain with framework updates

  4. Share useful patterns: Contribute back to community

  5. Document customizations: Note changes made

  6. Test generated code: Ensure it works in your context

  7. Use consistent patterns: Across your application

See Also

  • wheels generate controller - Generate controllers

  • wheels generate model - Generate models

  • wheels scaffold - Generate complete resources

wheels generate frontend

Generate frontend code including JavaScript, CSS, and interactive components.

⚠️ Note: This command is currently marked as disabled in the codebase. The documentation below represents the intended functionality when the command is restored.

Synopsis

wheels generate frontend [type] [name] [options]
wheels g frontend [type] [name] [options]

Description

The wheels generate frontend command creates frontend assets including JavaScript modules, CSS stylesheets, and interactive components. It supports various frontend frameworks and patterns while integrating seamlessly with Wheels views.

Current Status

This command is temporarily disabled. Use manual approaches:

# Create frontend files manually in:
# /public/javascripts/
# /public/stylesheets/
# /views/components/

Arguments (When Enabled)

Argument
Description
Default

type

Type of frontend asset (component, module, style)

Required

name

Name of the asset

Required

Options (When Enabled)

Option
Description
Default

--framework

Frontend framework (vanilla, alpine, vue, htmx)

vanilla

--style

CSS framework (none, bootstrap, tailwind)

none

--bundler

Use bundler (webpack, vite, none)

none

--typescript

Generate TypeScript files

false

--test

Generate test files

true

--force

Overwrite existing files

false

--help

Show help information

Intended Functionality

Generate Component

wheels generate frontend component productCard --framework=alpine

Would generate:

/public/javascripts/components/productCard.js:

// Product Card Component
document.addEventListener('alpine:init', () => {
    Alpine.data('productCard', (initialData = {}) => ({
        // State
        product: initialData.product || {},
        isLoading: false,
        isFavorite: false,
        
        // Computed
        get formattedPrice() {
            return new Intl.NumberFormat('en-US', {
                style: 'currency',
                currency: 'USD'
            }).format(this.product.price || 0);
        },
        
        // Methods
        async toggleFavorite() {
            this.isLoading = true;
            try {
                const response = await fetch(`/api/products/${this.product.id}/favorite`, {
                    method: this.isFavorite ? 'DELETE' : 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-CSRF-Token': this.getCsrfToken()
                    }
                });
                
                if (response.ok) {
                    this.isFavorite = !this.isFavorite;
                    this.$dispatch('favorite-changed', {
                        productId: this.product.id,
                        isFavorite: this.isFavorite
                    });
                }
            } catch (error) {
                console.error('Failed to toggle favorite:', error);
                this.$dispatch('notification', {
                    type: 'error',
                    message: 'Failed to update favorite status'
                });
            } finally {
                this.isLoading = false;
            }
        },
        
        async addToCart() {
            this.isLoading = true;
            try {
                const response = await fetch('/api/cart/items', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-CSRF-Token': this.getCsrfToken()
                    },
                    body: JSON.stringify({
                        productId: this.product.id,
                        quantity: 1
                    })
                });
                
                if (response.ok) {
                    this.$dispatch('cart-updated');
                    this.$dispatch('notification', {
                        type: 'success',
                        message: 'Added to cart'
                    });
                }
            } catch (error) {
                console.error('Failed to add to cart:', error);
            } finally {
                this.isLoading = false;
            }
        },
        
        getCsrfToken() {
            return document.querySelector('meta[name="csrf-token"]')?.content || '';
        }
    }));
});

/public/stylesheets/components/productCard.css:

/* Product Card Styles */
.product-card {
    @apply bg-white rounded-lg shadow-md overflow-hidden transition-transform hover:scale-105;
}

.product-card__image {
    @apply w-full h-48 object-cover;
}

.product-card__content {
    @apply p-4;
}

.product-card__title {
    @apply text-lg font-semibold text-gray-800 mb-2;
}

.product-card__price {
    @apply text-xl font-bold text-blue-600 mb-3;
}

.product-card__actions {
    @apply flex justify-between items-center;
}

.product-card__button {
    @apply px-4 py-2 rounded font-medium transition-colors;
}

.product-card__button--primary {
    @apply bg-blue-500 text-white hover:bg-blue-600;
}

.product-card__button--secondary {
    @apply bg-gray-200 text-gray-700 hover:bg-gray-300;
}

.product-card__button:disabled {
    @apply opacity-50 cursor-not-allowed;
}

/views/components/_productCard.cfm:

<div class="product-card" 
     x-data="productCard({
         product: #SerializeJSON({
             id: arguments.product.id,
             name: arguments.product.name,
             price: arguments.product.price,
             image: arguments.product.imageUrl
         })#
     })">
    
    <img :src="product.image" 
         :alt="product.name" 
         class="product-card__image">
    
    <div class="product-card__content">
        <h3 class="product-card__title" x-text="product.name"></h3>
        <p class="product-card__price" x-text="formattedPrice"></p>
        
        <div class="product-card__actions">
            <button @click="addToCart" 
                    :disabled="isLoading"
                    class="product-card__button product-card__button--primary">
                <span x-show="!isLoading">Add to Cart</span>
                <span x-show="isLoading">Adding...</span>
            </button>
            
            <button @click="toggleFavorite" 
                    :disabled="isLoading"
                    class="product-card__button product-card__button--secondary">
                <span x-show="!isFavorite">♡</span>
                <span x-show="isFavorite">♥</span>
            </button>
        </div>
    </div>
</div>

Generate JavaScript Module

wheels generate frontend module api --typescript

Would generate /public/javascripts/modules/api.ts:

// API Module
interface RequestOptions extends RequestInit {
    params?: Record<string, any>;
}

interface ApiResponse<T = any> {
    data: T;
    meta?: Record<string, any>;
    errors?: Array<{
        field: string;
        message: string;
    }>;
}

class ApiClient {
    private baseUrl: string;
    private defaultHeaders: Record<string, string>;
    
    constructor(baseUrl: string = '/api') {
        this.baseUrl = baseUrl;
        this.defaultHeaders = {
            'Content-Type': 'application/json',
            'X-Requested-With': 'XMLHttpRequest'
        };
        
        // Add CSRF token if available
        const csrfToken = document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')?.content;
        if (csrfToken) {
            this.defaultHeaders['X-CSRF-Token'] = csrfToken;
        }
    }
    
    async request<T = any>(
        endpoint: string,
        options: RequestOptions = {}
    ): Promise<ApiResponse<T>> {
        const { params, ...fetchOptions } = options;
        
        // Build URL with params
        const url = new URL(this.baseUrl + endpoint, window.location.origin);
        if (params) {
            Object.entries(params).forEach(([key, value]) => {
                if (value !== undefined && value !== null) {
                    url.searchParams.append(key, String(value));
                }
            });
        }
        
        // Merge headers
        const headers = {
            ...this.defaultHeaders,
            ...fetchOptions.headers
        };
        
        try {
            const response = await fetch(url.toString(), {
                ...fetchOptions,
                headers
            });
            
            if (!response.ok) {
                throw new ApiError(response.status, await response.text());
            }
            
            const data = await response.json();
            return data;
            
        } catch (error) {
            if (error instanceof ApiError) {
                throw error;
            }
            throw new ApiError(0, 'Network error');
        }
    }
    
    get<T = any>(endpoint: string, params?: Record<string, any>): Promise<ApiResponse<T>> {
        return this.request<T>(endpoint, { method: 'GET', params });
    }
    
    post<T = any>(endpoint: string, data?: any): Promise<ApiResponse<T>> {
        return this.request<T>(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }
    
    put<T = any>(endpoint: string, data?: any): Promise<ApiResponse<T>> {
        return this.request<T>(endpoint, {
            method: 'PUT',
            body: JSON.stringify(data)
        });
    }
    
    delete<T = any>(endpoint: string): Promise<ApiResponse<T>> {
        return this.request<T>(endpoint, { method: 'DELETE' });
    }
}

class ApiError extends Error {
    constructor(public status: number, message: string) {
        super(message);
        this.name = 'ApiError';
    }
}

// Export singleton instance
export const api = new ApiClient();

// Export types
export type { ApiResponse, RequestOptions };
export { ApiClient, ApiError };

Generate HTMX Component

wheels generate frontend component searchForm --framework=htmx

Would generate:

/views/components/_searchForm.cfm:

<form hx-get="/products/search" 
      hx-trigger="submit, keyup changed delay:500ms from:input[name='q']"
      hx-target="##search-results"
      hx-indicator="##search-spinner"
      class="search-form">
    
    <div class="search-form__input-group">
        <input type="search" 
               name="q" 
               value="#params.q ?: ''#"
               placeholder="Search products..."
               class="search-form__input">
        
        <button type="submit" class="search-form__button">
            Search
        </button>
    </div>
    
    <div class="search-form__filters">
        <select name="category" 
                hx-get="/products/search"
                hx-trigger="change"
                hx-target="##search-results"
                hx-include="[name='q']"
                class="search-form__select">
            <option value="">All Categories</option>
            <cfloop query="categories">
                <option value="#categories.id#" 
                        <cfif params.category == categories.id>selected</cfif>>
                    #categories.name#
                </option>
            </cfloop>
        </select>
        
        <select name="sort" 
                hx-get="/products/search"
                hx-trigger="change"
                hx-target="##search-results"
                hx-include="[name='q'],[name='category']"
                class="search-form__select">
            <option value="relevance">Relevance</option>
            <option value="price-asc">Price: Low to High</option>
            <option value="price-desc">Price: High to Low</option>
            <option value="name">Name</option>
        </select>
    </div>
</form>

<div id="search-spinner" class="htmx-indicator">
    <div class="spinner"></div>
</div>

<div id="search-results">
    <!--- Results will be loaded here --->
</div>

Generate Vue Component

wheels generate frontend component todoList --framework=vue

Would generate /public/javascripts/components/TodoList.vue:

<template>
  <div class="todo-list">
    <h2>{{ title }}</h2>
    
    <form @submit.prevent="addTodo" class="todo-form">
      <input
        v-model="newTodo"
        type="text"
        placeholder="Add a new todo..."
        class="todo-form__input"
      >
      <button type="submit" class="todo-form__button">
        Add
      </button>
    </form>
    
    <ul class="todo-items">
      <li
        v-for="todo in filteredTodos"
        :key="todo.id"
        class="todo-item"
        :class="{ 'todo-item--completed': todo.completed }"
      >
        <input
          type="checkbox"
          v-model="todo.completed"
          @change="updateTodo(todo)"
        >
        <span class="todo-item__text">{{ todo.text }}</span>
        <button
          @click="deleteTodo(todo.id)"
          class="todo-item__delete"
        >
          ×
        </button>
      </li>
    </ul>
    
    <div class="todo-filters">
      <button
        v-for="filter in filters"
        :key="filter"
        @click="currentFilter = filter"
        :class="{ active: currentFilter === filter }"
        class="todo-filter"
      >
        {{ filter }}
      </button>
    </div>
  </div>
</template>

<script>
import { ref, computed, onMounted } from 'vue';
import { api } from '../modules/api';

export default {
  name: 'TodoList',
  props: {
    title: {
      type: String,
      default: 'Todo List'
    }
  },
  setup() {
    const todos = ref([]);
    const newTodo = ref('');
    const currentFilter = ref('all');
    const filters = ['all', 'active', 'completed'];
    
    const filteredTodos = computed(() => {
      switch (currentFilter.value) {
        case 'active':
          return todos.value.filter(todo => !todo.completed);
        case 'completed':
          return todos.value.filter(todo => todo.completed);
        default:
          return todos.value;
      }
    });
    
    const loadTodos = async () => {
      try {
        const response = await api.get('/todos');
        todos.value = response.data;
      } catch (error) {
        console.error('Failed to load todos:', error);
      }
    };
    
    const addTodo = async () => {
      if (!newTodo.value.trim()) return;
      
      try {
        const response = await api.post('/todos', {
          text: newTodo.value,
          completed: false
        });
        todos.value.push(response.data);
        newTodo.value = '';
      } catch (error) {
        console.error('Failed to add todo:', error);
      }
    };
    
    const updateTodo = async (todo) => {
      try {
        await api.put(`/todos/${todo.id}`, {
          completed: todo.completed
        });
      } catch (error) {
        console.error('Failed to update todo:', error);
        todo.completed = !todo.completed;
      }
    };
    
    const deleteTodo = async (id) => {
      try {
        await api.delete(`/todos/${id}`);
        todos.value = todos.value.filter(todo => todo.id !== id);
      } catch (error) {
        console.error('Failed to delete todo:', error);
      }
    };
    
    onMounted(loadTodos);
    
    return {
      todos,
      newTodo,
      currentFilter,
      filters,
      filteredTodos,
      addTodo,
      updateTodo,
      deleteTodo
    };
  }
};
</script>

<style scoped>
.todo-list {
  max-width: 500px;
  margin: 0 auto;
}

.todo-form {
  display: flex;
  margin-bottom: 1rem;
}

.todo-form__input {
  flex: 1;
  padding: 0.5rem;
  border: 1px solid #ddd;
  border-radius: 4px 0 0 4px;
}

.todo-form__button {
  padding: 0.5rem 1rem;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 0 4px 4px 0;
  cursor: pointer;
}

.todo-items {
  list-style: none;
  padding: 0;
}

.todo-item {
  display: flex;
  align-items: center;
  padding: 0.5rem;
  border-bottom: 1px solid #eee;
}

.todo-item--completed .todo-item__text {
  text-decoration: line-through;
  opacity: 0.6;
}

.todo-item__delete {
  margin-left: auto;
  background: none;
  border: none;
  color: #dc3545;
  font-size: 1.5rem;
  cursor: pointer;
}

.todo-filters {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
}

.todo-filter {
  padding: 0.25rem 0.75rem;
  border: 1px solid #ddd;
  background: white;
  cursor: pointer;
  border-radius: 4px;
}

.todo-filter.active {
  background: #007bff;
  color: white;
  border-color: #007bff;
}
</style>

Workaround Implementation

Until the command is fixed, create frontend assets manually:

1. Directory Structure

/public/
├── javascripts/
│   ├── app.js
│   ├── components/
│   ├── modules/
│   └── vendor/
├── stylesheets/
│   ├── app.css
│   ├── components/
│   └── vendor/
└── images/

2. Basic App Structure

/public/javascripts/app.js:

// Main application JavaScript
(function() {
    'use strict';
    
    // Initialize on DOM ready
    document.addEventListener('DOMContentLoaded', function() {
        initializeComponents();
        setupEventListeners();
        loadDynamicContent();
    });
    
    function initializeComponents() {
        // Initialize all components
        document.querySelectorAll('[data-component]').forEach(element => {
            const componentName = element.dataset.component;
            if (window.components && window.components[componentName]) {
                new window.components[componentName](element);
            }
        });
    }
    
    function setupEventListeners() {
        // Global event delegation
        document.addEventListener('click', handleClick);
        document.addEventListener('submit', handleSubmit);
    }
    
    function handleClick(event) {
        // Handle data-action clicks
        const action = event.target.closest('[data-action]');
        if (action) {
            event.preventDefault();
            const actionName = action.dataset.action;
            // Handle action
        }
    }
    
    function handleSubmit(event) {
        // Handle AJAX forms
        const form = event.target;
        if (form.dataset.remote === 'true') {
            event.preventDefault();
            submitFormAjax(form);
        }
    }
    
    function submitFormAjax(form) {
        const formData = new FormData(form);
        
        fetch(form.action, {
            method: form.method,
            body: formData,
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            }
        })
        .then(response => response.json())
        .then(data => {
            // Handle response
        })
        .catch(error => {
            console.error('Form submission error:', error);
        });
    }
    
    function loadDynamicContent() {
        // Load content marked for lazy loading
        const lazyElements = document.querySelectorAll('[data-lazy-load]');
        
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    loadContent(entry.target);
                    observer.unobserve(entry.target);
                }
            });
        });
        
        lazyElements.forEach(el => observer.observe(el));
    }
    
    function loadContent(element) {
        const url = element.dataset.lazyLoad;
        fetch(url)
            .then(response => response.text())
            .then(html => {
                element.innerHTML = html;
                initializeComponents();
            });
    }
    
})();

3. CSS Structure

/public/stylesheets/app.css:

/* Base styles */
:root {
    --primary-color: #007bff;
    --secondary-color: #6c757d;
    --success-color: #28a745;
    --danger-color: #dc3545;
    --warning-color: #ffc107;
    --info-color: #17a2b8;
    --light-color: #f8f9fa;
    --dark-color: #343a40;
}

/* Layout */
.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 1rem;
}

/* Components */
@import 'components/buttons.css';
@import 'components/forms.css';
@import 'components/cards.css';
@import 'components/modals.css';

/* Utilities */
.hidden {
    display: none !important;
}

.loading {
    opacity: 0.6;
    pointer-events: none;
}

/* Animations */
@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.fade-in {
    animation: fadeIn 0.3s ease-in;
}

Best Practices

  1. Organize by feature: Group related files together

  2. Use modules: Keep code modular and reusable

  3. Follow conventions: Consistent naming and structure

  4. Progressive enhancement: Work without JavaScript

  5. Optimize performance: Minimize and bundle assets

  6. Test components: Unit and integration tests

  7. Document APIs: Clear component documentation

  8. Handle errors: Graceful error handling

  9. Accessibility: ARIA labels and keyboard support

  10. Security: Validate inputs, use CSRF tokens

See Also

  • wheels generate view - Generate view files

  • wheels generate controller - Generate controllers

  • wheels scaffold - Generate complete CRUD

wheels generate test

Generate test files for models, controllers, views, and other components.

Synopsis

wheels generate test [type] [name] [options]
wheels g test [type] [name] [options]

Description

The wheels generate test command creates test files for various components of your Wheels application. It generates appropriate test scaffolding based on the component type and includes common test cases to get you started.

Arguments

Argument
Description
Default

type

Type of test (model, controller, view, helper, route)

Required

name

Name of the component to test

Required

Options

Option
Description
Default

--methods

Specific methods to test

All methods

--integration

Generate integration tests

false

--coverage

Include coverage setup

false

--fixtures

Generate test fixtures

true

--force

Overwrite existing files

false

--help

Show help information

Examples

Model Test

wheels generate test model product

Generates /tests/models/ProductTest.cfc:

component extends="wheels.Test" {
    
    function setup() {
        super.setup();
        // Clear test data
        model("Product").deleteAll();
        
        // Setup test fixtures
        variables.validProduct = {
            name: "Test Product",
            price: 19.99,
            description: "Test product description"
        };
    }
    
    function teardown() {
        super.teardown();
        // Clean up after tests
        model("Product").deleteAll();
    }
    
    // Validation Tests
    
    function test_valid_product_saves_successfully() {
        // Arrange
        product = model("Product").new(variables.validProduct);
        
        // Act
        result = product.save();
        
        // Assert
        assert(result, "Product should save successfully");
        assert(product.id > 0, "Product should have an ID after saving");
    }
    
    function test_product_requires_name() {
        // Arrange
        product = model("Product").new(variables.validProduct);
        product.name = "";
        
        // Act
        result = product.save();
        
        // Assert
        assert(!result, "Product should not save without name");
        assert(ArrayLen(product.errorsOn("name")) > 0, "Should have error on name");
    }
    
    function test_product_requires_positive_price() {
        // Arrange
        product = model("Product").new(variables.validProduct);
        product.price = -10;
        
        // Act
        result = product.save();
        
        // Assert
        assert(!result, "Product should not save with negative price");
        assert(ArrayLen(product.errorsOn("price")) > 0, "Should have error on price");
    }
    
    function test_product_name_must_be_unique() {
        // Arrange
        product1 = model("Product").create(variables.validProduct);
        product2 = model("Product").new(variables.validProduct);
        
        // Act
        result = product2.save();
        
        // Assert
        assert(!result, "Should not save duplicate product name");
        assert(ArrayLen(product2.errorsOn("name")) > 0, "Should have uniqueness error");
    }
    
    // Association Tests
    
    function test_product_has_many_reviews() {
        // Arrange
        product = model("Product").create(variables.validProduct);
        review = product.createReview(rating=5, comment="Great product!");
        
        // Act
        reviews = product.reviews();
        
        // Assert
        assert(reviews.recordCount == 1, "Product should have one review");
        assert(reviews.rating == 5, "Review rating should be 5");
    }
    
    // Callback Tests
    
    function test_before_save_sanitizes_input() {
        // Arrange
        product = model("Product").new(variables.validProduct);
        product.name = "  Test Product  ";
        
        // Act
        product.save();
        
        // Assert
        assert(product.name == "Test Product", "Name should be trimmed");
    }
    
    // Scope Tests
    
    function test_active_scope_returns_only_active_products() {
        // Arrange
        activeProduct = model("Product").create(
            variables.validProduct & {isActive: true}
        );
        inactiveProduct = model("Product").create(
            name="Inactive Product",
            price=29.99,
            isActive=false
        );
        
        // Act
        activeProducts = model("Product").active();
        
        // Assert
        assert(activeProducts.recordCount == 1, "Should have one active product");
        assert(activeProducts.id == activeProduct.id, "Should return active product");
    }
    
    // Method Tests
    
    function test_calculate_discount_price() {
        // Arrange
        product = model("Product").create(variables.validProduct);
        
        // Act
        discountPrice = product.calculateDiscountPrice(0.20); // 20% discount
        
        // Assert
        expected = product.price * 0.80;
        assert(discountPrice == expected, "Discount price should be 80% of original");
    }
    
    // Integration Tests
    
    function test_product_lifecycle() {
        transaction {
            // Create
            product = model("Product").new(variables.validProduct);
            assert(product.save(), "Should create product");
            productId = product.id;
            
            // Read
            foundProduct = model("Product").findByKey(productId);
            assert(IsObject(foundProduct), "Should find product");
            assert(foundProduct.name == variables.validProduct.name, "Should have correct name");
            
            // Update
            foundProduct.price = 24.99;
            assert(foundProduct.save(), "Should update product");
            
            // Verify update
            updatedProduct = model("Product").findByKey(productId);
            assert(updatedProduct.price == 24.99, "Price should be updated");
            
            // Delete
            assert(updatedProduct.delete(), "Should delete product");
            
            // Verify deletion
            deletedProduct = model("Product").findByKey(productId);
            assert(!IsObject(deletedProduct), "Product should not exist");
            
            // Rollback transaction
            transaction action="rollback";
        }
    }
    
}

Controller Test

wheels generate test controller products

Generates /tests/controllers/ProductsTest.cfc:

View Test

wheels generate test view products --name=index

Generates a test for the products/index view.

CRUD Tests

wheels generate test controller products --crud

Generates complete CRUD test methods for the controller.

component extends="wheels.Test" {
    
    function setup() {
        super.setup();
        // Setup test data
        model("Product").deleteAll();
        
        variables.testProducts = [];
        for (i = 1; i <= 3; i++) {
            ArrayAppend(variables.testProducts, 
                model("Product").create(
                    name="Product #i#",
                    price=19.99 * i,
                    description="Description #i#"
                )
            );
        }
    }
    
    function teardown() {
        super.teardown();
        model("Product").deleteAll();
    }
    
    // Action Tests
    
    function test_index_returns_all_products() {
        // Act
        result = processRequest(route="products", method="GET");
        
        // Assert
        assert(result.status == 200, "Should return 200 status");
        assert(Find("<h1>Products</h1>", result.body), "Should have products heading");
        
        for (product in variables.testProducts) {
            assert(Find(product.name, result.body), "Should display product: #product.name#");
        }
    }
    
    function test_show_displays_product_details() {
        // Arrange
        product = variables.testProducts[1];
        
        // Act
        result = processRequest(route="product", key=product.id, method="GET");
        
        // Assert
        assert(result.status == 200, "Should return 200 status");
        assert(Find(product.name, result.body), "Should display product name");
        assert(Find(DollarFormat(product.price), result.body), "Should display formatted price");
    }
    
    function test_show_returns_404_for_invalid_product() {
        // Act
        result = processRequest(route="product", key=99999, method="GET");
        
        // Assert
        assert(result.status == 302, "Should redirect");
        assert(result.flash.error == "Product not found.", "Should have error message");
    }
    
    function test_new_displays_form() {
        // Act
        result = processRequest(route="newProduct", method="GET");
        
        // Assert
        assert(result.status == 200, "Should return 200 status");
        assert(Find("<form", result.body), "Should have form");
        assert(Find('name="product[name]"', result.body), "Should have name field");
        assert(Find('name="product[price]"', result.body), "Should have price field");
    }
    
    function test_create_with_valid_data() {
        // Arrange
        params = {
            product: {
                name: "New Test Product",
                price: 39.99,
                description: "New product description"
            }
        };
        
        // Act
        result = processRequest(route="products", method="POST", params=params);
        
        // Assert
        assert(result.status == 302, "Should redirect after creation");
        assert(result.flash.success == "Product was created successfully.", "Should have success message");
        
        // Verify product was created
        newProduct = model("Product").findOne(where="name='New Test Product'");
        assert(IsObject(newProduct), "Product should be created");
        assert(newProduct.price == 39.99, "Should have correct price");
    }
    
    function test_create_with_invalid_data() {
        // Arrange
        params = {
            product: {
                name: "",
                price: -10,
                description: "Invalid product"
            }
        };
        
        // Act
        result = processRequest(route="products", method="POST", params=params);
        
        // Assert
        assert(result.status == 200, "Should render form again");
        assert(Find("error", result.body), "Should display errors");
        assert(model("Product").count(where="description='Invalid product'") == 0, 
               "Should not create invalid product");
    }
    
    function test_edit_displays_form_with_product_data() {
        // Arrange
        product = variables.testProducts[1];
        
        // Act
        result = processRequest(route="editProduct", key=product.id, method="GET");
        
        // Assert
        assert(result.status == 200, "Should return 200 status");
        assert(Find('value="#product.name#"', result.body), "Should pre-fill name");
        assert(Find(ToString(product.price), result.body), "Should pre-fill price");
    }
    
    function test_update_with_valid_data() {
        // Arrange
        product = variables.testProducts[1];
        params = {
            product: {
                name: "Updated Product Name",
                price: 49.99
            }
        };
        
        // Act
        result = processRequest(route="product", key=product.id, method="PUT", params=params);
        
        // Assert
        assert(result.status == 302, "Should redirect after update");
        assert(result.flash.success == "Product was updated successfully.", "Should have success message");
        
        // Verify update
        updatedProduct = model("Product").findByKey(product.id);
        assert(updatedProduct.name == "Updated Product Name", "Name should be updated");
        assert(updatedProduct.price == 49.99, "Price should be updated");
    }
    
    function test_delete_removes_product() {
        // Arrange
        product = variables.testProducts[1];
        initialCount = model("Product").count();
        
        // Act
        result = processRequest(route="product", key=product.id, method="DELETE");
        
        // Assert
        assert(result.status == 302, "Should redirect after deletion");
        assert(result.flash.success == "Product was deleted successfully.", "Should have success message");
        assert(model("Product").count() == initialCount - 1, "Should have one less product");
        assert(!IsObject(model("Product").findByKey(product.id)), "Product should be deleted");
    }
    
    // Filter Tests
    
    function test_authentication_required_for_protected_actions() {
        // Test that certain actions require authentication
        protectedRoutes = [
            {route: "newProduct", method: "GET"},
            {route: "products", method: "POST"},
            {route: "editProduct", key: variables.testProducts[1].id, method: "GET"},
            {route: "product", key: variables.testProducts[1].id, method: "PUT"},
            {route: "product", key: variables.testProducts[1].id, method: "DELETE"}
        ];
        
        for (route in protectedRoutes) {
            // Act without authentication
            result = processRequest(argumentCollection=route);
            
            // Assert
            assert(result.status == 302, "Should redirect unauthenticated user");
            assert(result.redirectUrl contains "login", "Should redirect to login");
        }
    }
    
    // Helper method for processing requests
    private function processRequest(
        required string route,
        string method = "GET",
        struct params = {},
        numeric key = 0
    ) {
        local.args = {
            route: arguments.route,
            method: arguments.method,
            params: arguments.params
        };
        
        if (arguments.key > 0) {
            local.args.key = arguments.key;
        }
        
        return $processRequest(argumentCollection=local.args);
    }
    
}

View Test

wheels generate test view products/index

Generates /tests/views/products/IndexTest.cfc:

component extends="wheels.Test" {
    
    function setup() {
        super.setup();
        // Create test data
        variables.products = QueryNew(
            "id,name,price,createdAt",
            "integer,varchar,decimal,timestamp"
        );
        
        for (i = 1; i <= 3; i++) {
            QueryAddRow(variables.products, {
                id: i,
                name: "Product #i#",
                price: 19.99 * i,
                createdAt: Now()
            });
        }
    }
    
    function test_index_view_renders_product_list() {
        // Act
        result = $renderView(
            view="/products/index",
            products=variables.products,
            layout=false
        );
        
        // Assert
        assert(Find("<h1>Products</h1>", result), "Should have products heading");
        assert(Find("<table", result), "Should have products table");
        assert(Find("Product 1", result), "Should display first product");
        assert(Find("Product 2", result), "Should display second product");
        assert(Find("Product 3", result), "Should display third product");
    }
    
    function test_index_view_shows_empty_state() {
        // Arrange
        emptyQuery = QueryNew("id,name,price,createdAt");
        
        // Act
        result = $renderView(
            view="/products/index",
            products=emptyQuery,
            layout=false
        );
        
        // Assert
        assert(Find("No products found", result), "Should show empty state message");
        assert(Find("Create one now", result), "Should have create link");
        assert(!Find("<table", result), "Should not show table when empty");
    }
    
    function test_index_view_formats_prices_correctly() {
        // Act
        result = $renderView(
            view="/products/index",
            products=variables.products,
            layout=false
        );
        
        // Assert
        assert(Find("$19.99", result), "Should format first price");
        assert(Find("$39.98", result), "Should format second price");
        assert(Find("$59.97", result), "Should format third price");
    }
    
    function test_index_view_includes_action_links() {
        // Act
        result = $renderView(
            view="/products/index",
            products=variables.products,
            layout=false
        );
        
        // Assert
        assert(Find("New Product", result), "Should have new product link");
        assert(FindNoCase("href=""/products/new""", result), "New link should be correct");
        
        // Check action links for each product
        for (row in variables.products) {
            assert(Find("View</a>", result), "Should have view link");
            assert(Find("Edit</a>", result), "Should have edit link");
            assert(Find("Delete</a>", result), "Should have delete link");
        }
    }
    
    function test_index_view_with_pagination() {
        // Arrange
        paginatedProducts = Duplicate(variables.products);
        paginatedProducts.currentPage = 2;
        paginatedProducts.totalPages = 5;
        paginatedProducts.totalRecords = 50;
        
        // Act
        result = $renderView(
            view="/products/index",
            products=paginatedProducts,
            layout=false
        );
        
        // Assert
        assert(Find("class=""pagination""", result), "Should have pagination");
        assert(Find("Previous", result), "Should have previous link");
        assert(Find("Next", result), "Should have next link");
        assert(Find("Page 2 of 5", result), "Should show current page");
    }
    
    function test_index_view_escapes_html() {
        // Arrange
        productsWithHtml = QueryNew("id,name,price,createdAt");
        QueryAddRow(productsWithHtml, {
            id: 1,
            name: "<script>alert('XSS')</script>",
            price: 19.99,
            createdAt: Now()
        });
        
        // Act
        result = $renderView(
            view="/products/index",
            products=productsWithHtml,
            layout=false
        );
        
        // Assert
        assert(!Find("<script>alert('XSS')</script>", result), 
               "Should not have unescaped script tag");
        assert(Find("&lt;script&gt;", result), "Should have escaped HTML");
    }
    
}

Integration Test

wheels generate test controller products --integration

Generates additional integration tests:

component extends="wheels.Test" {
    
    function test_complete_product_workflow() {
        transaction {
            // 1. View product list (empty)
            result = $visit(route="products");
            assert(result.status == 200);
            assert(Find("No products found", result.body));
            
            // 2. Navigate to new product form
            result = $click("Create one now");
            assert(result.status == 200);
            assert(Find("<form", result.body));
            
            // 3. Submit new product form
            result = $submitForm({
                "product[name]": "Integration Test Product",
                "product[price]": "29.99",
                "product[description]": "Test description"
            });
            assert(result.status == 302);
            assert(result.flash.success);
            
            // 4. View created product
            product = model("Product").findOne(order="id DESC");
            result = $visit(route="product", key=product.id);
            assert(result.status == 200);
            assert(Find("Integration Test Product", result.body));
            
            // 5. Edit product
            result = $click("Edit");
            assert(Find('value="Integration Test Product"', result.body));
            
            result = $submitForm({
                "product[name]": "Updated Product",
                "product[price]": "39.99"
            });
            assert(result.status == 302);
            
            // 6. Verify update
            result = $visit(route="product", key=product.id);
            assert(Find("Updated Product", result.body));
            assert(Find("$39.99", result.body));
            
            // 7. Delete product
            result = $click("Delete", confirm=true);
            assert(result.status == 302);
            assert(result.flash.success contains "deleted");
            
            // 8. Verify deletion
            assert(!IsObject(model("Product").findByKey(product.id)));
            
            transaction action="rollback";
        }
    }
    
}

Test Types

Model Tests

Focus on:

  • Validations

  • Associations

  • Callbacks

  • Scopes

  • Custom methods

  • Data integrity

Controller Tests

Focus on:

  • Action responses

  • Parameter handling

  • Authentication/authorization

  • Flash messages

  • Redirects

  • Error handling

View Tests

Focus on:

  • Content rendering

  • Data display

  • HTML structure

  • Escaping/security

  • Conditional display

  • Helpers usage

Helper Tests

wheels generate test helper format
component extends="wheels.Test" {
    
    function test_format_currency() {
        assert(formatCurrency(19.99) == "$19.99");
        assert(formatCurrency(1000) == "$1,000.00");
        assert(formatCurrency(0) == "$0.00");
        assert(formatCurrency(-50.5) == "-$50.50");
    }
    
}

Route Tests

wheels generate test route products
component extends="wheels.Test" {
    
    function test_products_routes() {
        // Test route resolution
        assert($resolveRoute("/products") == {controller: "products", action: "index"});
        assert($resolveRoute("/products/new") == {controller: "products", action: "new"});
        assert($resolveRoute("/products/123") == {controller: "products", action: "show", key: "123"});
        
        // Test route generation
        assert(urlFor(route="products") == "/products");
        assert(urlFor(route="product", key=123) == "/products/123");
        assert(urlFor(route="newProduct") == "/products/new");
    }
    
}

Test Fixtures

Generate Fixtures

wheels generate test model product --fixtures

Creates /tests/fixtures/products.cfc:

component {
    
    function load() {
        // Clear existing data
        model("Product").deleteAll();
        
        // Load fixture data
        fixtures = [
            {
                name: "Widget",
                price: 19.99,
                description: "Standard widget",
                categoryId: 1,
                isActive: true
            },
            {
                name: "Gadget",
                price: 29.99,
                description: "Premium gadget",
                categoryId: 2,
                isActive: true
            },
            {
                name: "Doohickey",
                price: 9.99,
                description: "Budget doohickey",
                categoryId: 1,
                isActive: false
            }
        ];
        
        for (fixture in fixtures) {
            model("Product").create(fixture);
        }
        
        return fixtures;
    }
    
    function loadWithAssociations() {
        products = load();
        
        // Add reviews
        model("Review").create(
            productId: products[1].id,
            rating: 5,
            comment: "Excellent product!"
        );
        
        return products;
    }
    
}

Test Helpers

Custom Assertions

// In test file
function assertProductValid(required any product) {
    assert(IsObject(arguments.product), "Product should be an object");
    assert(arguments.product.id > 0, "Product should have valid ID");
    assert(Len(arguments.product.name), "Product should have name");
    assert(arguments.product.price > 0, "Product should have positive price");
}

function assertHasError(required any model, required string property) {
    local.errors = arguments.model.errorsOn(arguments.property);
    assert(ArrayLen(local.errors) > 0, 
           "Expected error on #arguments.property# but found none");
}

Test Data Builders

function createTestProduct(struct overrides = {}) {
    local.defaults = {
        name: "Test Product #CreateUUID()#",
        price: RandRange(10, 100) + (RandRange(0, 99) / 100),
        description: "Test description",
        isActive: true
    };
    
    StructAppend(local.defaults, arguments.overrides, true);
    
    return model("Product").create(local.defaults);
}

function createTestUser(struct overrides = {}) {
    local.defaults = {
        email: "test-#CreateUUID()#@example.com",
        password: "password123",
        firstName: "Test",
        lastName: "User"
    };
    
    StructAppend(local.defaults, arguments.overrides, true);
    
    return model("User").create(local.defaults);
}

Running Tests

Run all tests

wheels test

Run specific test file

wheels test app tests/models/ProductTest.cfc

Run specific test method

wheels test app tests/models/ProductTest.cfc::test_product_requires_name

Run with coverage

wheels test --coverage

Best Practices

  1. Test in isolation: Each test should be independent

  2. Use descriptive names: Test names should explain what they test

  3. Follow AAA pattern: Arrange, Act, Assert

  4. Clean up data: Use setup/teardown or transactions

  5. Test edge cases: Empty data, nulls, extremes

  6. Mock external services: Don't rely on external APIs

  7. Keep tests fast: Optimize slow tests

  8. Test one thing: Each test should verify one behavior

  9. Use fixtures wisely: Share common test data

  10. Run tests frequently: Before commits and in CI

Common Testing Patterns

Testing Private Methods

function test_private_method_through_public_interface() {
    // Don't test private methods directly
    // Test them through public methods that use them
    product = model("Product").new(name: "  Test  ");
    product.save(); // Calls private sanitize method
    assert(product.name == "Test");
}

Testing Time-Dependent Code

function test_expiration_date() {
    // Use specific dates instead of Now()
    testDate = CreateDate(2024, 1, 1);
    product = model("Product").new(
        expiresAt: DateAdd("d", 30, testDate)
    );
    
    // Test with mocked current date
    request.currentDate = testDate;
    assert(!product.isExpired());
    
    request.currentDate = DateAdd("d", 31, testDate);
    assert(product.isExpired());
}

Testing Randomness

function test_random_discount() {
    // Test the range, not specific values
    product = model("Product").new(price: 100);
    
    for (i = 1; i <= 100; i++) {
        discount = product.getRandomDiscount();
        assert(discount >= 0.05 && discount <= 0.25, 
               "Discount should be between 5% and 25%");
    }
}

See Also

  • wheels test run - Run tests

  • wheels test coverage - Test coverage

  • Testing Guide - Testing documentation

Command Reference

Complete reference for all Wheels CLI commands organized by category.

Quick Reference

Most Common Commands

Command
Description

Core Commands

Essential commands for managing your Wheels application.

Command
Description
Documentation

Server Management

Enhanced server commands that wrap CommandBox's native functionality with Wheels-specific features.

Command
Description
Documentation

Server Command Features

  • Validates Wheels application directory

  • Shows framework-specific information

  • Integrates with application reload

  • Provides helpful error messages

Code Generation

Commands for generating application code and resources.

Command
Alias
Description
Documentation

Generator Options

Common options across generators:

  • --force - Overwrite existing files

  • --help - Show command help

Database Commands

Commands for managing database schema and migrations.

Migration Management

Command
Description
Documentation

Migration Creation

Command
Description
Documentation

Database Operations

Command
Description
Documentation

Testing Commands

Commands for running and managing tests.

Command
Description
Documentation

Test Options

  • --watch - Auto-run on changes

  • --reporter - Output format (simple, json, junit)

  • --bundles - Specific test bundles

  • --labels - Filter by labels

Configuration Commands

Commands for managing application configuration.

Command
Description
Documentation

Environment Management

Commands for managing development environments and application context.

Command
Description
Documentation

Legacy Environment Commands

Command
Description
Documentation

Plugin Management

Commands for managing Wheels plugins.

Command
Description
Documentation

Plugin Options

  • --global - Install/list globally

  • --dev - Development dependency

Code Analysis

Commands for analyzing code quality and patterns.

Command
Description
Documentation

Security Commands

Commands for security scanning and hardening.

Command
Description
Documentation

Security Options

  • --fix - Auto-fix issues

  • --path - Specific path to scan

Performance Commands

Commands for optimizing application performance.

Command
Description
Documentation

Documentation Commands

Commands for generating and serving documentation.

Command
Description
Documentation

Documentation Options

  • --format - Output format (html, markdown)

  • --output - Output directory

  • --port - Server port

Maintenance Commands

Commands for managing application maintenance mode and cleanup tasks.

Command
Description
Documentation

Maintenance Options

  • --force - Skip confirmation prompts

  • --dryRun - Preview changes without executing

CI/CD Commands

Commands for continuous integration and deployment workflows.

Command
Description
Documentation

Docker Commands

Commands for Docker container management and deployment.

Command
Description
Documentation

Deployment Commands

Commands for managing application deployments.

Command
Description
Documentation

Deployment Options

  • --environment - Target environment

  • --force - Force deployment

  • --dry-run - Preview changes without deploying

Command Patterns

Getting Help

Every command supports --help:

Command Aliases

Many commands have shorter aliases:

Common Workflows

Creating a new feature:

Starting development:

Deployment preparation:

Interactive debugging:

Running maintenance scripts:

Environment Variables

Variable
Description
Default

Exit Codes

Code
Description

Command Status Notes

Some commands in the Wheels CLI are currently in various states of development or maintenance:

Broken Commands

  • wheels docs - Base documentation command is currently broken

  • wheels generate api-resource - API resource generation is currently broken

Disabled Commands

The following commands exist in the codebase but are currently disabled:

  • Some CI and Docker commands have disabled variants in the codebase

These commands may be re-enabled in future versions of Wheels.

See Also

wheels generate app [name]

Create new application

wheels scaffold [name]

Generate complete CRUD

wheels dbmigrate latest

Run database migrations

wheels test run

Run application tests

wheels server start

Start development server

wheels server status

Check server status

wheels watch

Watch files for changes

wheels reload

Reload application

wheels init

Bootstrap existing app for CLI

Details

wheels info

Display version information

Details

wheels reload [mode]

Reload application

Details

wheels deps

Manage dependencies

Details

wheels destroy [type] [name]

Remove generated code

Details

wheels watch

Watch for file changes

Details

wheels server

Display server commands help

Details

wheels server start

Start development server

Details

wheels server stop

Stop development server

Details

wheels server restart

Restart server and reload app

Details

wheels server status

Show server status with Wheels info

Details

wheels server log

Tail server logs

Details

wheels server open

Open application in browser

Details

wheels generate app

wheels new

Create new application

Details

wheels generate app-wizard

Interactive app creation

Details

wheels generate controller

wheels g controller

Generate controller

Details

wheels generate model

wheels g model

Generate model

Details

wheels generate view

wheels g view

Generate view

Details

wheels generate property

Add model property

Details

wheels generate route

Generate route

Details

wheels generate resource

REST resource

Details

wheels generate api-resource

API resource (Currently broken)

Details

wheels generate frontend

Frontend code

Details

wheels generate test

Generate tests

Details

wheels generate snippets

Code snippets

Details

wheels scaffold

Complete CRUD

Details

wheels dbmigrate info

Show migration status

Details

wheels dbmigrate latest

Run all pending migrations

Details

wheels dbmigrate up

Run next migration

Details

wheels dbmigrate down

Rollback last migration

Details

wheels dbmigrate reset

Reset all migrations

Details

wheels dbmigrate exec [version]

Run specific migration

Details

wheels dbmigrate create blank [name]

Create empty migration

Details

wheels dbmigrate create table [name]

Create table migration

Details

wheels dbmigrate create column [table] [column]

Add column migration

Details

wheels dbmigrate remove table [name]

Drop table migration

Details

wheels db schema

Export/import schema

Details

wheels db seed

Seed database

Details

wheels test [type]

Run framework tests

Details

wheels test run [spec]

Run TestBox tests

Details

wheels test coverage

Generate coverage report

Details

wheels test debug

Debug test execution

Details

wheels config list

List configuration

Details

wheels config set [key] [value]

Set configuration

Details

wheels config env

Environment config

Details

wheels environment

Display/switch environment

Details

wheels environment set [env]

Set environment with reload

Details

wheels environment list

List available environments

Details

wheels console

Interactive REPL console

Details

wheels runner [script]

Execute scripts with context

Details

wheels env

Environment base command

Details

wheels env setup [name]

Setup environment

Details

wheels env list

List environments

Details

wheels env switch [name]

Switch environment

Details

wheels plugins

Plugin management base command

Details

wheels plugins list

List plugins

Details

wheels plugins install [name]

Install plugin

Details

wheels plugins remove [name]

Remove plugin

Details

wheels analyze

Code analysis base command

Details

wheels analyze code

Analyze code quality

Details

wheels analyze performance

Performance analysis

Details

wheels analyze security

Security analysis (deprecated)

Details

wheels security

Security management base command

Details

wheels security scan

Scan for vulnerabilities

Details

wheels optimize

Optimization base command

Details

wheels optimize performance

Optimize application

Details

wheels docs

Documentation base command (Currently broken)

Details

wheels docs generate

Generate documentation

Details

wheels docs serve

Serve documentation

Details

wheels maintenance:on

Enable maintenance mode

Details

wheels maintenance:off

Disable maintenance mode

Details

wheels cleanup:logs

Remove old log files

Details

wheels cleanup:tmp

Remove temporary files

Details

wheels cleanup:sessions

Remove expired sessions

Details

wheels ci init

Initialize CI/CD configuration

Details

wheels docker init

Initialize Docker configuration

Details

wheels docker deploy

Deploy using Docker

Details

wheels deploy

Deployment base command

Details

wheels deploy audit

Audit deployment configuration

Details

wheels deploy exec

Execute deployment

Details

wheels deploy hooks

Manage deployment hooks

Details

wheels deploy init

Initialize deployment

Details

wheels deploy lock

Lock deployment state

Details

wheels deploy logs

View deployment logs

Details

wheels deploy proxy

Configure deployment proxy

Details

wheels deploy push

Push deployment

Details

wheels deploy rollback

Rollback deployment

Details

wheels deploy secrets

Manage deployment secrets

Details

wheels deploy setup

Setup deployment environment

Details

wheels deploy status

Check deployment status

Details

wheels deploy stop

Stop deployment

Details

wheels [command] --help
wheels generate controller --help
wheels dbmigrate create table --help
wheels g controller users  # Same as: wheels generate controller users
wheels g model user       # Same as: wheels generate model user
wheels new myapp         # Same as: wheels generate app myapp
wheels scaffold name=product properties=name:string,price:decimal
wheels dbmigrate latest
wheels test run
wheels server start      # Start the server
wheels watch            # Terminal 1: Watch for file changes
wheels server log       # Terminal 2: Monitor logs
wheels test run --watch # Terminal 3: Run tests in watch mode
wheels test run
wheels security scan
wheels optimize performance
wheels dbmigrate info
wheels environment production
wheels console                    # Start REPL
wheels console environment=testing # Test in specific env
wheels console execute="model('User').count()"  # Quick check
wheels runner scripts/cleanup.cfm
wheels runner scripts/migrate.cfm environment=production
wheels runner scripts/report.cfm params='{"month":12}'

WHEELS_ENV

Environment mode

development

WHEELS_DATASOURCE

Database name

From config

WHEELS_RELOAD_PASSWORD

Reload password

From config

0

Success

1

General error

2

Invalid arguments

3

File not found

4

Permission denied

5

Database error

Installation Guide
Quick Start Guide
Creating Custom Commands
CLI Architecture