Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 166 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...

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...

Working with Wheels

Loading...

Loading...

Loading...

Loading...

Loading...

Running Local Development Servers

Wheels uses a Docker-based development environment that provides consistent, containerized development with support for multiple CFML engines and databases.

Prerequisites

  1. Docker: Install Docker Desktop from docker.com

  2. Wheels CLI: Install the Wheels CommandBox module:

    box install wheels-cli

Setting up Docker Development Environment

Ensure you are in the application root directory.

Initialize your Docker development environment:

wheels docker init cfengine=adobe cfversion=2018 db=mysql

Command Options

cfengine options:

  • lucee - Lucee CFML engine

  • adobe - Adobe ColdFusion

  • boxlang - BoxLang CFML engine

cfversion options:

  • Major versions for Adobe ColdFusion: 2018, 2021, 2023, 2025

  • Major versions for Lucee: 5, 6, 7

  • Major versions for BoxLang: 1

db options:

  • mysql - MySQL database

  • postgres - PostgreSQL database

  • mssql - Microsoft SQL Server

  • h2 - H2 embedded database

Generated Files

The wheels docker init command creates several files in your project:

  • .dockerignore - Specifies files to exclude from Docker build context

  • Dockerfile - Container definition for your chosen CFML engine

  • docker-compose.yml - Multi-container application definition

  • CFConfig.json - CFML engine configuration with datasource setup

Starting Your Development Environment

After running the init command, start your containers:

docker-compose up -d

The containers will take a few minutes to start the first time as Docker downloads the necessary images. Once started, your application will be available at:

  • Default: http://localhost:8080

  • Custom port: Check your server.json file for the configured port

Managing Your Docker Environment

# Stop the containers
docker-compose down

# View running containers
docker-compose ps

# View container logs
docker-compose logs

# Rebuild and restart
docker-compose up -d --build

Additional Configuration

Custom Ports

The default port is 8080, but you can customize this by modifying the server.json:

{
    "name":"wheels",
    "web":{
        "host":"localhost",
        "http":{
            "port":3000
        },
        "webroot":"public",
        "rewrites":{
            "enable":true,
            "config":"public/urlrewrite.xml"
        }
    }
}

Database Configuration

The generated CFConfig.json file automatically configures a datasource for your chosen database. The configuration includes:

  • Connection settings for your selected database type

  • Default datasource named wheels-dev

  • Appropriate drivers for the database engine

Development Workflow

  1. Make code changes in your directory

  2. Changes are reflected immediately due to Docker volume mounting

  3. Database changes persist between container restarts

  4. Use standard Wheels commands like migrations, generators, etc.

Troubleshooting

Containers won't start:

# Check if ports are in use
docker-compose ps
netstat -an | grep 8080

# Force recreate containers
docker-compose down
docker-compose up -d --force-recreate

Database connection issues:

# Check database container logs
docker-compose logs db

# Restart just the database
docker-compose restart db

Performance issues:

  • Ensure Docker Desktop has adequate memory allocated (4GB+ recommended)

  • On Windows/Mac, enable file sharing for your project directory

CFIDE / Lucee administrators

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

Getting Started

Install Wheels and get a local development server running

By far the quickest way to get started with Wheels is via . 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 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 on Windows, 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.

Core Commands

CommandBox
CommandBox
Chocolatey
Homebrew
Figure: Wheels congratulations screen

Database Operations

Database Commands

Migration Commands

Documentation

Code Generation

Testing Commands

Code Analysis

Plugins

CLI Development Guides

Asset Management

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 jQuery framework 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:

/config/routes.cfm
mapper()
  .get(name="sayHello", controller="say", action="hello")
.end()

The shorthand method would look like:

/config/routes.cfm
mapper()
  .get(name="sayHello", to="say##hello")
.end()

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:

/app/views/say/hello.cfm
<cfoutput>

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

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

</cfoutput>

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 javaScriptIncludeTag() ):

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);

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 provides() and renderWith() functions:

/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);
    }
}

In this controller's config() method, we use the provides() 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 renderWith() 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 provides() and renderWith(), reference the chapter on Responding with Multiple Formats.

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.

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.

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; Lucee offers an excellent open source alternative. Using CommandBox 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:

  • Adobe ColdFusion 2018 / 2021 / 2023 / 2025

  • Lucee 5.2.1.9+ / 6 / 7

  • BoxLang 1

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 CommandBox.

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 URL Rewriting 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+

  • Oracle 19c+

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 here.

  • We also recommend using the InnoDB engine if you want Transactions 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.

Docker Commands

Get Commands

Environment Management

Config

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 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)

New Command

Why the Change?

  • Better command organization with dedicated security namespace

  • Enhanced scanning capabilities

  • Improved reporting options

  • Integration with security vulnerability databases

See Also

  • - 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 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.

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 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 analyze security
wheels security scan [path] [--fix] [--output=<format>] [--detailed]
security scan
wheels info
,--.   ,--.,--.                   ,--.            ,-----.,--.   ,--. 
|  |   |  ||  ,---.  ,---.  ,---. |  | ,---.     '  .--./|  |   |  | 
|  |.'.|  ||  .-.  || .-. :| .-. :|  |(  .-'     |  |    |  |   |  | 
|   ,'.   ||  | |  |\   --.\   --.|  |.-'  `)    '  '--'\|  '--.|  | 
'--'   '--'`--' `--' `----' `----'`--'`----'      `-----'`-----'`--' 
============================ Wheels CLI ============================
Current Working Directory: /Users/username/myapp
CommandBox Module Root: /Users/username/.CommandBox/cfml/modules/wheels-cli/
Current Wheels Version in this directory: 3.0.0-SNAPSHOT
====================================================================
wheels init
wheels deps

Screencasts

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

Wheels 2.x

Create a basic CRUD interface in Wheels 2.x https://youtu.be/K5HLItTru1g

Create a basic JSON API in Wheels 2.x https://youtu.be/qZr5JzO0vo4

Routing in Wheels 2.x - Part 1 https://youtu.be/BnPGApAvMVQ

Routing in Wheels 2.x - Part 2 https://youtu.be/0CiGxJyJEIQ

Introduction to Unit Testing in Wheels 2.x https://youtu.be/XgMuzzmBQ98

Unit Testing Controllers in Wheels 2.x https://youtu.be/cygj9WDqHjY

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.

View all screencasts on Vimeo

CRUD series

Episode 1: "C" Is for "Create" - Basic CRUD Learn about basic create operations when building standard CRUD functionality in Wheels

Episode 2: "R"; Is for "Read" - Basic CRUD Learn about basic read operations when building standard CRUD functionality in Wheels

Episode 3: "U" Is for "Update" - Basic CRUD Chris Peters demonstrates updating data in a simple CRUD Wheels application

Episode 4: "D" Is for Delete - Basic CRUD Learn how simple it is to delete records in a basic CRUD application using Wheels

"Building a Social Network"

Episode 1: Setting up ColdFusion on Wheels Chris Peters starts the webcast series by demonstrating how to set up ColdFusion on Wheels;

Episode 2: Form Helpers Chris Peters demonstrates how to bind a Wheels model object to a form through the use of form helpers

Episode 3: Object Validation and Showing Errors

Chris Peters adds data validation to the user registration form

Episode 4: Redirects and the Flash 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

Episode 5: Object Validation Chris Peters teaches you about more validation options and how you can add them to the registration form quickly and easily

Episode 6: Styling Forms Chris Peters stylizes form markup globally using a Wheels feature called global helpers

Episode 7: Authentication with Filters Learn how to set up simple user authentication on a website by using a Wheels feature called filters

Episode 8: Reading and Displaying a Single Record Learn the mechanics of reading a single record from the database and displaying its data in the view

Episode 9: Adding a Route for User Profiles Creating custom URL patterns is a breeze in ColdFusion on Wheels

Episode 10: Displaying Sets of Records Learn how to fetch multiple records from your model with findAll() and then display them to the user using ColdFusion on Wheels

Episode 11: Custom View Helpers Learn how to factor out logic in your view templates into custom helper functions in ColdFusion on Wheels

Episode 12: Joining Models with Associations Chris Peters demonstrates joining data together with model associations using ColdFusion on Wheels

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

Episode 14: Responding with Multiple Formats Learn how to use the provides() and renderWith() functions to automatically serialize data into XML, JSON, and more

Other

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

CFUnited 2010: Simplifying Database Code with the ColdFusion on Wheels ORM Chris Peters gives a high level overview of the ORM included with ColdFusion on Wheels

ColdRoute Plugin Chris Peters from Liquifusion demonstrates the ColdRoute plugin for Wheels

Wirebox Plugin for Wheels Doug Boude demonstrates using his new Wirebox plugin for Wheels

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

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

Wheels Textmate Bundle Demo A quick demo of the Wheels Textmate bundle by Russ Johnson

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, Oracle 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!

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 / in 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.

/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.

wheels init

Bootstrap an existing Wheels application for CLI usage by creating necessary configuration files.

Bootstrap an existing Wheels application for CLI usage.

Synopsis

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.

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

Example interaction:

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

box.json

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

  • - Create a new Wheels application

  • - Reload the application

  • - Display version 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 plugin list

Lists installed Wheels plugins from the /plugins folder or shows available plugins from ForgeBox.

Usage

Parameters

Parameter
Required
Type
Options
Default
Description

Description

The plugins list command displays information about Wheels plugins. By default, it shows plugins installed locally in the /plugins folder. With the --available flag, it queries ForgeBox to show all available cfwheels-plugins packages.

Local Plugin Information

When listing installed plugins, the command displays:

  • Plugin name

  • Version number

  • Description (if available)

Available Plugin Information

When using --available, the command shows all cfwheels-plugins type packages from ForgeBox.

Examples

List installed plugins

Output:

List with no plugins installed

Output:

Export as JSON

Output:

Show available plugins from ForgeBox

Output:

How It Works

  1. Local Plugin Detection: Scans the /plugins folder for subdirectories

  2. Metadata Extraction: Reads each plugin's box.json file for name, version, slug, and description

  3. Dynamic Formatting: Calculates column widths based on content for clean alignment

  4. ForgeBox Integration: Uses forgebox show type=cfwheels-plugins for available plugins

Notes

  • Only lists plugins from the /plugins folder (not box.json dependencies)

  • Only works with cfwheels-plugins type packages

  • Plugins without a valid box.json are ignored

  • Column widths adjust dynamically for optimal display

  • JSON output includes plugin count for programmatic use

  • Use wheels plugin info <name> to see detailed information about a specific plugin

wheels docs serve

Serves generated documentation locally for development and review.

Usage

Parameters

Parameter
Description
Default

Description

The docs serve command starts a local web server to preview your generated documentation.

Examples

Basic documentation server

Serve on different port

Serve from custom directory

Serve without opening browser

Custom configuration

Server Output

If documentation is not found:

Features

Browser Integration

With --open=true (default), the server automatically opens your default browser to the documentation URL.

Development Workflow

Typical usage:

Custom workflow:

Troubleshooting

Port already in use

Documentation not found

Browser doesn't open

Notes

  • Server is intended for development/review only

  • For production, deploy static files to web server

  • Large documentation sets may take time to generate

  • Offline mode caches documentation locally

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

format

No

string

table, json

table

Output format for the plugin list

available

No

boolean

true, false

false

Show available plugins from ForgeBox

wheels plugins list
===========================================================
  Installed Wheels Plugins (3)
===========================================================

Plugin Name           Version     Description
---------------------------------------------------------------
bcrypt                0.0.4       Bcrypt encryption for Wheels
shortcodes            0.0.4       Shortcode support
wheels-test           1.0.0       Testing utilities

-----------------------------------------------------------

[OK] 3 plugins installed

Commands:
  wheels plugin info <name>      View plugin details
  wheels plugin update:all       Update all plugins
  wheels plugin outdated         Check for updates
wheels plugins list
===========================================================
  Installed Wheels Plugins
===========================================================

No plugins installed in /plugins folder

Install plugins with:
  wheels plugin install <plugin-name>

See available plugins:
  wheels plugin list --available
wheels plugins list --format=json
{
  "plugins": [
    {
      "name": "bcrypt",
      "slug": "cfwheels-bcrypt",
      "version": "0.0.4",
      "description": "Bcrypt encryption for Wheels"
    },
    {
      "name": "shortcodes",
      "slug": "cfwheels-shortcodes",
      "version": "0.0.4",
      "description": "Shortcode support"
    }
  ],
  "count": 2
}
wheels plugins list --available
===========================================================
  Available Wheels Plugins on ForgeBox
===========================================================

[Lists all cfwheels-plugins type packages from ForgeBox using 'forgebox show']
wheels docs serve [--root=<dir>] [--port=<port>] [--open]

--root

Root directory to serve

docs/api

--port

Port to serve on

35729

--open

Open browser automatically

true

wheels docs serve
wheels docs serve --port=8080
wheels docs serve --root=public/api-docs
wheels docs serve --open=false
wheels docs serve --root=docs/generated --port=3000
Output directory: D:\Command Box\wheels\templates\base\src\docs\api\
CommandBox:src> wheels docs serve
 √ | Starting Server
   | √ | Setting site [wheels-docs-BBAA12EF-7A83-4D03-BD6DBFE4AC17C1F9] Profile to [development]
   | √ | Loading CFConfig into server

Status: starting
Server is still starting... waiting...
Server is up and running!
Starting documentation server...

Documentation server started!

Serving: D:\Command Box\wheels\templates\base\src\docs\api\
URL: http://localhost:35729
Opening browser...

Press Ctrl+C to stop the server
Documentation directory not found: /docs/api

💡 Tip: Run 'wheels docs generate' first to create documentation
# Step 1: Generate documentation
wheels docs generate

# Step 2: Serve documentation
wheels docs serve

# Step 3: Make changes and regenerate
wheels docs generate
# Browser will show updated docs
# Generate and serve from custom location
wheels docs generate --output=public/docs
wheels docs serve --root=public/docs --port=8080
# Use a different port
wheels docs serve --port=8081
# Make sure to generate docs first
wheels docs generate
wheels docs serve
# Manually navigate to the URL shown
# Or check your default browser settings
wheels init
wheels init
==================================== 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
{
  "name": "myapp",
  "web": {
    "http": {
      "port": 60000
    }
  },
  "app": {
    "cfengine": "lucee5"
  }
}
{
  "name": "myapp",
  "version": "1.0.0",
  "dependencies": {
    "wheels": "^2.5.0"
  }
}
wheels generate app
wheels reload
wheels 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 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

wheels reload

Reload the Wheels application in different modes.

Synopsis

wheels reload [options]
wheels r [options]

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

mode

Reload mode: development, testing, maintenance, production

development

password

Required - The reload password configured in your application

None

Reload Modes

Development Mode

wheels reload password=mypassword
  • Enables debugging

  • Shows detailed error messages

  • Disables caching

  • Ideal for active development

Testing Mode

wheels reload mode=testing password=mypassword
  • Optimized for running tests

  • Consistent environment

  • Predictable caching

Maintenance Mode

wheels reload mode=maintenance password=mypassword
  • Shows maintenance page to users

  • Allows admin access

  • Useful for deployments

Production Mode

wheels reload mode=production password=mypassword
  • Full caching enabled

  • Minimal error information

  • Optimized performance

Examples

Basic reload (development mode)

wheels reload password=wheels

Reload in production mode

wheels reload mode=production password=mySecretPassword

Using the alias

wheels r password=wheels

Reload for testing

wheels reload mode=testing password=wheels

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:

set(reloadPassword="mySecretPassword");

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

  • wheels init - Initialize application configuration=

  • wheels info - Display application information

wheels dbmigrate exec

Execute a specific database migration by version number.

Synopsis

wheels dbmigrate exec version=<version>

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

version

string

Yes

Version to migrate to

Examples

Execute a specific migration

wheels dbmigrate exec version=20240115123456

Migrate to version 0 (revert all migrations)

wheels dbmigrate exec version=0

Use Cases

Migrating to a Specific Version

Move to any point in migration history:

# Check current status
wheels dbmigrate info

# Migrate to specific version
wheels dbmigrate exec version=20240115123456

Rolling Back to Previous Version

Move to an earlier migration state:

# Check migration history
wheels dbmigrate info

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

Reset Database

Clear all migrations:

# Migrate to version 0
wheels dbmigrate exec version=0

# Verify empty state
wheels dbmigrate info

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

  • 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 dbmigrate create blank - Create a new migration

wheels plugin remove

Removes an installed Wheels CLI plugin.

Synopsis

wheels plugins remove <name> [--force]

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Positional parameters: wheels plugins remove cfwheels-bcrypt (plugin name)

  • Named parameters: name=value (e.g., name=cfwheels-bcrypt)

  • Flag parameters: --flag equals flag=true (e.g., --force equals force=true)

Parameter Mixing Rules:

✅ ALLOWED:

  • Positional: wheels plugins remove cfwheels-bcrypt

  • Positional + flags: wheels plugins remove cfwheels-bcrypt --force

  • All named: name=cfwheels-bcrypt force=true

❌ NOT ALLOWED:

  • Positional + named for same param: wheels plugins remove cfwheels-bcrypt name=other

Recommendation: Use positional for plugin name, flags for options: wheels plugins remove cfwheels-bcrypt --force

Parameters

Parameter
Required
Type
Description

name

Yes

string

Plugin name to remove

--force

No

boolean

Force removal without confirmation

Description

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

  • Checks if the plugin is installed

  • Prompts for confirmation (unless --force is used)

  • Removes plugin from box.json

  • Cleans up plugin files

  • Updates plugin registry

Examples

Basic plugin removal

wheels plugins remove wheels-vue-cli

Force removal (skip confirmation)

wheels plugins remove wheels-testing --force

Removal Process

  1. Installation Check: Verifies the plugin is installed in box.json or plugins folder

  2. Confirmation: Prompts user to confirm removal (unless --force is used)

  3. Removal: Removes plugin entry from box.json

  4. File Cleanup: Deletes plugin files via CommandBox package service

  5. 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...

[OK] Plugin removed successfully
Run 'wheels plugins list' to see remaining plugins

With force flag

[*] Removing plugin: wheels-vue-cli...

[OK] Plugin removed successfully
Run 'wheels plugins list' to see remaining plugins

Plugin not installed

Are you sure you want to remove the plugin 'bcrypt'? (y/n): y
[*] Removing plugin: bcrypt...

[ERROR] Failed to remove plugin: Plugin 'bcrypt' is not installed

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 wheels plugins list to verify removal

  • The command checks if plugin is actually installed before removal

  • Plugin must exist in box.json dependencies, devDependencies, or plugins folder

  • Restart your application after removing plugins that affect core functionality

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, H2, MSSQL(SQL Server), Oracle.)

  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 c_o_r_e_migrator_versions 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

Troubleshooting

Migration Not Showing

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

  • Verify .cfc extension

  • Ensure proper timestamp format

Version Mismatch

  • Check c_o_r_e_migrator_versions 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 plugin info

Shows detailed information about a Wheels plugin, including version, description, author, and links.

Usage

Parameters

Parameter
Required
Type
Description

Description

The plugins info command displays comprehensive information about a Wheels plugin. It prioritizes local installation data when available, only querying ForgeBox when the plugin is not installed locally.

Information Displayed

When the plugin is installed locally, the command shows:

  • Installation status

  • Plugin name and version

  • Slug

  • Type (mvc, plugin, etc.)

  • Author information

  • Description

  • Homepage URL

  • Repository URL

  • Documentation URL

  • Issues/Bugs URL

  • Keywords

When the plugin is not installed, the command shows:

  • Installation status

  • ForgeBox package information

  • Available versions

  • Installation instructions

Examples

Check installed plugin

Output:

Check plugin not installed

Output:

Plugin not found anywhere

Output:

How It Works

  1. Check Local Installation: First checks if the plugin is installed in:

    • box.json dependencies

    • box.json devDependencies

    • Reads plugin's local box.json for detailed information

  2. Display Local Information: If installed, shows all metadata from the plugin's box.json

  3. ForgeBox Fallback: Only queries ForgeBox if the plugin is not installed locally

  4. Installation Commands: Shows appropriate commands based on installation status

Notes

  • The command prioritizes local plugin data over ForgeBox data for accuracy

  • No network call is made for installed plugins (faster response)

  • Use wheels plugin search to browse all available plugins

  • Plugin names can include variations (e.g., "wheels-core", "cfwheels-core")

wheels docs generate

Generates documentation for your Wheels application from code comments and annotations.

Usage

Parameters

  • --output - (Optional) Output directory for docs. Default: docs/api

  • --format - (Optional) Documentation format: html, json, markdown. Default: html

  • --include - (Optional) Components to include: models, controllers, views, services. Default: models,controllers

  • --serve - (Optional) Start local server after generation

  • --verbose - (Optional) Verbose output

Description

The docs generate command automatically creates comprehensive documentation from your Wheels application by parsing:

  • JavaDoc-style comments in CFCs

  • Model relationships and validations

  • Controller actions and routes

  • Configuration files

  • Database schema

  • API endpoints

Examples

Generate complete documentation

Generate markdown docs

Generate and serve immediately

Generate specific components with verbose output

Custom output directory

Documentation Sources

Model Documentation

Controller Documentation

Generated Output

HTML Format

Documentation includes:

  • Overview: Application structure and architecture

  • Models: Properties, methods, relationships, validations

  • Controllers: Actions, filters, routes

  • API Reference: Endpoints, parameters, responses

  • Database Schema: Tables, columns, indexes

  • Configuration: Settings and environment variables

Output Example

Documentation Features

Auto-generated Content

  • Class hierarchies and inheritance

  • Method signatures and parameters

  • Property types and defaults

  • Relationship diagrams

  • Route mappings

  • Database ERD

Notes

  • Documentation is generated from code comments

  • Use consistent JavaDoc format for best results

  • Private methods are excluded by default

Conventions

With a convention-over-configuration framework like Wheels, it's important to know these conventions. This is your guide.

There is a specific set of standards that Wheels follows when you run it in its default state. This is to save you time. With conventions in place, you can get started coding without worrying about configuring every little detail.

But it is important for you to know these conventions, especially if you're running an operating system and/or DBMS configuration that's picky about things like case sensitivity.

URLs

Wheels uses a very flexible routing system to match your application's URLs to controllers, views, and parameters.

Within this routing system is a default route that handles many scenarios that you'll run across as a developer. The default route is mapped using the pattern [controller]/[action]/[key].

Consider this example URL: http://localhost:8080/users/edit/12

http://localhost:8080/users/edit/12

This maps to the Users controller, edit action, and a key of 12. For all intents and purposes, this will load a view for editing a user with a primary key value in the database of 12.

This URL pattern works up the chain and will also handle the following example URLs:

URL
Controller
Action
Key

Note that the above conventions are for GET requests and only apply when you have a wildcard() call in /config/routes.cfm (which is the default). See for instructions on overriding this behavior and how to deal with PUT, POST etc.

Naming Conventions for Controllers, Actions, and Views

Controllers, actions, and views are closely linked together by default. And how you name them will influence the URLs that Wheels will generate.

Controllers

First, a controller is a CFC file placed in the controllers folder. It should be named in PascalCase. For example, a site map controller would be stored at /app/controllers/SiteMap.cfc.

Multi-word controllers will be delimited by hyphens in their calling URLs. For example, a URL of /site-map will reference the SiteMap controller.

See for instructions on overriding this behavior.

Actions

Methods within the controllers, known as actions, should be named in camelCase.

Like with controllers, any time a capital letter is used in camelCase, a hyphen will be used as a word delimiter in the corresponding URL. For example, a URL of /site-map/search-engines will reference the searchEngines action in the SiteMap controller.

See for instructions on overriding this behavior.

Views

By default, view files are named after the action names and are stored in folders that correspond to controller names. Both the folder names and view file names should be all lowercase, and there is no word delimiter.

In our /site-map/search-engines URL example, the corresponding view file would be stored at /app/views/sitemap/searchengines.cfm.

For information on overriding this behavior, refer to documentation for the function and read the chapter.

Layouts

A special type of view file called a layout defines markup that should surround the views loaded by the application. The default layout is stored at /app/views/layout.cfm and is automatically used by all views in the application.

Controller-level layouts can also be set automatically by creating a file called layout.cfm and storing it in the given controller's view folder. For example, to create a layout for the users controller, the file would be stored at /app/views/users/layout.cfm.

When a controller-level layout is present, it overrides the default layout stored in the root /app/views folder.

For information on overriding the layout file to be loaded by an action, see the chapter on and documentation for the function.

Naming Conventions for Models and Databases

By default, the names of Wheels models, model properties, database tables, and database fields all relate to each other. Wheels even sets a sensible default for the CFML data source used for database interactions.

Data Sources

By default, the datasource is set to wheels-dev in the /config/settings.cfm file. You can change the value in the set(dataSourceName="wheels-dev") function to whatever you want the name of the datasource to be.

Refer to the chapter for instructions on overriding data source information.

Plural Database Table Names, Singular Model Names

Wheels adopts a Rails-style naming conventions for database tables and model files. Think of a database table as a collection of model objects; therefore, it is named with a plural name. Think of a model object as a representation of a single record from the database table; therefore, it is named with a singular word.

For example, a user model represents a record from the users database table. Wheels also recognizes plural patterns like binary/binaries, mouse/mice, child/children, etc.

Like controller files, models are also CFCs and are named in PascalCase. They are stored in the /app/models folder. So the user model would be stored at /app/models/User.cfc.

For instructions on overriding database naming conventions, refer to documentation for the function and the chapter on .

Everything in the Database is Lowercase

In your database, both table names and column names should be lowercase. The customersegments table could have fields called title, regionid, and incomelevel, for example.

Because of CFML's case-insensitive nature, we recommend that you refer to model names and corresponding properties in camelCase. This makes for easier readability in your application code.

In the customersegments example above, you could refer to the properties in your CFML as title, regionId, and incomeLevel to stick to CFML's Java-style roots. (Built-in CFML functions are often written in camelCase and PascalCase, after all.)

For information on overriding column and property names, refer to documentation for the function and the chapter.

Configuration and Defaults

There are many default values and settings that you can tweak in Wheels when you need to. Some of them are conventions and others are just configurations available for you to change. You can even change argument defaults for built-in Wheels functions to keep your code DRYer.

For more details on what you can configure, read the chapter.

wheels plugins info <name>

name

Yes

string

Name or slug of the plugin to inspect

wheels plugins info wheels-core
===========================================================
  Plugin Information: wheels-core
===========================================================

Status:
  [OK] Installed locally

Wheels Core
Wheels Framework Core Directory

Details:
  Version:     3.0.0-SNAPSHOT+1030
  Slug:        wheels-core
  Type:        mvc
  Author:      Wheels Core Team and Community
  Keywords:    mvc, rails, wheels, wheels.dev, core

Links:
  Homepage:    https://wheels.dev/
  Repository:  https://github.com/wheels-dev/wheels
  Docs:        https://wheels.dev/docs
  Issues:      https://github.com/wheels-dev/wheels/issues

Commands:
  Update:  wheels plugin update wheels-core
  Search:  wheels plugin search
wheels plugins info wheels-vue-cli
===========================================================
  Plugin Information: wheels-vue-cli
===========================================================

Status:
  [X] Not installed

[ForgeBox package information displayed]

Commands:
  Install: wheels plugin install wheels-vue-cli
  Search:  wheels plugin search
wheels plugins info nonexistent-plugin
===========================================================
  Plugin Information: nonexistent-plugin
===========================================================

Status:
  [X] Not installed

Plugin Not Installed

The plugin 'nonexistent-plugin' was not found in:
 Local installation (box.json dependencies)
 ForgeBox repository

Possible reasons:
 Plugin name may be misspelled
 Plugin may not exist on ForgeBox
 Network connection issues

Suggestions:
 Search for available plugins: wheels plugin list --available
 Verify the correct plugin name
wheels docs generate [--output=<dir>] [--format=<format>]  [--include=<components>] [--serve] [--verbose]
wheels docs generate
wheels docs generate --format=markdown
wheels docs generate --serve
wheels docs generate --include=models,controllers,services --verbose
wheels docs generate --output=public/api-docs --format=html
/**
 * User model for authentication and authorization
 * 
 * @author John Doe
 * @since 1.0.0
 */
component extends="Model" {
    
    /**
     * Initialize user relationships and validations
     * @hint Sets up the user model configuration
     */
    function config() {
        // Relationships
        hasMany("orders");
        belongsTo("role");
        
        // Validations
        validatesPresenceOf("email,firstName,lastName");
        validatesUniquenessOf("email");
    }
    
    /**
     * Find active users with recent activity
     * 
     * @param days Number of days to look back
     * @return query Active users
     */
    public query function findActive(numeric days=30) {
        return findAll(
            where="lastLoginAt >= :date",
            params={date: dateAdd("d", -arguments.days, now())}
        );
    }
}
/**
 * Handles user management operations
 * 
 * @displayname User Controller
 * @namespace /users
 */
component extends="Controller" {
    
    /**
     * Display paginated list of users
     * 
     * @hint GET /users
     * @access public
     * @return void
     */
    function index() {
        param name="params.page" default="1";
        users = model("user").findAll(
            page=params.page,
            perPage=20,
            order="createdAt DESC"
        );
    }
}
/docs/api/
├── index.html
├── models/
│   ├── model.html/
├── controllers/
│   ├── controller.html
├── views/
|   └── view.html
├── services/
    └── view.html
Documentation Generator
==================================================

Generating documentation...

Scanning source files...
[OK] Found 1 models
[OK] Found 1 controllers

Writing documentation... [OK] HTML files generated

==================================================
[SUCCESS] Documentation generated successfully!

Summary:
  - Models: 1 files
  - Controllers: 1 files
  - Total: 2 components documented

Output directory: C:\path\to\docs\api\
wheels dbmigrate info
+-----------------------------------------+-----------------------------------------+
| Datasource:                       myApp | Total Migrations:                     4 |
| Database Type:                       H2 | Available Migrations:                 4 |
|                                         | Current Version:                      0 |
|                                         | Latest Version:          20250812161449 |
+-----------------------------------------+-----------------------------------------+
+----------+------------------------------------------------------------------------+
|          | 20250812161449_cli__create_reporting_procedures                        |
|          | 20250812161302_cli__blacnk                                             |
|          | 20250812161250_cli__name                                               |
|          | 20250812154338_cli__0                                                  |
+----------+------------------------------------------------------------------------+
[timestamp]_[description].cfc
20240125160000_create_users_table.cfc
SELECT * FROM c_o_r_e_migrator_versions;
+----------------+
| version        |
+----------------+
| 20240101100000 |
| 20240105150000 |
| 20240110090000 |
| 20240115120000 |
+----------------+
wheels dbmigrate info
wheels dbmigrate latest
wheels dbmigrate info
#!/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

http://localhost:8080/users/edit/12

users

edit

12

http://localhost:8080/users/new

users

new

http://localhost:8080/users

users

index

Routing
Routing
Routing
renderView()
Pages
Layouts
renderView
Configuration and Defaults
table()
Object Relational Mapping
property()
Object Relational Mapping
Configuration and Defaults

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

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Named parameters: name=value (e.g., name=users)

  • Flag parameters: --flag=value (e.g., --name=users)

Parameter Mixing Rules:

✅ ALLOWED:

  • Named: wheels dbmigrate remove table name=users

  • Flag: wheels dbmigrate remove table --name=users

❌ NOT ALLOWED:

  • Positional parameters: This command does not support positional parameters

Recommendation: Use named parameters: wheels dbmigrate remove table name=users

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 dbmigrate latest

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

Synopsis

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

Migration Execution

Each migration file must contain:

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

Add Column

Add Index

Modify Column

Best Practices

  1. Test migrations locally first

  2. Backup before production migrations

  3. Use transactions

  4. Make migrations reversible

Environment-Specific Migrations

Migrations can check environment:

Checking Migrations

Preview migrations before running:

Performance Considerations

For large tables:

Continuous Integration

Add to CI/CD pipeline:

Rollback Strategy

If issues occur after migration:

  1. Use down migrations

  2. Restore from backup

  3. Fix and retry

    • Fix migration file

    • Run wheels dbmigrate latest

Common Issues

Timeout on Large Tables

Foreign Key Constraints

See Also

  • - Check migration status

  • - Run single migration

  • - Rollback migration

  • - Create migration

wheels db drop

⚠️ Note: This command depends on configuration values. Please verify your database configuration before executing it.

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

Option
Type
Default
Description

Examples:

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

  • Automatically handles active connections

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

Oracle

  • Drops USER/schema (Oracle's equivalent of a database)

  • Uses DROP USER ... CASCADE to remove all objects

  • Supports Oracle 12c+ with Container Database (CDB) architecture

  • Uses _ORACLE_SCRIPT session variable for non-C## users

  • Important: Database names cannot contain hyphens (use underscores)

  • Cannot drop system users (SYS, SYSTEM, ADMIN, XDB, etc.)

H2

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

  • Shows which files were deleted

  • No server connection required

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.

"Invalid Oracle identifier" (Oracle-specific)

Database name contains invalid characters. Oracle usernames can only contain letters, numbers, and underscores.

Fix: Use underscores instead of hyphens:

"ORA-01918: user does not exist" (Oracle-specific)

The Oracle user/schema doesn't exist. No action needed.

"ORA-28014: cannot drop administrative user" (Oracle-specific)

Attempting to drop an Oracle system user. System users like SYS, SYSTEM, ADMIN, XDB cannot be dropped.

Fix: Verify you're targeting the correct database. System users are protected and cannot be removed.

Related Commands

  • - Create a new database

  • - Drop and recreate database

  • - Backup before dropping

wheels destroy

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

Synopsis

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Positional parameters: wheels destroy user (resource name as positional)

  • Named parameters: name=value (e.g., name=user)

Parameter Mixing Rules:

✅ ALLOWED:

  • Positional: wheels destroy user

  • Named: wheels destroy name=user

❌ NOT ALLOWED:

  • Positional + named for same parameter: wheels destroy user name=other

Recommendation: Use positional parameter for simplicity: wheels destroy user

Note: This command always prompts for confirmation before proceeding. There is no --force flag to skip confirmation.

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

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/specs/models/[Name].cfc)

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

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

  • Resource route entry in /config/routes.cfm

  • Database table (if confirmed)

Examples

Basic destroy

This will prompt:

Using the short alias

Confirmation

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

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

Clean up after experimentation

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

  • - Generate resources

  • - Generate scaffolding

  • - Remove database tables

wheels dbmigrate create blank

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

Synopsis

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Positional parameters: wheels dbmigrate create blank addIndexes (name as positional)

  • Named parameters: name=value (e.g., name=addIndexes, description="Add indexes")

  • Flag parameters: --flag=value (e.g., --name=addIndexes)

Parameter Mixing Rules:

✅ ALLOWED:

  • Positional: wheels dbmigrate create blank addIndexes

  • All named: name=addIndexes description="Custom migration"

  • Positional + named: wheels dbmigrate create blank addIndexes description="Add indexes"

❌ NOT ALLOWED:

  • Mixing positional + named for same parameter: wheels dbmigrate create blank addIndexes name=other

Recommendation: Use positional for name, named for optional parameters: wheels dbmigrate create blank addIndexes description="My migration"

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)

--description

  • Type: String

  • Default: Empty

  • Description: Add a description comment to the migration file

Examples

Create a basic empty migration

Create migration with description

Generated File Structure

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

Use Cases

Custom Database Operations

For complex operations not covered by other generators:

Data Migrations

When you need to migrate data, not just schema:

Multi-Step Operations

For migrations requiring multiple coordinated changes:

Database-Specific Features

For database-specific features not abstracted by Wheels:

Best Practices

1. Descriptive Names

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

2. Implement Both Methods

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

3. Use Transactions

Wrap operations in transactions for atomicity:

4. Add Comments

Document complex operations:

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, columnNames, unique, indexName) - 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

  • - Create a table migration

  • - Create a column migration

  • - Run migrations

  • - Rollback migrations

  • - View migration status

wheels dbmigrate create table

Generate a migration file for creating a new database table.

Synopsis

Alias: wheels db create table

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Named parameters: name=value (e.g., name=users, primaryKey=userId)

  • Flag parameters: --flag equals flag=true (e.g., --force equals force=true)

  • Flag with value: --flag=value equals flag=value (e.g., --id=false)

Parameter Mixing Rules:

✅ ALLOWED:

  • All named: name=users primaryKey=userId

  • Named + flags: name=users --force --id=false

❌ NOT ALLOWED:

  • Positional parameters: This command does not support positional parameters

Recommendation: Use named for required parameters, flags for booleans: name=users --force

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

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

Create table without ID column

Create table with custom primary key

Force creation (overwrite existing)

Generated Migration Example

For the command:

Generates a migration file that you can customize:

Use Cases

Standard Entity Table

Create a typical entity table:

Join Table for Many-to-Many

Create a join table without primary key:

Table with Custom Primary Key

Create a table with non-standard primary key:

Best Practices

1. Use Singular Table Names

Wheels conventions expect singular table names:

2. Edit Migration Files

After generating the migration, edit it to add columns:

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:

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

  • - Add columns to existing table

  • - Create custom migration

  • - Create table removal migration

  • - Run migrations

  • - View migration status

wheels plugin outdated

Check for outdated Wheels plugins that have newer versions available on ForgeBox.

Usage

Parameters

Parameter
Required
Type
Options
Default
Description

Description

The plugins outdated command checks all installed plugins in the /plugins folder against ForgeBox to identify which ones have updates available. It performs real-time version checks and displays the results in a clear, formatted output.

Features

  • Checks only cfwheels-plugins type packages

  • Real-time version checking via ForgeBox

  • Color-coded status indicators

  • Detailed version comparison

  • Helpful update commands

Examples

Check for outdated plugins

Output (with outdated plugins):

Output (all up to date):

Output (with errors):

Multiple outdated plugins

Output:

Export as JSON

Output:

Status Indicators

During checking, each plugin displays:

  • [OUTDATED] (yellow) - Newer version available

  • [OK] (green) - Already at latest version

  • [ERROR] (red) - Could not check version (network issue, plugin not on ForgeBox, etc.)

How It Works

  1. Plugin Discovery: Scans /plugins folder for installed plugins

  2. Version Query: Uses forgebox show command for each plugin to get latest version

  3. Version Comparison: Cleans and compares version strings (strips non-numeric characters)

  4. Display Results: Shows outdated plugins with current and latest versions

  5. Update Suggestions: Provides appropriate update commands

Version Comparison

The command performs string-based version comparison after cleaning:

  • Removes non-numeric characters except dots (e.g., "v0.0.4" becomes "0.0.4")

  • Compares cleaned versions for equality

  • Marks as outdated if versions differ

Update Strategies

Update Single Plugin

Update All Outdated Plugins

Error Handling

Network Issues

If ForgeBox cannot be reached, the plugin is marked with [ERROR] and listed separately.

No Plugins Installed

Notes

  • Only checks plugins from /plugins folder (not box.json dependencies)

  • Only works with cfwheels-plugins type packages

  • Requires internet connection to query ForgeBox

  • Version check is performed in real-time (not cached)

  • Plugins are checked sequentially with status updates

  • Use wheels plugin update:all to update all outdated plugins at once

  • Dynamic table formatting adjusts column widths based on content

See Also

  • - Update a single plugin

  • - Update all plugins

  • - List installed plugins

  • - Show plugin details

Switching Environments

Environments that match your development stages.

Wheels allows you to set up different environments that match stages in your development cycle. That way you can configure different values that match what services to call and how your app behaves based on where you are in your development.

The Development environment is the most convenient one to use as you start building your application because it does not cache any data. Therefore, if you make any changes to your controllers and actions, for example, it will immediately be picked up by Wheels.

Other environment modes cache this information in order to speed up your application as much as possible. Making changes to the database in most of these modes will cause Wheels to throw an error. (Although that can be avoided with a reload call. More on that later.)

The fastest environment mode in terms of page load time is the Production mode. This is what you should set your application to run in before you launch your website.

By default, all new applications will start in the Development environment which is middle-of-the-road in terms of convenience versus speed.

The 4 Environment Modes

Besides the 2 environments mentioned above, there are 2 more. Let's go through them all one by one so you can see the differences between them and choose the most appropriate one given your current stage of development.

Development

  • Shows friendly Wheels specific errors as well as regular ColdFusion errors on screen.

  • Does not email you when an error is encountered.

  • Caches controller and model initialization (the config() methods).

  • Caches the database schema.

  • Caches routes.

  • Caches image information.

Production

  • Caches everything that the Development mode caches.

  • Activates all developer controlled caching (actions, pages, queries and partials).

  • Shows your custom error page when an error is encountered.

  • Shows your custom 404 page when a controller or action is not found.

  • Sends an email to you when an error is encountered.

Testing

  • Same caching settings as the Production mode but using the error handling of the Development mode. (Good for testing an application at its true speed while still getting errors reported on screen.)

Maintenance

  • Shows your custom maintenance page unless the requesting IP address or user agent is in the exception list (set by calling set(ipExceptions="127.0.0.1") in /config/settings.cfm or passed along in the URL as except=127.0.0.1, or as except=myuseragentstring to match against the user agent instead. Please note that if passing an exception on the URL using the except parameter, you must also provide the password parameter if a reload password has been defined. This eliminates the possibility of a rogue actor breaking out of maintenance mode by simply adding an except to the URL.

This environment mode comes in handy when you want to briefly take your website offline to upload changes or modify databases on production servers.

How to Switch Modes

Wheels provides multiple ways to switch between environments. Choose the method that best fits your workflow:

Method 1: Using the CLI Command (Recommended)

The easiest and most reliable way to switch environments is using the Wheels CLI command:

This command will:

  1. Update the set(environment="...") setting in /config/environment.cfm

  2. Automatically reload the application to apply the changes

  3. Validate that the target environment exists before switching

Examples:

Method 2: Manual File Editing

If you prefer not to use the CLI command, you can manually change the environment by editing the /config/environment.cfm file:

Available environment values:

  • development - For local development with full debugging

  • production - For live production servers with caching enabled

  • testing - For automated testing with production-like caching

  • maintenance - For temporarily taking your site offline

After manually editing the file, you must reload the application using one of these methods:

Option A: URL-Based Reload

Issue a reload request by passing reload as a URL parameter:

This tells Wheels to reload the entire framework (including your /app/events/onapplicationstart.cfm file), picking up any changes made in the /config/environment.cfm file.

Option B: Lazy URL Reloading

For quick testing, you can temporarily switch environments without editing any files:

This will make Wheels skip your /config/environment.cfm file and use the URL value instead (testing in this case). Note: This is temporary and will revert to the configured environment on the next application restart.

Option C: Server Restart

Alternatively, you can restart the ColdFusion/Lucee service to reload the application and apply environment changes.

Password-Protected Reloads

When using URL-based reload methods (Options A and B above), you should add protection by setting the reloadPassword variable in /config/settings.cfm:

When a password is set, reload requests must include the password parameter:

⚠️ WARNING: Always use a reload password in production

You really don't want random users hitting ?reload=development on a production server, as it could expose sensitive data about your application and error messages. Always set a reload password for production environments!

Note: The wheels env switch CLI command bypasses URL-based reloads and directly updates the configuration file, so it's not affected by the reload password setting.

Disabling Environment Switching

If you're deploying to a container based environment, or one that you know you'll never want to switch out of production mode, you can disable URL based environment switching completely via: set(allowEnvironmentSwitchViaUrl = false);

This is just an additional check to ensure that your production mode acts in the way you expect! Application reloading is still allowed, but the configuration can not be altered.

IP-Based Debug Access in Non-Development Environments

By default, Wheels only enables the debug GUI (wheels interface) and debug information in the development environment. However, there may be situations where you need access to these debugging tools in other environments like testing, production, or maintenance - but only for specific IP addresses.

Wheels provides IP-based access control for the debug GUI and debug information through two configuration settings:

Configuration Settings

Add these settings to your environment-specific configuration files (e.g., /config/production/settings.cfm):

How It Works

When these settings are configured:

  1. In the development environment, the debug GUI is always accessible regardless of IP address.

  2. In other environments (testing, production), the debug GUI and debug information will only be enabled for requests coming from IP addresses listed in the debugAccessIPs array.

  3. If a request comes from an IP address not in the list, the debug GUI and debug information remain disabled, maintaining security in production environments.

Example Configuration

Here's a typical setup for different environments:

Development (/config/development/settings.cfm):

Testing (/config/testing/settings.cfm):

Production (/config/production/settings.cfm):

This feature allows administrators to access debugging tools in production or other environments while keeping them secure from regular users, providing a flexible way to troubleshoot issues in all environments without compromising security.

wheels env switch production
# Switch to production environment
wheels env switch production

# Switch to testing environment
wheels env switch testing

# Switch to development environment
wheels env switch development
// /config/environment.cfm
<cfscript>
    // Change this value to your desired environment
    set(environment="production");
</cfscript>
http://www.mysite.com/?reload=true
http://www.mysite.com/?reload=testing
// /config/settings.cfm
<cfscript>
    set(reloadPassword="mySecurePassword");
</cfscript>
http://www.mysite.com/?reload=true&password=mySecurePassword
http://www.mysite.com/?reload=testing&password=mySecurePassword
// Define allowed IP addresses that can access debug features
set(debugAccessIPs = ["203.0.113.45", "198.51.100.22", "127.0.0.1"]);

// Enable IP-based debug access control
set(allowIPBasedDebugAccess = true);
// Debug GUI is enabled by default in development, no additional settings needed
set(debugAccessIPs = ["127.0.0.1", "192.168.1.100"]);
set(allowIPBasedDebugAccess = true);
set(debugAccessIPs = ["10.0.0.5"]); // Only specific admin IPs
set(allowIPBasedDebugAccess = true);
wheels dbmigrate latest
+-----------------------------------------+-----------------------------------------+
| Datasource:                       myApp | Total Migrations:                     4 |
| Database Type:                       H2 | Available Migrations:                 0 |
|                                         | Current Version:         20250812161449 |
|                                         | Latest Version:          20250812161449 |
+-----------------------------------------+-----------------------------------------+
+----------+------------------------------------------------------------------------+
| migrated | 20250812161449_cli__create_reporting_procedures                        |
| migrated | 20250812161302_cli__example_1                                          |
| migrated | 20250812161250_cli__example_2                                          |
| migrated | 20250812154338_cli__example_3                                          |
+----------+------------------------------------------------------------------------+
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
        }
    }

}
function up() {
    transaction {
        t = createTable("products");
        t.string("name");
        t.decimal("price");
        t.timestamps();
        t.create();
    }
}
function up() {
    transaction {
        addColumn(table="users", columnNames="email", type="string");
    }
}
function up() {
    transaction {
        addIndex(table="users", columnNames="email", unique=true);
    }
}
function up() {
    transaction {
        changeColumn(table="products", columnNames="price", type="decimal", precision=10, scale=2);
    }
}
# Test on development database
wheels dbmigrate latest

# Verify
wheels dbmigrate info
# Backup database
mysqldump myapp_production > backup.sql

# Run migrations
wheels dbmigrate latest
function up() {
    transaction {
        // All changes here
    }
}
function down() {
    transaction {
        dropTable("products");
    }
}
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]')");
        }
    }
}
# Check what would run
wheels dbmigrate info

# Review migration files
ls app/migrator/migrations/
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");
        }
    }
}
# .github/workflows/deploy.yml
- name: Run migrations
  run: |
    wheels dbmigrate latest
    wheels test app
wheels dbmigrate down
wheels dbmigrate down
mysql myapp_production < backup.sql
function up() {
    // Increase timeout for large operations
    setting requestTimeout="300";
    
    transaction {
        // Long running operation
    }
}
function up() {
    transaction {
        // Disable checks temporarily
        sql("SET FOREIGN_KEY_CHECKS=0");
        
        // Make changes
        dropTable("orders");
        
        // Re-enable
        sql("SET FOREIGN_KEY_CHECKS=1");
    }
}
wheels dbmigrate info
wheels dbmigrate up
wheels dbmigrate down
wheels dbmigrate create blank
wheels db drop [--datasource=<name>] [--environment=<env>] [--database=<dbname>] [--force]

datasource

string

Current datasource

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

environment

string

Current environment

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

database

string

wheels_dev

Specify the database name to drop. Note for Oracle: Database names cannot contain hyphens. Use underscores instead (e.g., myapp_dev not myapp-dev).

force

boolean

false

Skip the confirmation prompt. Useful for scripting.

# Use specific datasource
wheels db drop --datasource=myapp_dev

# Specify environment
wheels db drop --environment=testing

# Custom database name
wheels db drop --database=myapp_test

# Force drop without confirmation
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
# Wrong
wheels db drop --database=my-app-dev

# Correct
wheels db drop --database=my_app_dev
wheels db create
wheels db reset
wheels db dump
wheels destroy <name>
wheels d <name>

name

Name of the resource to destroy

Yes

# Positional (recommended)
wheels destroy user

# OR named
wheels destroy name=user

# OR alias (positional)
wheels d user
================================================
= 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/specs/models/User.cfc
/tests/specs/controllers/Users.cfc
/tests/specs/views/users/
/config/routes.cfm
.resources("users")

Are you sure? [y/n]
# Positional with alias (recommended)
wheels d product

# OR named with alias
wheels d name=product
================================================
= 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/specs/models/User.cfc
/tests/specs/controllers/Users.cfc
/tests/specs/views/users/
/config/routes.cfm
.resources("users")

Are you sure? [y/n]
# Generated the wrong name
wheels generate resource prduct  # Oops, typo!
wheels destroy prduct            # Remove it
wheels generate resource product # Create correct one
# Try out a feature
wheels generate scaffold blog_post title:string,content:text
# Decide you don't want it
wheels destroy blog_post
wheels generate resource
wheels generate scaffold
wheels dbmigrate remove table
wheels dbmigrate create blank <name>
# Positional (recommended)
wheels dbmigrate create blank add_custom_indexes

# OR flag syntax
wheels dbmigrate create blank --name=add_custom_indexes

# OR named
wheels dbmigrate create blank name=add_custom_indexes
# Positional + named (recommended)
wheels dbmigrate create blank update_user_permissions description="Add role-based permissions to users"

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

# OR all named
wheels dbmigrate create blank name=update_user_permissions description="Add role-based permissions to users"
component extends="wheels.migrator.Migration" hint="<description>" {

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

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

}
# 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
# Create data migration
wheels dbmigrate create blank --name=normalize_user_emails

# Edit to add data transformation logic
# Example: lowercase all email addresses
# 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
# Create migration for PostgreSQL-specific features
wheels dbmigrate create blank --name=add_json_columns

# Edit to use PostgreSQL JSON operations
# Good
wheels dbmigrate create blank --name=add_user_authentication_tokens

# Bad
wheels dbmigrate create blank --name=update1
function up() {
    transaction {
        execute("CREATE INDEX idx_users_email ON users(email)");
    }
}

function down() {
    transaction {
        execute("DROP INDEX idx_users_email");
    }
}
function up() {
    transaction {
        // All operations succeed or all fail
        createTable("new_table");
        execute("INSERT INTO new_table SELECT * FROM old_table");
        dropTable("old_table");
    }
}
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
        ");
    }
}
wheels dbmigrate create table
wheels dbmigrate create column
wheels dbmigrate up
wheels dbmigrate down
wheels dbmigrate info
wheels dbmigrate create table name=<table_name> [--force] [--id] primaryKey=<key_name>

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

primaryKey

string

No

"id"

Overrides the default primary key column name

# Named parameter (required)
wheels dbmigrate create table name=users
# Named + flag (recommended)
wheels dbmigrate create table name=user_roles --id=false

# OR all named
wheels db create table name=user_roles id=false
# Named parameters (recommended)
wheels dbmigrate create table name=products primaryKey=productCode
# Named + flag (recommended)
wheels dbmigrate create table name=users --force

# OR all named
wheels db create table name=users force=true
wheels dbmigrate create table name=users
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(columnNames="name");
            // t.integer(columnNames="age");
            t.timestamps();
            t.create();
        }
    }

    function down() {
        transaction {
            dropTable("users");
        }
    }

}
# Generate the migration
wheels dbmigrate create table name=customer

# Then edit the migration file to add columns
wheels dbmigrate create table name=products_categories --id=false
wheels dbmigrate create table name=legacy_customer primaryKey=customer_code
# 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
// In the generated migration file
t = createTable(name="orders", force=false, id=true, primaryKey="id");
t.integer(columnNames="customer_id");
t.decimal(columnNames="total", precision=10, scale=2);
t.string(columnNames="status", default="pending");
t.timestamps();
t.create();
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");
        }
    }
}
wheels dbmigrate create column
wheels dbmigrate create blank
wheels dbmigrate remove table
wheels dbmigrate up
wheels dbmigrate info
wheels plugin outdated [--format=<format>]

format

No

string

table, json

table

Output format for outdated plugin list

wheels plugin outdated
===========================================================
  Checking for Plugin Updates
===========================================================

  bcrypt                                  [OUTDATED] 0.0.3 -> 0.0.4
  shortcodes                              [OK] v0.0.4
  wheels-test                             [OK] v1.0.0

===========================================================

Found 1 outdated plugin:

Plugin              Current     Latest
-----------------------------------------------
bcrypt              0.0.3       0.0.4

-----------------------------------------------------------

Commands:

  wheels plugin update bcrypt
===========================================================
  Checking for Plugin Updates
===========================================================

  bcrypt                                  [OK] v0.0.4
  shortcodes                              [OK] v0.0.4
  wheels-test                             [OK] v1.0.0

===========================================================

[OK] All plugins are up to date!
===========================================================
  Checking for Plugin Updates
===========================================================

  bcrypt                                  [OK] v0.0.4
  problematic-plugin                      [ERROR] Could not check version
  shortcodes                              [OK] v0.0.4

===========================================================

[OK] All plugins are up to date!

Could not check 1 plugin:

  - problematic-plugin
wheels plugin outdated
===========================================================
  Checking for Plugin Updates
===========================================================

  bcrypt                                  [OUTDATED] 0.0.3 -> 0.0.4
  shortcodes                              [OUTDATED] 0.0.3 -> 0.0.4
  wheels-test                             [OK] v1.0.0

===========================================================

Found 2 outdated plugins:

Plugin              Current     Latest
-----------------------------------------------
bcrypt              0.0.3       0.0.4
shortcodes          0.0.3       0.0.4

-----------------------------------------------------------

Commands:

Update all outdated plugins:
  wheels plugin update:all

Update specific plugin:
  wheels plugin update <plugin-name>
wheels plugin outdated --format=json
{
  "outdated": [
    {
      "name": "bcrypt",
      "slug": "cfwheels-bcrypt",
      "currentVersion": "0.0.3",
      "latestVersion": "0.0.4"
    },
    {
      "name": "shortcodes",
      "slug": "cfwheels-shortcodes",
      "currentVersion": "0.0.3",
      "latestVersion": "0.0.4"
    }
  ],
  "count": 2,
  "errors": []
}
wheels plugin update bcrypt
wheels plugin update:all
===========================================================
  Checking for Plugin Updates
===========================================================

No plugins installed in /plugins folder
Install plugins with: wheels plugin install <plugin-name>
wheels plugin update
wheels plugin update:all
wheels plugin list
wheels plugin info

Boxlang Support

Wheels supports BoxLang 1.x, providing developers with a modern, high-performance CFML runtime. You can run BoxLang applications using either CommandBox or BoxLang Mini-Server.

Prerequisites

  • Java 21 or higher installed on your system

  • Wheels application (generated or existing)

  • BoxLang runtime (see installation options below)

Method 1: Using CommandBox (Recommended)

CommandBox provides the easiest and most feature-rich way to run BoxLang applications with Wheels.

Installation and Setup

# Install BoxLang engine in CommandBox
box install boxlang

# Start server with BoxLang
box server start cfengine=boxlang

# Or specify specific BoxLang version (optional)
box server start [email protected]

BoxLang Module Dependencies

BoxLang requires specific modules for full Wheels compatibility. These dependencies should be added to your box.json file:

{
  "dependencies": {
    "bx-compat-cfml": "^1.27.0+35",
    "bx-csrf": "^1.2.0+3", 
    "bx-esapi": "^1.6.0+9",
    "bx-image": "^1.0.1",
    "bx-wddx":"^1.5.0+8",
    "bx-mysql": "^1.0.1+7"
  }
}

Installing Dependencies

# Install all dependencies from box.json
box install

# Or install specific BoxLang modules individually
box install bx-compat-cfml
box install bx-csrf
box install bx-esapi
box install bx-image
box install bx-wddx
box install bx-mysql

Module Descriptions

  • bx-compat-cfml - CFML compatibility layer for BoxLang

  • bx-csrf - Cross-Site Request Forgery protection

  • bx-esapi - Enterprise Security API for input validation

  • bx-image - Image manipulation functionality

  • bx-wddx - Web Distributed Data eXchange (WDDX) conversion

  • bx-mysql - MySQL database driver

Additional Database Support

For other databases supported by Wheels, install the corresponding BoxLang modules:

  • Microsoft SQL Server: box install bx-mssql

  • PostGreSQL Server: box install bx-postgresql

  • Oracle Server: box install bx-oracle

Finding Additional Modules

For any additional functionality or database drivers not included in the core dependencies:

  • Browse ForgeBox: Visit forgebox.io

  • Search for BoxLang modules: Look for modules with bx- prefix

  • Copy install command: Each module page provides the exact box install command

  • Install the module: Run the command in your project directory

Example: For Microsoft SQL Server support, visit the bx-mssql module page on ForgeBox and copy the installation command.

Server Configuration

Create a server.json file in your application root to persist BoxLang settings:

{
    "name":"wheels",
    "web":{
        "host":"localhost",
        "http":{
            "port":3000
        },
        "webroot":"public",
        "rewrites":{
            "enable":true,
            "config":"public/urlrewrite.xml"
        }
    },
    "app": {
        "cfengine": "boxlang"
    }
}

Development Workflow

# Generate new Wheels app (if needed)
wheels g app myapp --engine=boxlang

# Navigate to app directory
cd myapp

# Install BoxLang dependencies
box install

# Start BoxLang development server  
server start cfengine=boxlang

# Access your application at http://localhost:8080

Method 2: Using BoxLang Mini-Server

BoxLang Mini-Server provides a lightweight, standalone option perfect for minimal setups or specific deployment scenarios.

Installation

BoxLang Mini-Server can be downloaded directly from the official BoxLang releases. The latest version is fully compatible with Wheels.

Download the latest BoxLang Mini-Server:

# Download the latest BoxLang Mini-Server JAR file
curl -LO https://downloads.ortussolutions.com/ortussolutions/boxlang-runtimes/boxlang-miniserver/boxlang-miniserver-latest.jar

Installation Steps

  1. Download BoxLang Mini-Server Package (optional, for additional files)

    # Download complete package with additional files
    curl -LO https://downloads.ortussolutions.com/ortussolutions/boxlang-runtimes/boxlang-miniserver/boxlang-miniserver-latest.zip
    unzip boxlang-miniserver.zip
  2. Prepare Your Application Structure

    your-wheels-app/
    ├── config/           # Configuration files
    ├── app/             # Controllers, models, views
    ├── public/          # Web-accessible files
    │   ├── index.bxm    # BoxLang entry point (required)
    │   ├── stylesheets/
    │   ├── javascripts/
    │   └── images/
    └── vendor/wheels/   # Wheels framework files
  3. Setup BoxLang Entry Point

    Create an index.bxm file in your public/ folder with the following content:

    <bx:script>
    // BoxLang rewrite handler for Wheels
    // This file handles URL rewriting for BoxLang compatibility
    
    // Get the requested URI
    requestURI = cgi.request_uri ?: "";
    
    // Remove query string for pattern matching  
    if (find("?", requestURI)) {
        requestURI = listFirst(requestURI, "?");
    }
    
    // Handle requests that come through /index.bxm/path - redirect to clean URL
    if (find("/index.bxm/", requestURI)) {
        cleanPath = replace(requestURI, "/index.bxm", "");
        queryString = cgi.query_string ?: "";
        redirectURL = cleanPath;
        if (len(queryString)) {
            redirectURL &= "?" & queryString;
        }
        bx:header name="Location" value=redirectURL;
        bx:header statusCode=301;
        bx:abort;
    }
    
    // Static paths that should not be rewritten (based on urlrewrite.xml)
    staticPaths = "cf_script,flex2gateway,jrunscripts,CFIDE,lucee,cfformgateway,cffileservlet,files,images,javascripts,miscellaneous,stylesheets,wheels/public/assets";
    specialFiles = "robots.txt,favicon.ico,sitemap.xml,index.cfm";
    
    // Check if this is a static path or special file
    isStatic = false;
    
    if (len(requestURI) && requestURI != "/") {
        cleanPath = right(requestURI, len(requestURI) - 1); // Remove leading slash
        
        // Check special files first
        fileName = listLast(requestURI, "/");
        if (listFindNoCase(specialFiles, fileName)) {
            isStatic = true;
        }
        
        // Check static paths
        if (!isStatic) {
            for (staticPath in listToArray(staticPaths)) {
                if (left(cleanPath, len(staticPath)) == staticPath) {
                    isStatic = true;
                    break;
                }
            }
        }
        
        // Check file extensions for static files
        if (!isStatic && listLen(cleanPath, ".") > 1) {
            extension = listLast(cleanPath, ".");
            staticExtensions = "js,css,png,jpg,jpeg,gif,ico,pdf,zip,txt,xml,json";
            if (listFindNoCase(staticExtensions, extension)) {
                isStatic = true;
            }
        }
    }
    
    // If it's a static file, let it pass through
    if (isStatic) {
        bx:header statusCode=404;
        writeOutput("File not found");
        bx:abort;
    }
    
    // Set up CGI variables for clean URL generation
    if (len(requestURI) && requestURI != "/") {
        cgi.path_info = requestURI;
    }
    
    // Override script name for clean URL generation
    cgi.script_name = "/index.cfm";
    
    // Clean up request URI
    if (find("/index.bxm", cgi.request_uri ?: "")) {
        cgi.request_uri = replace(cgi.request_uri, "/index.bxm", "");
    }
    
    // Update request scope for Wheels compatibility
    if (structKeyExists(request, "cgi")) {
        request.cgi.script_name = "/index.cfm";
        if (structKeyExists(request.cgi, "request_uri") && find("/index.bxm", request.cgi.request_uri)) {
            request.cgi.request_uri = replace(request.cgi.request_uri, "/index.bxm", "");
        }
    }
    </bx:script>
    
    <!--- Include the main Wheels bootstrap file --->
    <bx:include template="index.cfm" />

    This file serves as the BoxLang-specific entry point that handles URL rewriting and bootstraps your Wheels application.

Starting BoxLang Mini-Server

Basic Command

java -jar /path/to/boxlang-miniserver-1.6.0.jar \
  --webroot /path/to/your/app/public \
  --rewrite

Full Configuration Example

java -jar /path/to/boxlang-miniserver-1.6.0.jar \
  --webroot /path/to/your/app/public \
  --host 127.0.0.1 \
  --port 8080 \
  --rewrite \
  --debug

For Wheels Template Structure

If using the Wheels base template structure:

java -jar /path/to/boxlang-miniserver-1.6.0.jar \
  --webroot /path/to/your/app/templates/base/src/public \
  --rewrite \
  --port 8080

Mini-Server Command Options

Option
Description
Default

--webroot

Document root directory (required)

None

--host

IP address to bind to

0.0.0.0

--port

Port number

8080

--rewrite

Enable URL rewriting (recommended for Wheels)

false

--debug

Enable debug mode

false

--config

Path to configuration file

None

--libs

Additional library paths

None

You can read the further details from the boxlang mini-server documentation

Troubleshooting

Common Issues

  1. Missing BoxLang Dependencies (CommandBox)

    • Problem: Functions or features not working, missing module errors

    • Solution: Ensure all required BoxLang modules are installed: box install

    • Check: Verify box.json contains all required bx-* dependencies

  2. Missing index.bxm File (Mini-Server)

    • Problem: Server returns 404 or directory listing

    • Solution: Create index.bxm in your public/ folder using the complete file content provided above in the Setup steps

  3. URL Routing Not Working

    • Problem: Routes return 404 errors

    • Solution: Always include the --rewrite flag when starting Mini-Server

  4. Version Compatibility Issues

    • Problem: Unexpected errors or features not working

    • Solution: Verify you're using a recent version of BoxLang 1.x

  5. Path Resolution Problems

    • Problem: Files not found or incorrect paths

    • Solution: Use absolute paths to avoid directory resolution issues

Testing Your Setup

# Verify server is responding
curl http://localhost:8080

# Test Wheels is loading
curl http://localhost:8080/wheels

# Check specific routes
curl http://localhost:8080/say/hello

Recommendation

For most developers, CommandBox with BoxLang provides the best experience with automatic updates, dependency management, and integrated tooling. Use BoxLang Mini-Server for specialized deployment scenarios or minimal footprint requirements.

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

Command Reference

Complete reference for all CLI commands organized by category:

  • Core Commands - Essential commands like init, reload.

  • Code Generation - Generate applications, models, controllers, views

  • Database Commands - Complete database management and migrations

  • Testing Commands - Run tests and generate coverage

  • Configuration - Manage application settings

  • And more...

Quick Start Guide

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

  • Service Architecture - Understand the CLI's architecture

  • Creating Custom Commands - Extend the CLI with your own commands

  • Template System - Customize code generation templates

  • Testing Guide - Write and run tests effectively

Best Practices

  • Migration Guide - Database migration best practices

  • Security Guide - Security scanning and hardening

  • Performance Guide - Optimization techniques

Reference

  • Configuration Options - All available configuration settings

  • Template Variables - Variables available in templates

  • Exit Codes - Understanding command exit codes

  • Environment Variables - Environment configuration

Key Features

Code Generation

Generate complete applications or individual components:

# 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 Management

Complete database lifecycle management:

# 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

Testing

Comprehensive testing support:

# 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

Development Tools

Enhance your development workflow:


# Reload application
wheels reload development

# Analyze code
wheels analyze code
wheels security scan

Getting Started

  1. Install CommandBox (if not already installed):

    # macOS/Linux
    curl -fsSl https://downloads.ortussolutions.com/debs/gpg | sudo apt-key add -
    or
     brew install commandbox
    
    # Windows
    choco install commandbox
  2. Install Wheels CLI:

    box install wheels-cli
  3. Create Your First App:

    wheels new myapp
    cd myapp
    box server start
  4. Explore Commands:

    wheels --help
    wheels generate --help
    wheels dbmigrate --help

Version Compatibility

Wheels CLI
Wheels
CommandBox
CFML Engine

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+

Community & Support

  • Documentation: https://wheels.dev/docs

  • GitHub: https://github.com/wheels-dev/wheels

  • Slack: CFML Slack - #wheels channel

  • Forums: https://groups.google.com/forum/#!forum/wheels

Contributing

We welcome contributions! See our Contributing Guide 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

  • All Commands - Complete command reference

  • Quick Start - Get started in minutes

  • Creating Commands - Extend the CLI

  • Service Architecture - Technical deep dive

  • Testing Guide - Testing best practices

License

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


Ready to get started? Head to the Quick Start Guide or explore the Command Reference.

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:
  cbvalidation @ ^4.6.0+28 (Production) - Not Installed
  wirebox @ ^7.4.2+24 (Production) - Not Installed
  shortcodes @ ^0.0.4 (Production) - Not Installed

Dev Dependencies:
  testbox @ ^6.4.0+17 (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 (saves to devDependencies)
wheels deps install testbox --dev

Important: The --dev flag uses CommandBox's --saveDev flag internally, ensuring packages are correctly saved to the devDependencies section of box.json.

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-09-19 11:38:44
Wheels Version: 3.0.0-SNAPSHOT
CFML Engine: Lucee 5.4.6.9

Dependencies:
  cbvalidation @ ^4.6.0+28 - Installed: No
  shortcodes @ ^0.0.4 - Installed: No
  wirebox @ ^7.4.2+24 - Installed: No

Dev Dependencies:
  testbox @ ^6.4.0+17 - Installed: Yes

Checking for outdated packages...
┌────────────────┬───────────┬──────────┬──────────┬─────────────────────┐
│ Package        │ Installed │ Update   │ Latest   │ Location            │
├────────────────┼───────────┼──────────┼──────────┼─────────────────────┤
│ testbox@^6.4.. │ 6.4.0+17  │ 6.4.0+17 │ 6.4.0+17 │ /testbox            │
└────────────────┴───────────┴──────────┴──────────┴─────────────────────┘

There are no outdated dependencies!

Full report exported to: dependency-report-20250919-113851.json

Integration with CommandBox

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

  • install uses box install with --saveDev flag for dev dependencies

  • 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": "^6",
    "commandbox-cfformat": "*"
  }
}

Installation Status

The command checks for installed packages using the installPaths defined in box.json to determine actual installation locations. This ensures accurate detection regardless of where packages are installed:

Detection Method:

  1. Primary: Checks the exact path specified in box.json → installPaths

  2. Fallback: Checks standard locations like /modules directory

Supported Package Formats:

  • Simple names: wirebox

  • Namespaced: forgebox:wirebox

  • Versioned: [email protected]

Example Install Paths:

"installPaths": {
    "testbox": "testbox/",
    "wirebox": "wirebox/",
    "cbvalidation": "modules/cbvalidation/",
    "shortcodes": "plugins/Shortcodes/"
}

The installation status reflects the physical presence of packages on the filesystem, not just their listing in box.json dependencies.

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

Directory Structure

A comprehensive guide to the Wheels directory structure...

Understanding the Wheels directory structure is essential whether you're building applications or contributing to the framework itself.

Two Structures for Different Workflows

Wheels uses different directory layouts depending on your role:

Application Development: A streamlined structure optimized for building applications, available through ForgeBox or the CLI.

Framework Development: A comprehensive monorepo structure that includes development tools, tests, and documentation for framework contributors.

Both structures serve specific purposes, and understanding each will help you work more effectively with Wheels.


Application Development Structure

When you create a new Wheels application using wheels new or download from ForgeBox, you'll work with this focused project structure:

app/
  controllers/
    Controller.cfc
  events/
  global/
  migrator/
    migrations/
  models/
    Model.cfc
  plugins/
  views/
config/
public/
  files/
  images/
  javascripts/
  stylesheets/
  miscellaneous/
    Application.cfc
  urlrewrite.xml
  Application.cfc
  index.cfm
tests/
  TestBox/
vendor

Core Application Directories

app/controllers/ - Contains your controller files with a base Controller.cfc file already present. Place shared controller methods in Controller.cfc since all controllers inherit from this base class.

app/models/ - Houses your model files, typically one per database table. The existing Model.cfc file serves as the base class for all models and should contain shared model functionality.

app/views/ - Stores your view templates, organized by controller (e.g., views for the Users controller go in app/views/users/). This is where you prepare content for your users.

app/events/ - Contains event handlers that respond to ColdFusion application events, providing a cleaner alternative to placing code directly in Application.cfc.

app/global/ - Holds globally accessible functions available throughout your application.

plugins/ - Contains downloaded Wheels plugins that extend your application's functionality.

Configuration and Assets

config/ - All configuration changes should be made here. Set environments, routes, and other application settings. Individual setting files in subdirectories can override main configuration.

public/files/ - Files intended for delivery to users via the sendFile() function should be placed here. Also serves as general file storage.

public/images/ - Recommended location for image assets. While not required, Wheels functions involving images assume this conventional location.

public/javascripts/ - Recommended location for JavaScript files.

public/stylesheets/ - Recommended location for CSS files.

public/miscellaneous/ - Special directory for code that must run completely outside the framework. Contains an empty Application.cfc that prevents Wheels involvement. Ideal for Flash AMF binding or <cfajaxproxy> connections to CFCs.

System Files and Testing

app/migrator/migrations - Database migration CFC files.

tests/ - Location for your application's unit tests.

public/urlrewrite.xml - Required for Tomcat/Tuckey or CommandBox URL rewriting.

public/Application.cfc and public/index.cfm - Framework bootstrap files. Do not modify these files.


Framework Development Structure

Contributors working on the Wheels framework itself will encounter this comprehensive repository structure:

cli/
core/
  src/
    wheels/
design_docs/
docs/
examples/
templates/
  base/
    src/
      app/
      config/
      public/
      tests/
      vendor/
      .env
      box.json
      server.json
test-artifacts/
tests/
tools/
.cfformat.json
.editorconfig
CFConfig.json
CHANGELOG.md
compose.yml

Framework Development Components

cli/ - Source code for command-line interface tools including generators and database migration utilities.

core/src/wheels/ - The core Wheels framework code. This is the actual framework that gets distributed. When new versions are released, this directory often contains all necessary updates.

design_docs/ - Architecture documentation, design decisions, and planning materials explaining the framework's structural choices.

docs/ - Official documentation in Markdown format, synchronized with the public website at wheels.dev.

examples/ - Sample applications demonstrating various Wheels features, useful for testing framework changes in realistic scenarios.

/ - The exact application template structure that developers receive when creating new projects. This mirrors the application development structure described above and includes essential configuration files like box.json, server.json, and .env.

vendor/ - Third-party dependencies and packages used by the framework development environment. Contains libraries managed through CommandBox/ForgeBox package management.

test-artifacts/ - Files generated during test suite execution, typically excluded from version control.

tests/ - Complete TestBox test suite for framework validation and regression testing.

tools/ - Build scripts, Docker configurations, and development utilities for maintaining the framework.

Configuration files (.cfformat.json, .editorconfig, CFConfig.json) maintain consistent development standards across contributor environments.


Key Directory Relationships

Application Context

When working on applications, your primary focus areas are:

  • Configuration: config/ directory for all settings

  • Application Logic: app/controllers/, app/models/, app/migrator/migrations, app/views/, and app/events/

  • Static Assets: public/files/, public/images/, public/javascripts/, and public/stylesheets/

  • Extensions: plugins/ for third-party functionality

Framework Context

The / folder in the framework repository becomes the root directory of every new Wheels application. When contributing to the framework:

  • Work primarily in core/src/wheels/ for framework code

  • Update docs/ for documentation changes

  • Test changes using applications in / or examples/

  • Use tests/ for framework testing


Directory Customization

You can add additional directories to your application structure. When doing so:

  • Include a blank Application.cfc file in custom directories to prevent Wheels from processing requests in those locations

  • Follow the established naming conventions for consistency

  • Consider whether new directories belong in public/ (web-accessible) or app/ (application logic)


Guidelines for Contributors

Environment Setup - Use the provided compose.yml file to test changes across multiple CFML engines, ensuring broad compatibility.

Testing Requirements - Execute the complete test suite located in /tests before submitting pull requests to prevent regressions.

Code Standards - Follow the formatting rules defined in .cfformat.json. Most development environments can automatically apply these standards.

Documentation Updates - Update relevant documentation in /docs when adding features or modifying existing behavior.

CLI Development - When working on command-line tools, ensure corresponding documentation updates in /docs/command-line-tools.


Guidelines for Application Developers

Configuration First - Begin development by setting up routes, environments, and database connections in the config/ directory.

MVC Architecture - Organize code according to the Model-View-Controller pattern that the directory structure supports:

  • Controllers handle requests and coordinate between models and views

  • Models manage data and business logic

  • Views present information to users

Asset Organization - Use the conventional public/ subdirectories for different asset types. This ensures Wheels functions work as expected and maintains project organization.

Plugin Integration - Evaluate existing plugins in plugins/ before developing custom solutions.

Database Management - Use the migration system (wheels db migrate) rather than manual SQL scripts for database schema changes.

Package Management - Use CommandBox and the box.json file to manage dependencies. The vendor/ directory contains installed packages and should be excluded from version control.

Local Development - Configure your development environment using server.json for server settings and .env for environment variables. Never commit sensitive data in .env files to version control.

Testing Strategy - Implement unit tests in tests/ to ensure application reliability.


This directory structure reflects years of framework development and community feedback. Each directory serves a specific purpose that supports either application development or framework contribution. The clear separation between public assets, application logic, and configuration ensures maintainable and scalable Wheels applications.

asset management commands

The Wheels CLI provides commands for managing static assets in your application, including compilation, optimization, and cleanup of JavaScript, CSS, and image files.

Available Commands

wheels assets precompile
wheels assets clean
wheels assets clobber

Asset Commands

wheels assets precompile

Prepares your assets for production deployment by minifying and optimizing them.

Usage

# Basic precompilation (defaults to production)
wheels assets precompile

# Force recompilation of all assets
wheels assets precompile --force

# Target specific environments (with aliases)
wheels assets precompile --environment=production    # Full minification
wheels assets precompile --environment=prod          # Alias for production
wheels assets precompile --environment=staging       # Light minification
wheels assets precompile --environment=stage         # Alias for staging
wheels assets precompile --environment=testing       # Light minification
wheels assets precompile --environment=test          # Alias for testing
wheels assets precompile --environment=maintenance   # Light minification
wheels assets precompile --environment=development   # No minification
wheels assets precompile --environment=dev           # Alias for development

Environment-Specific Processing

The command applies different levels of asset optimization based on the target environment:

  • Production (production, prod): Full minification - Maximum compression, removes all comments and whitespace, optimizes code structure

  • Staging (staging, stage): Light minification - Removes comments and excessive whitespace but preserves some formatting for debugging

  • Testing (testing, test): Light minification - Same as staging, optimized for testing environments

  • Maintenance (maintenance): Light minification - Minimal processing for maintenance mode deployments

  • Development (development, dev): No minification - Preserves original formatting and comments for debugging

What it does

  • Minifies JavaScript files: Removes comments, whitespace, and unnecessary characters (level depends on environment)

  • Minifies CSS files: Removes comments, whitespace, and optimizes CSS rules (level depends on environment)

  • Generates cache-busted filenames: Adds MD5 hashes to filenames (e.g., application-a1b2c3d4.min.js)

  • Creates manifest.json: Maps original filenames to compiled versions

  • Processes images: Copies images with cache-busted names

  • Output location: Stores all compiled assets in /public/assets/compiled/

Generated Manifest Example

{
  "application.js": "application-a1b2c3d4.min.js",
  "admin.js": "admin-b2c3d4e5.min.js",
  "styles.css": "styles-c3d4e5f6.min.css",
  "admin.css": "admin-d4e5f6g7.min.css",
  "logo.png": "logo-e5f6g7h8.png",
  "banner.jpg": "banner-f6g7h8i9.jpg"
}

wheels assets clean

Removes old compiled assets while keeping recent versions for rollback capability.

Usage

# Clean old assets (keeps 3 most recent versions by default)
wheels assets clean

# Keep 5 versions of each asset
wheels assets clean --keep=5

# Preview what would be deleted without actually deleting
wheels assets clean --dryRun

What it does

  • Identifies old versions: Finds all compiled assets with hash fingerprints

  • Keeps recent versions: Retains the specified number of most recent versions (default: 3)

  • Removes old files: Deletes older versions to free disk space

  • Updates manifest: Ensures manifest.json remains current

  • Dry run option: Preview deletions without making changes

wheels assets clobber

Completely removes all compiled assets and the manifest file.

Usage

# Remove all compiled assets (with confirmation prompt)
wheels assets clobber

# Skip confirmation prompt
wheels assets clobber --force

What it does

  • Deletes compiled directory: Removes /public/assets/compiled/ and all contents

  • Removes manifest: Deletes the manifest.json file

  • Confirmation prompt: Asks for confirmation unless --force is used

  • Complete cleanup: Useful for fresh starts or troubleshooting

Best Practices

Production Deployment Workflow

  1. Before deployment:

# Compile assets for production
wheels assets precompile --environment=production

# Clean old versions to save space
wheels assets clean --keep=3
  1. After deployment verification:

# If rollback needed, previous versions are still available
# If deployment successful, further cleanup can be done
wheels assets clean --keep=2

Development Workflow

During development, you typically don't need compiled assets:

# Remove all compiled assets in development
wheels assets clobber --force

# Precompile only when testing production builds
wheels assets precompile --environment=development

Continuous Integration

Example CI/CD pipeline step:

# .github/workflows/deploy.yml
- name: Compile Assets
  run: |
    wheels assets precompile --environment=production
    wheels assets clean --keep=3

File Structure

After running wheels assets precompile:

/public/
  /assets/
    /compiled/
      manifest.json
      application-a1b2c3d4.min.js
      styles-e5f6g7h8.min.css
      logo-i9j0k1l2.png
    /javascripts/
      application.js (original)
    /stylesheets/
      styles.css (original)
    /images/
      logo.png (original)

Configuration

Configure asset handling in your Wheels application:

// config/settings.cfm

// Enable asset fingerprinting in production
set(useAssetFingerprinting = true);

// Set asset cache duration (in minutes)
set(assetsCacheMinutes = 1440); // 24 hours

// Define assets path
set(assetsPath = "/assets");

Troubleshooting

Assets not updating in production

# Force recompilation
wheels assets precompile --force

# Verify manifest exists and is current
cat public/assets/compiled/manifest.json

Disk space issues

# Check space used by compiled assets
du -sh public/assets/compiled/

# Aggressive cleanup - keep only 1 version
wheels assets clean --keep=1

# Or remove everything and recompile
wheels assets clobber --force
wheels assets precompile

Missing assets after deployment

# Ensure assets were compiled for the correct environment
wheels assets precompile --environment=production

# Check that manifest.json exists
ls -la public/assets/compiled/manifest.json

Notes

  • Backup: Always backup assets before running clobber in production

  • Version Control: Don't commit compiled assets to version control

  • Deployment: Run precompile as part of your deployment process

  • Performance: Compiled assets significantly improve load times

  • Cache Busting: Hash fingerprints ensure browsers load updated assets

wheels generate app

This command works correctly without options (parameters). Option support is under development and will be available soon.

Create a new Wheels application from templates.

Synopsis

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Positional parameters: wheels generate app blog (most common)

  • Named parameters: name=value (e.g., name=blog, template=HelloWorld)

  • Flag parameters: --flag equals flag=true (e.g., --useBootstrap equals useBootstrap=true)

Parameter Mixing Rules:

✅ ALLOWED:

  • All positional: wheels generate app blog

  • All positional + flags: wheels generate app blog --useBootstrap --init

  • All named: name=blog template=HelloWorld --useBootstrap

❌ NOT ALLOWED:

  • Positional + named: wheels generate app blog name=myapp (causes error)

Recommendation: Use positional for name/template, flags for options: wheels generate app blog --useBootstrap --init

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

Options

Option
Description
Default

Available Templates

wheels-base-template@BE (Default)

  • Backend Edition template

  • Complete MVC structure

  • Sample code and configuration

  • H2 database setup by default

HelloWorld

  • Simple "Hello World" example

  • One controller and view

  • Great for learning

HelloDynamic

  • Dynamic content example

  • Database interaction

  • Form handling

HelloPages

  • Static pages example

  • Layout system

  • Navigation structure

Examples

Create basic application

Create with custom template

Create in specific directory

Create with Bootstrap

Create with H2 database (default is true)

Create with all options

Generated Structure

Configuration Files

box.json

server.json

.wheels-cli.json

Database Setup

With H2 (Embedded)

  • 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:

  2. Configure in CommandBox:

Post-Generation Steps

  1. Navigate to directory

  2. Install dependencies

  3. Start server

  4. Open browser

Template Development

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

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

  • - Initialize existing application

  • - Interactive app creation

  • - Generate CRUD scaffolding

wheels analyze performance

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

Usage

Parameters

Parameter
Description
Default

Description

The analyze performance command monitors your Wheels application to identify performance bottlenecks and provide optimization recommendations. It tracks metrics in real-time and provides both console output and optional HTML reports.

What It Monitors

  • Request Performance: Response times, slow requests, controller/action patterns

  • Database Queries: Query execution times, slow queries, query patterns

  • Memory Usage: Memory consumption, peak usage, memory trends

  • View Rendering: Template rendering times (when target includes views)

  • Overall Health: Performance score and grade (A-F)

Performance Grading

The analyzer assigns a performance grade based on collected metrics:

  • A (90-100): Excellent performance

  • B (80-89): Good performance

  • C (70-79): Acceptable performance

  • D (60-69): Poor performance, optimization needed

  • F (0-59): Critical performance issues

Examples

Basic performance analysis

Monitor all metrics for 30 seconds:

Extended monitoring

Analyze for 2 minutes:

Focus on database performance

Monitor only database queries:

Focus on memory usage

Monitor only memory consumption:

Adjust performance threshold

Set slow request threshold to 200ms:

Enable profiling mode

Attempt to collect real metrics (if available):

Generate HTML report

Create a detailed HTML report with charts:

Complete analysis

Full analysis with all options:

Output Format

Console Output

HTML Report

The HTML report includes:

  • Performance Dashboard: Visual metrics with color-coded indicators

  • Performance Grade: Large visual display of grade and score

  • Charts:

    • Response time trends over monitoring period

    • Memory usage over time

  • Detailed Tables:

    • Top 10 slow requests with timing

    • Top 10 slow queries with execution times

  • Recommendations: Context-aware optimization suggestions

Reports are saved to: reports/performance-[timestamp].html

Data Collection

Simulated Mode (Default)

  • Generates realistic performance patterns

  • Useful for testing and demonstration

  • Provides consistent baseline metrics

Profile Mode (--profile)

When enabled, attempts to:

  • Access ColdFusion's metrics service

  • Hook into Wheels debug information

  • Collect real request/response times

  • Capture actual query execution data

  • Falls back to simulation if real data unavailable

Performance Thresholds

Default thresholds used for categorization:

  • Slow Requests: > 100ms (configurable via --threshold)

  • Slow Queries: > 50ms (fixed)

  • High Memory: > 500MB

  • Response Time Categories:

    • Fast: < 100ms

    • Moderate: 100-300ms

    • Slow: > 300ms

Recommendations

The analyzer provides context-aware recommendations based on:

  • High Average Response Time (>200ms): Suggests caching strategies

  • Slow Queries Detected: Recommends indexing and query optimization

  • High Memory Usage (>500MB): Suggests memory optimization

  • Multiple Slow Requests: Recommends async processing and lazy loading

Integration

CI/CD Pipeline

Exit codes for automation:

  • 0: Performance acceptable

  • 1: Performance issues detected (slow requests or queries found)

Example Jenkins integration:

Monitoring Strategy

  1. Run regularly in staging environment

  2. Compare metrics over time to track improvements

  3. Use HTML reports for stakeholder communication

  4. Set appropriate thresholds based on application requirements

Best Practices

  1. Baseline First: Establish performance baselines before optimization

  2. Target Specific Areas: Use --target to focus on suspected bottlenecks

  3. Realistic Load: Run during typical usage patterns for accurate results

  4. Profile Mode: Enable --profile when real metrics are needed

  5. Regular Monitoring: Schedule regular performance checks

  6. Track Trends: Save reports to track performance over time

Limitations

  • Simulated Data: Default mode uses simulated data; enable --profile for real metrics

  • Single Instance: Monitors single application instance only

  • External Services: Doesn't track external API or service calls

  • Browser Performance: Server-side only, doesn't measure client-side performance

Troubleshooting

No real data with --profile

  • Ensure application has debug mode available

  • Check ColdFusion administrator settings

  • Verify appropriate permissions for metrics access

High memory usage reported

  • Normal for JVM applications

  • Monitor trends rather than absolute values

  • Consider JVM heap settings

All requests showing as slow

  • Adjust threshold to match application expectations

  • Check if application is under unusual load

  • Verify database connection pool settings

wheels dbmigrate create column

Generate a migration file for adding columns to an existing database table.

Synopsis

Alias: wheels db create column

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Named parameters: name=value (e.g., name=users, dataType=string, columnName=email)

  • Flag parameters: --flag equals flag=true (e.g., --allowNull=false)

  • Flag with value: --flag=value (same as named parameters)

Parameter Mixing Rules:

✅ ALLOWED:

  • All named: name=users dataType=string columnName=email

  • Named parameters only (no positional support)

❌ NOT ALLOWED:

  • Positional parameters: This command does not support positional parameters

Recommendation: Use named parameters for all values: name=users dataType=string columnName=email allowNull=false

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

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:

Example:

Examples

Add a simple column

Add column with default value

Add nullable column with limit

Add decimal column with precision

Generated Migration Example

For the command:

Generates:

Use Cases

Adding User Preferences

Add preference column to user table:

Adding Audit Fields

Add tracking column to any table:

Adding Price Fields

Add decimal columns for pricing:

Best Practices

1. Consider NULL Values

For existing tables with data, make new columns nullable or provide defaults:

2. Use Appropriate Types

Choose the right column type for your data:

3. One Column Per Migration

This command creates one column at a time:

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:

Complex Column Types

For special column types, use blank migrations:

Common Pitfalls

1. Non-Nullable Without Default

2. Changing Column Types

This command adds columns, not modifies them:

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

  • - Create new tables

  • - Create custom migrations

  • - Remove tables

  • - Run migrations

  • - Rollback migrations

wheels env list

List all available environments for your Wheels application.

Synopsis

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

Examples

List all environments

Show detailed information

Output as JSON

Check environment validity

Filter production environments

Output Example

Basic Output

Verbose Output

JSON Output Format

Environment Status

Status Indicators

  • OK Valid - Configuration is valid and working

  • Active - Currently active environment

  • WARN 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

By Status

By Pattern

Sorting Options

By Name

By Type

By Last Modified

Environment Details

When using --verbose, shows:

  1. Configuration:

    • Config file path

    • Last modified date

    • File size

  2. Database:

    • Database name

    • Datasource name

  3. Settings:

    • Debug mode

    • Cache settings

    • Custom configurations

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

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

  • - Environment management overview

  • - Setup new environment

  • - Switch environments

  • - List configuration

wheels plugin install

Install a Wheels plugin from ForgeBox into the /plugins folder.

Synopsis

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Positional parameters: wheels plugins install cfwheels-bcrypt (plugin name)

  • Named parameters: name=value (e.g., name=cfwheels-bcrypt, version=1.0.0)

  • Flag parameters: --flag equals flag=true (e.g., --dev equals dev=true)

  • Flag with value: --flag=value (e.g., --version=1.0.0)

Parameter Mixing Rules:

✅ ALLOWED:

  • Positional: wheels plugins install cfwheels-bcrypt

  • Positional + flags: wheels plugins install cfwheels-bcrypt --version=1.0.0

  • All named: name=cfwheels-bcrypt version=1.0.0

❌ NOT ALLOWED:

  • Positional + named for same param: wheels plugins install cfwheels-bcrypt name=other

Recommendation: Use positional for plugin name, flags for options: wheels plugins install cfwheels-bcrypt --version=1.0.0

Parameters

Parameter
Required
Type
Description

Description

The plugins install command installs cfwheels-plugins type packages from ForgeBox into your application's /plugins folder. The command validates that the package is a valid cfwheels-plugin before installation.

Features

  • Installs only cfwheels-plugins type packages

  • Validates package type before installation

  • Automatically places plugins in /plugins folder

  • Supports specific version installation

  • Beautiful, color-coded output

  • Helpful error messages

Package Type Validation

The command ensures that only packages with type cfwheels-plugins can be installed. This prevents accidental installation of non-plugin packages.

Examples

Install latest version from ForgeBox

Output:

Install specific version

Output:

Install using plugin name (matches slug)

The command will find and install cfwheels-bcrypt from ForgeBox.

Installation fails (wrong package type)

Output:

Installation fails (plugin not found)

Output:

Installation Process

  1. Display Header: Shows plugin name and target version

  2. Package Validation: Verifies the package is type cfwheels-plugins

  3. Download: Uses CommandBox's PackageService to download from ForgeBox

  4. Installation: CommandBox installs the package

  5. Directory Move: If installed to wrong location, moves to /plugins folder

  6. Verification: Confirms installation success

  7. Display Results: Shows success message with helpful next steps

How It Works

The command uses PluginService which:

  1. Calls ForgeBox API to check package type

  2. Uses packageService.installPackage() to download and install

  3. Checks common installation paths (/modules/, root)

  4. Moves plugin to /plugins/ folder if needed

  5. Returns success/failure status

Package Sources

ForgeBox (Only Supported Source)

The command only supports installing from ForgeBox:

Unsupported Sources

The following sources are NOT supported:

  • ❌ GitHub repositories

  • ❌ Direct URLs

  • ❌ Local ZIP files

  • ❌ Local directories

To install plugins from these sources, use CommandBox's native install command and manually move to /plugins folder.

Error Messages

Package Type Validation Failed

Solution: Verify the package type on ForgeBox is cfwheels-plugins

Plugin Not Found

Solution: Check available plugins with wheels plugin list --available

Network Error

Solution: Check internet connection and ForgeBox status

Notes

  • Only installs cfwheels-plugins type packages from ForgeBox

  • Plugins are installed to /plugins folder

  • The --dev parameter is accepted but not currently used

  • Package type validation prevents installation of incorrect packages

  • If a plugin is already installed, it will be overwritten

  • After installation, use wheels plugin info <name> to view details

  • Restart your application to activate the new plugin

  • The command automatically handles directory placement

See Also

  • - List installed plugins

  • - View plugin details

  • - Update a plugin

  • - Remove a plugin

wheels get environment

Overview

The wheels get environment command displays the current environment setting for your Wheels application. It automatically detects which environment your application is configured to run in (development, staging, production, etc.) and shows where this configuration is coming from.

Command Syntax

Alias

Parameters

This command takes no parameters.

Basic Usage

Display Current Environment

This will output something like:

How It Works

Detection Priority

The command checks for environment configuration in the following order of precedence:

  1. .env file - Looks for WHEELS_ENV variable first, then Environment variable

  2. System environment variable - Checks for WHEELS_ENV first, then Environment system variable

  3. Default - Falls back to development if no configuration is found

The first valid configuration found is used and reported, along with which specific variable name was found.

Configuration Sources

1. .env File

The command first looks for a WHEELS_ENV variable in your application's .env file:

If WHEELS_ENV is not found, it then checks for Environment:

The regex pattern used ensures it correctly reads the value while ignoring:

  • Comments after the value (anything after #)

  • Trailing whitespace

  • Lines that are commented out

2. System Environment Variable

If not found in .env, it checks for system-level environment variables in the same order:

3. Default Value

If no configuration is found anywhere, it defaults to development.

Variable Priority

The command checks for two different variable names in this specific order:

  1. WHEELS_ENV in .env file

  2. Environment in .env file

  3. WHEELS_ENV system environment variable

  4. Environment system environment variable

  5. Default to development

Output Examples

Configured with WHEELS_ENV in .env

Configured with Environment in .env

Configured via System Variable (WHEELS_ENV)

Configured via System Variable (Environment)

Using Default

Common Use Cases

Verify Environment Before Deployment

Troubleshooting Configuration Issues

CI/CD Pipeline Verification

Supporting Legacy Systems

Error Handling

The command will show an error if:

  • It's not run from a Wheels application directory

  • There's an error reading configuration files

  • File permissions prevent reading configuration

Not a Wheels Application

Read Error

Best Practices

  1. Use WHEELS_ENV - Prefer WHEELS_ENV over Environment for clarity and consistency

  2. Consistent Configuration - Use one primary method for setting environment across your team

  3. Environment-Specific Files - Consider using .env.production, .env.development files with the merge command

  4. Don't Commit Production Settings - Keep production .env files out of version control

  5. Document Your Setup - Document which configuration method and variable name your team uses

  6. Verify Before Deployment - Always run this command to verify environment before deploying

Environment Precedence

Understanding precedence is important when multiple configurations exist:

If both WHEELS_ENV and Environment are set in .env, only WHEELS_ENV will be used.

Migration Guide

If you're migrating from a system that uses different environment variable names:

From "Environment" to "WHEELS_ENV"

Gradual Migration

You can migrate gradually since the command checks both:

  1. Leave existing Environment variables in place

  2. Start using WHEELS_ENV for new deployments

  3. The command will prefer WHEELS_ENV when both exist

Integration with Other Commands

This command works well with other Wheels CLI commands:

Tips

  • The command must be run from your Wheels application root directory

  • Environment values are case-sensitive (development ≠ Development)

  • Comments in .env files are properly ignored using #

  • Whitespace around values is automatically trimmed

  • The command clearly shows which variable name was found

  • WHEELS_ENV takes precedence over Environment when both exist

Troubleshooting

Environment Not Changing

If changing environment variables doesn't seem to work:

  1. Run wheels get environment to see which source and variable is being used

  2. Remember .env file takes precedence over system variables

  3. Remember WHEELS_ENV takes precedence over Environment

  4. Restart your CommandBox server after changes

  5. Check for typos in variable names

Variable Priority Issues

If the wrong environment is being detected:

  • Check if both WHEELS_ENV and Environment are set

  • Remember WHEELS_ENV has higher priority

  • Use the output to see exactly which variable is being read

Permission Errors

If you get permission errors:

  • Ensure you have read access to .env files

  • Check that you're in the correct directory

  • Verify file ownership and permissions

Unexpected Default

If you're getting the default development when you expect a different value:

  • Check for typos in configuration files (it's WHEELS_ENV not WHEEL_ENV)

  • Ensure .env file is in the application root

  • Verify system environment variables are properly exported

  • Check that values don't have quotes unless intended

wheels env merge

Overview

The wheels env merge command allows you to merge multiple environment configuration files (.env files) into a single consolidated file. This is particularly useful when working with different environments (development, staging, production) or when you have base configurations that need to be combined with environment-specific overrides.

Command Syntax

Parameters

Parameter
Type
Required
Description

Basic Usage Examples

Simple Two-File Merge

This merges .env.defaults and .env.local into .env.merge

Custom Output File

This merges the files and saves the result as .env

Production Environment Merge

Combines base configuration with production-specific settings

Multiple File Merge

Merges multiple files in the specified order (unlimited number of source files supported)

Dry Run (Preview)

Shows what the merged result would look like without creating a file

How It Works

File Processing Order

Files are processed in the order they are specified on the command line. Later files take precedence over earlier ones when there are conflicting variable names.

Supported File Formats

  • Properties format (standard .env format):

  • JSON format:

File Parsing Details

  • Empty lines and comments (lines starting with #) are skipped in properties files

  • Values can contain = signs (everything after the first = is considered the value)

  • Leading and trailing whitespace is trimmed from keys and values

  • The command automatically detects whether a file is JSON or properties format

Conflict Resolution

When the same variable exists in multiple files:

  • The value from the last processed file wins

  • Conflicts are tracked and reported with details showing:

    • The variable name

    • The original value and source file

    • The new value and source file

  • You'll see a summary showing which values were overridden

Output Features

Organized Structure

The merged output file is automatically organized:

  • Variables are grouped by prefix (e.g., DATABASE_*, API_*)

  • Groups are sorted alphabetically

  • Variables within groups are sorted alphabetically

  • Includes generated header comments with:

    • Generation timestamp

    • Source information

    • Section headers for each variable group

Security Features

When using --dryRun or viewing output, sensitive values are automatically masked:

  • Variables containing password, secret, key, or token (case-insensitive) show as ***MASKED***

  • The actual values are still written to the output file (only display is masked)

  • Each variable shows its source file for traceability

Common Use Cases

Development Workflow

Multi-Environment Setup

Deployment Preparation

Configuration Validation

Sample Output

Command Execution

Dry Run Output

Generated File Format

Error Handling

The command will stop and show an error if:

  • Source files don't exist

  • Less than two source files are provided

  • Output file cannot be written (permissions, disk space, etc.)

  • File read operations fail

Important Notes

  1. Default output filename is .env.merge (not .env.merged)

  2. Multiple files supported - You can merge any number of files (not just 2 or 3)

  3. Option format - Use double dashes for options: --output, --dryRun

  4. Value preservation - Values containing = signs are properly preserved

  5. Comment handling - Comments in source files are not preserved in the merged output

Best Practices

  1. Use descriptive file names that indicate their purpose (.env.base, .env.production, .env.local)

  2. Order files by precedence - place base/default files first, overrides last

  3. Use dry-run first to preview results before committing to a merge

  4. Keep sensitive data in local files that aren't committed to version control

  5. Document your merge strategy in your project's README

  6. Backup important configurations before merging

  7. Review conflicts - Pay attention to the conflicts report to ensure expected overrides

Tips

  • The merged file includes helpful comments showing when it was generated

  • Variables are automatically organized by prefix for better readability

  • Use the --dryRun option to understand what changes will be made

  • The command validates all source files exist before starting the merge process

  • You can merge as many files as needed in a single command

  • The source file information is preserved during dry runs for debugging

wheels analyze performance [--target=<target>] [--duration=<seconds>] [--report] [--threshold=<ms>] [--profile]

--target

Analysis target: all, controller, view, query, memory

all

--duration

Duration to run analysis in seconds (1-300)

30

--report

Generate HTML performance report with charts

false

--threshold

Performance threshold in milliseconds for slow requests

100

--profile

Enable profiling mode for real metrics (when available)

false

wheels analyze performance
wheels analyze performance --duration=120
wheels analyze performance --target=query
wheels analyze performance --target=memory
wheels analyze performance --threshold=200
wheels analyze performance --profile
wheels analyze performance --report
wheels analyze performance --target=all --duration=60 --threshold=200 --profile --report
Analyzing application performance...

Starting performance monitoring for 30 seconds...
Target: all
Threshold: 100ms

[====================] 100% Complete!

==================================================
       PERFORMANCE ANALYSIS COMPLETE
==================================================

Data Source: SIMULATED (Enable --profile for real data)

Request Performance
--------------------------------------------------
Requests Analyzed:          42
Average Response Time:     156ms
Slowest Request:          891ms
Fastest Request:           23ms
Slow Requests (>100ms):     18

Database Performance
--------------------------------------------------
Queries Executed:           42
Average Query Time:        28ms
Slow Queries (>50ms):       8

Memory Usage
--------------------------------------------------
Average Memory:           193MB
Peak Memory:              205MB

Top Slow Requests:
--------------------------------------------------
  1. reports.index() - 891ms
  2. users.create() - 645ms
  3. products.update() - 523ms
  4. orders.index() - 412ms
  5. dashboard.show() - 387ms

Top Slow Queries:
--------------------------------------------------
  1. SELECT * FROM orders WHERE id = ? - 187ms
  2. UPDATE products WHERE id = ? - 156ms
  3. SELECT * FROM users WHERE id = ? - 98ms

==================================================
Performance Grade: B (83/100)
==================================================

Performance Recommendations:
--------------------------------------------------
  * Implement caching strategies for frequently accessed data
  * Optimize database queries and add appropriate indexes
  * Enable query result caching in production
  * Minimize database round trips
  * Use connection pooling for database connections
stage('Performance Check') {
    steps {
        sh 'wheels analyze performance --duration=60 --threshold=200'
    }
}
wheels get environment
wheels get env
wheels get environment
Current Environment:
development

Configured in: .env file (WHEELS_ENV)
# .env file - Primary variable
WHEELS_ENV=production
DATABASE_HOST=localhost
# .env file - Alternative variable
Environment=staging
DATABASE_HOST=localhost
# Linux/Mac - Primary variable
export WHEELS_ENV=staging

# Windows - Primary variable
set WHEELS_ENV=staging

# Or using the alternative variable
export Environment=production
Current Environment:
production

Configured in: .env file (WHEELS_ENV)
Current Environment:
staging

Configured in: .env file (Environment)
Current Environment:
staging

Configured in: System environment variable (WHEELS_ENV)
Current Environment:
production

Configured in: System environment variable (Environment)
Current Environment:
development

Configured in: Using default
# Check environment before starting server
wheels get environment
commandbox server start
# Verify which configuration source and variable is being used
wheels get environment

# Check each source manually
cat .env | grep -E "WHEELS_ENV|Environment"
echo $WHEELS_ENV
echo $Environment
# In deployment script
wheels get environment
if [ $? -eq 0 ]; then
    echo "Environment configured successfully"
fi
# If migrating from a system that uses "Environment" variable
# Both will work without changes:
# Old: Environment=production
# New: WHEELS_ENV=production
wheels get environment
Error: This command must be run from a Wheels application directory
Error reading environment: [specific error message]
.env file - WHEELS_ENV (highest priority)
    ↓
.env file - Environment
    ↓
System variable - WHEELS_ENV
    ↓
System variable - Environment
    ↓
Default: development (lowest priority)
# Old configuration
Environment=production

# New configuration (both work)
WHEELS_ENV=production

# The command will detect either one
wheels get environment
# Check environment, then run migrations
wheels get environment
wheels db migrate

# Verify environment before running tests
wheels get environment
wheels test

# Check environment, then start server
wheels get environment
commandbox server start
wheels env merge <source1> <source2> [source3...] [options]

source1

string

Yes

First source .env file to merge

source2

string

Yes

Second source .env file to merge

source3, source4, ...

string

No

Additional source .env files to merge (unlimited)

--output

string

No

Output file name (default: .env.merge)

--dryRun

flag

No

Show what would be merged without actually writing the file

wheels env merge .env.defaults .env.local
wheels env merge .env.defaults .env.local --output=.env
wheels env merge source1=.env source2=.env.production --output=.env.merged
wheels env merge source1=base.env source2=common.env source3=dev.env source4=local.env --output=.env.development
wheels env merge base.env override.env --dryRun
DATABASE_HOST=localhost
DATABASE_PORT=5432
API_KEY=your-secret-key
{
  "DATABASE_HOST": "localhost",
  "DATABASE_PORT": "5432",
  "API_KEY": "your-secret-key"
}
# Start with base configuration
wheels env merge source1=.env.base source2=.env.development --output=.env

# Add local overrides
wheels env merge source1=.env source2=.env.local --output=.env
# Merge base, environment, and local configs
wheels env merge source1=.env.base source2=.env.staging source3=.env.local --output=.env
# Create production configuration
wheels env merge source1=.env.base source2=.env.production --output=.env.prod

# Preview staging configuration
wheels env merge source1=.env.base source2=.env.staging --dryRun
# Check what the final configuration looks like
wheels env merge source1=.env.defaults source2=.env.current --dryRun
Merging environment files:
  1. .env.defaults
  2. .env.local
  3. .env.override

Merged 3 files into .env.merge
  Total variables: 15

Conflicts resolved (later files take precedence):
  DATABASE_HOST: 'db.example.com' (.env.defaults) → 'localhost' (.env.local)
  DEBUG_MODE: 'false' (.env.defaults) → 'true' (.env.override)
Merged result (DRY RUN):

DATABASE Variables:
  DATABASE_HOST = localhost (from .env.local)
  DATABASE_NAME = myapp (from .env.defaults)
  DATABASE_PASSWORD = ***MASKED*** (from .env.local)
  DATABASE_PORT = 5432 (from .env.defaults)

API Variables:
  API_BASE_URL = https://api.example.com (from .env.defaults)
  API_KEY = ***MASKED*** (from .env.local)
  API_TOKEN = ***MASKED*** (from .env.override)

Other Variables:
  APP_NAME = MyApplication (from .env.defaults)
  DEBUG_MODE = true (from .env.override)
# Merged Environment Configuration
# Generated by wheels env merge command
# Date: 2024-12-15 14:30:45

# API Configuration
API_BASE_URL=https://api.example.com
API_KEY=actual-key-value
API_TOKEN=actual-token-value

# DATABASE Configuration
DATABASE_HOST=localhost
DATABASE_NAME=myapp
DATABASE_PASSWORD=actual-password
DATABASE_PORT=5432

# Other Configuration
APP_NAME=MyApplication
DEBUG_MODE=true
wheels generate app [name] [template] [directory] [options]
wheels g app [name] [template] [directory] [options]
wheels new [name] [template] [directory] [options]

name

Application name

MyApp

template

Template to use

wheels-base-template@BE

directory

Target directory

./{name}

reloadPassword

Set reload password

'' (empty)

datasourceName

Database datasource name

App name

cfmlEngine

CFML engine (lucee/adobe/boxlang)

lucee

--useBootstrap

Include Bootstrap CSS

false

--setupH2

Setup H2 embedded database

true

--init

Initialize as CommandBox package

false

--force

Overwrite existing directory

false

wheels generate app myapp
wheels generate app myapp HelloWorld
wheels generate app myapp HelloDynamic
wheels generate app myapp HelloPages
# Positional (recommended)
wheels generate app blog

# OR all named
wheels g app name=blog
# Positional (recommended)
wheels generate app api HelloDynamic

# OR all named
wheels g app name=api template=HelloDynamic
# Positional + named (recommended)
wheels generate app myapp --directory=./projects/

# OR all named
wheels g app name=myapp directory=./projects/
# Positional + flag (recommended)
wheels generate app portfolio --useBootstrap

# OR all named
wheels g app name=portfolio useBootstrap=true
# Positional + flag (recommended)
wheels generate app demo --setupH2

# OR all named
wheels g app name=demo setupH2=true
# Positional + flags (recommended)
wheels generate app enterprise --template=HelloDynamic --directory=./apps/ \
  --reloadPassword=secret \
  --datasourceName=enterprise_db \
  --cfmlEngine=adobe \
  --useBootstrap \
  --setupH2

# OR all named
wheels g app name=enterprise template=HelloDynamic directory=./apps/ \
  reloadPassword=secret \
  datasourceName=enterprise_db \
  cfmlEngine=adobe \
  useBootstrap=true \
  setupH2=true
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
{
  "name": "myapp",
  "version": "1.0.0",
  "dependencies": {
    "wheels": "^2.5.0"
  }
}
{
  "web": {
    "http": {
      "port": 3000
    }
  },
  "app": {
    "cfengine": "lucee5"
  }
}
{
  "name": "myapp",
  "version": "1.0.0",
  "framework": "wheels",
  "reload": "wheels"
}
wheels generate app myapp
wheels generate app myapp datasourceName=myapp_db --setupH2=false
server set app.datasources.myapp_db={...}
cd myapp
box install
box server start
http://localhost:3000
mytemplate/
├── config/
├── controllers/
├── models/
├── views/
└── template.json
wheels init
wheels generate app-wizard
wheels scaffold
wheels dbmigrate create column name=<table_name> dataType=<type> columnName=<column> [options]

name

string

Yes

-

The name of the database table to modify

dataType

string

Yes

-

The column type to add

columnName

string

Yes

-

The column name to add

default

any

No

-

The default value to set for the column

allowNull

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

[timestamp]_create_column_[columnname]_in_[tablename]_table.cfc
20240125160000_create_column_email_in_user_table.cfc
wheels dbmigrate create column name=user dataType=string columnName=email
wheels dbmigrate create column name=user dataType=boolean columnName=is_active default=true
wheels dbmigrate create column name=user dataType=string columnName=bio allowNull=true limit=500
wheels dbmigrate create column name=product dataType=decimal columnName=price precision=10 scale=2
wheels dbmigrate create column name=user dataType=string columnName=phone allowNull=true
component extends="wheels.migrator.Migration" hint="create column phone in user table" {

    function up() {
        transaction {
            addColumn(table="user", columnType="string", columnName="phone", allowNull=true);
        }
    }

    function down() {
        transaction {
            removeColumn(table="user", column="phone");
        }
    }

}
# Create separate migrations for each column
wheels dbmigrate create column name=user dataType=boolean columnName=newsletter_subscribed default=true
wheels dbmigrate create column name=user dataType=string columnName=theme_preference default="light"
wheels dbmigrate create column name=product dataType=integer columnName=last_modified_by allowNull=true
wheels dbmigrate create column name=product dataType=datetime columnName=last_modified_at allowNull=true
wheels dbmigrate create column name=product dataType=decimal columnName=price precision=10 scale=2 default=0
wheels dbmigrate create column name=product dataType=decimal columnName=cost precision=10 scale=2
# Good - nullable
wheels dbmigrate create column name=user dataType=text columnName=bio allowNull=true

# Good - with default
wheels dbmigrate create column name=user dataType=string columnName=status default="active"

# Bad - will fail if table has data (not nullable, no default)
wheels dbmigrate create column name=user dataType=string columnName=required_field --allowNull=false
# For short text
wheels dbmigrate create column name=user dataType=string columnName=username limit=50

# For long text
wheels dbmigrate create column name=post dataType=text columnName=content

# For money
wheels dbmigrate create column name=invoice dataType=decimal columnName=amount precision=10 scale=2
# Create separate migrations for related columns
wheels dbmigrate create column name=customer dataType=string columnName=address_line1
wheels dbmigrate create column name=customer dataType=string columnName=city
wheels dbmigrate create column name=customer dataType=string columnName=state limit=2
# Add foreign key column
wheels dbmigrate create column name=order dataType=integer columnName=customer_id

# Then create index in separate migration
wheels dbmigrate create blank name=add_order_customer_id_index
# Create blank migration for custom column types
wheels dbmigrate create blank name=add_user_preferences_json
# Then manually add the column with custom SQL
# This will fail if table has data
wheels dbmigrate create column name=user dataType=string columnName=required_field --allowNull=false

# Do this instead
wheels dbmigrate create column name=user dataType=string columnName=required_field default="pending"
# Wrong - trying to change existing column type
wheels dbmigrate create column name=user dataType=integer columnName=age

# Right - use blank migration for modifications
wheels dbmigrate create blank name=change_user_age_to_integer
wheels dbmigrate create table
wheels dbmigrate create blank
wheels dbmigrate remove table
wheels dbmigrate up
wheels dbmigrate down
wheels env list [options]

--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

wheels env list
wheels env list --verbose
wheels env list --format=json
wheels env list --check
wheels env list --filter=production
Available Environments
=====================

  NAME          TYPE         DATABASE           STATUS
  development * Development  wheels_dev         OK Valid
  testing       Testing      wheels_test        OK Valid
  staging       Staging      wheels_staging     OK Valid
  production    Production   wheels_prod        OK Valid
  qa            Custom       wheels_qa          WARN Invalid

* = Current environment
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
{
  "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
}
# Production environments only
wheels env list --filter=production

# Development environments
wheels env list --filter=development
# Valid environments only
wheels env list --filter=valid

# Environments with issues
wheels env list --filter=issues
# Environments containing "prod"
wheels env list --filter="*prod*"

# Can also be written as
wheels env list --filter=*prod*
wheels env list --sort=name
wheels env list --sort=type
wheels env list --sort=modified
wheels env
wheels env setup
wheels env switch
wheels config list
wheels plugins install <name> [--dev] [--version=<version>]

name

Yes

string

Plugin name or slug from ForgeBox

dev

No

boolean

Install as development dependency (not used)

version

No

string

Specific version to install (default: latest)

wheels plugins install cfwheels-bcrypt
===========================================================
  Installing Plugin
===========================================================

Plugin:  cfwheels-bcrypt
Version: latest

[CommandBox installation output...]

===========================================================

[OK] Plugin installed successfully!

Bcrypt encryption support for Wheels

Commands:
  wheels plugin list          View all installed plugins
  wheels plugin info cfwheels-bcrypt   View plugin details
wheels plugins install cfwheels-shortcodes --version=0.0.3
===========================================================
  Installing Plugin
===========================================================

Plugin:  cfwheels-shortcodes
Version: 0.0.3

[CommandBox installation output...]

===========================================================

[OK] Plugin installed successfully!

Shortcode support for Wheels content

Commands:
  wheels plugin list          View all installed plugins
  wheels plugin info cfwheels-shortcodes   View plugin details
wheels plugins install bcrypt
wheels plugins install commandbox-migrations
===========================================================
  Installing Plugin
===========================================================

Plugin:  commandbox-migrations
Version: latest

===========================================================

[ERROR] Failed to install plugin

Error: Only cfwheels-plugins can be installed via this command

Possible solutions:
  - Verify the plugin name is correct
  - Check if the plugin exists on ForgeBox:
    wheels plugin list --available
  - Ensure the plugin type is 'cfwheels-plugins'
wheels plugins install nonexistent-plugin
===========================================================
  Installing Plugin
===========================================================

Plugin:  nonexistent-plugin
Version: latest

===========================================================

[ERROR] Failed to install plugin

Error: Plugin not found on ForgeBox

Possible solutions:
  - Verify the plugin name is correct
  - Check if the plugin exists on ForgeBox:
    wheels plugin list --available
  - Ensure the plugin type is 'cfwheels-plugins'
# By slug
wheels plugins install cfwheels-bcrypt

# By name (auto-finds slug)
wheels plugins install bcrypt

# Specific version
wheels plugins install cfwheels-bcrypt --version=0.0.4
[ERROR] Failed to install plugin
Error: Only cfwheels-plugins can be installed via this command
[ERROR] Failed to install plugin
Error: Plugin not found on ForgeBox
[ERROR] Failed to install plugin
Error: Could not connect to ForgeBox
wheels plugin list
wheels plugin info
wheels plugin update
wheels plugin remove

wheels analyze code

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

Synopsis

wheels analyze code [--path=<path>] [--fix] [--format=<format>] [--severity=<severity>] [--report] [--verbose]

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Named parameters: name=value (e.g., path=app/controllers, format=json)

  • Flag parameters: --flag equals flag=true (e.g., --fix equals fix=true)

  • Flag with value: --flag=value equals flag=value (e.g., --path=app/models)

Parameter Mixing Rules:

✅ ALLOWED:

  • All flags: wheels analyze code --fix --report --verbose

  • Flags with values: wheels analyze code --path=app/models --format=json

  • Named + flags: path=app/controllers format=json --fix

❌ NOT ALLOWED:

  • Positional parameters: This command does not support positional parameters

Recommendation: Use flag syntax for consistency: wheels analyze code --path=app/models --fix --format=json

Parameters

Parameter
Description
Default

--path

Path to analyze (directory or file)

app

--fix

Attempt to fix issues automatically

false

--format

Output format: console, json, junit

console

--severity

Minimum severity level: info, warning, error

warning

--report

Generate HTML report

false

--verbose

Show detailed progress during analysis

false

Description

The analyze code command performs comprehensive code quality analysis on your Wheels application. It automatically excludes framework files and focuses only on your application code.

What It Checks

  • Code Complexity: Cyclomatic complexity and function length metrics

  • Code Style: Line length, indentation, trailing spaces, tabs vs spaces

  • Security Issues: SQL injection risks, hardcoded credentials, evaluate() usage

  • Performance: N+1 queries, missing query caching, SELECT * usage

  • Best Practices: Variable scoping, output attributes, code organization

  • Wheels Conventions: Controller/Model naming, validations, filters

  • Code Smells: Long parameter lists, nested loops, TODO comments

  • Duplicate Code: Detection of similar code blocks (30+ lines by default)

  • Deprecated Functions: Outdated Wheels function usage

Grading System

The analyzer assigns a health score (0-100) and letter grade (A-F) based on:

  • A (90-100): Excellent code quality

  • B (80-89): Good code quality with minor issues

  • C (70-79): Acceptable code quality, needs improvement

  • D (60-69): Poor code quality, significant refactoring needed

  • F (0-59): Critical issues, immediate attention required

Examples

Basic code analysis

Analyzes all code in the app/ directory by default:

wheels analyze code

Analyze specific directory

# Flag syntax (recommended)
wheels analyze code --path=app/controllers

# OR named
wheels analyze code path=app/controllers

Analyze specific file

# Flag syntax (recommended)
wheels analyze code --path=app/models/User.cfc

# OR named
wheels analyze code path=app/models/User.cfc

Auto-fix issues

Automatically fixes issues like trailing spaces, tabs, and missing var scoping:

wheels analyze code --fix

Generate HTML report

Creates a detailed HTML report with visualizations:

wheels analyze code --report

Reports are saved to reports/code-analysis-[timestamp].html

Analyze with JSON output for CI/CD

wheels analyze code --format=json

JUnit format for CI integration

wheels analyze code --format=junit

Check only errors (skip warnings and info)

wheels analyze code --severity=error

Verbose mode with progress indicators

wheels analyze code --verbose

Comprehensive analysis with all options

wheels analyze code --path=app/models --fix --report --verbose

Output Format

Console Output (Default)

==================================================
           CODE QUALITY REPORT
==================================================

           Grade: B (85/100)
           Good code quality with minor issues
==================================================

Code Metrics
--------------------------------------------------
Files Analyzed:          42
Total Lines:          3,567
Functions:              156
Avg Complexity:           4
Duplicate Blocks:         3
Code Smells:              7
Deprecated Calls:         2

Issue Summary
--------------------------------------------------
Errors:       2 (Critical issues requiring immediate attention)
Warnings:    12 (Issues that should be addressed)
Info:        28 (Suggestions for improvement)

[Additional details for each file...]

JSON Output

Structured JSON with all metrics, issues, and file details for programmatic processing.

JUnit Output

XML format compatible with CI/CD tools like Jenkins, GitLab CI, and GitHub Actions.

Configuration

Create a .wheelscheck file in your project root to customize rules:

{
  "rules": {
    "max-line-length": 120,
    "indent-size": 4,
    "max-function-length": 50,
    "max-function-complexity": 10,
    "max-file-length": 500,
    "duplicate-threshold": 30,
    "naming-convention": "camelCase"
  },
  "features": {
    "duplicateDetection": true,
    "complexityAnalysis": true,
    "wheelsConventions": true,
    "codeSmells": true
  },
  "exclude": [
    "custom/path/to/exclude/",
    "generated/"
  ]
}

Excluded Directories

The analyzer automatically excludes:

  • Wheels framework files (vendor/wheels/, wheels/)

  • Third-party dependencies (vendor/, node_modules/)

  • Test frameworks (testbox/, tests/)

  • Build artifacts (build/, dist/)

  • Version control (.git/, .svn/)

  • System directories (WEB-INF/, CFIDE/)

  • Generated files (*.min.js, *.min.css)

Auto-fixable Issues

The following issues can be automatically fixed with the --fix flag:

  • Trailing whitespace

  • Tab characters (converted to spaces)

  • Missing var scoping in functions

  • Missing output attribute on components

Integration with CI/CD

GitHub Actions

- name: Code Analysis
  run: |
    wheels analyze code --format=junit --severity=error

GitLab CI

code_quality:
  script:
    - wheels analyze code --format=json > code-quality.json
  artifacts:
    reports:
      codequality: code-quality.json

Jenkins

stage('Code Analysis') {
    steps {
        sh 'wheels analyze code --format=junit'
        junit 'code-analysis-results.xml'
    }
}

Performance Considerations

  • Small projects (< 100 files): Analysis completes in seconds

  • Medium projects (100-500 files): 30-60 seconds typical

  • Large projects (500+ files): Several minutes, use --verbose to track progress

  • HTML report generation adds 5-30 seconds depending on project size

Exit Codes

  • 0: Success, no errors found

  • 1: Analysis completed with errors found

  • 2: Analysis failed (invalid path, configuration error)

Tips

  1. Run analysis regularly during development to catch issues early

  2. Use --fix for quick cleanup before commits

  3. Include analysis in pre-commit hooks or CI pipelines

  4. Start with --severity=error and gradually include warnings

  5. Review the HTML report for visual insights into code quality

  6. Use the grade as a benchmark to track improvement over time

  7. Focus on fixing high-complexity functions first for maximum impact

Troubleshooting

No files found to analyze

  • Ensure you're in a Wheels application root directory

  • Check that the app/ directory exists

  • Verify path permissions

Analysis taking too long

  • Use --path to analyze specific directories

  • Add frequently changing directories to exclude list

  • Consider splitting analysis across multiple runs

Fix not working

  • Some issues require manual intervention

  • Check file permissions for write access

  • Review the specific fix recommendations in the output

wheels env set

Overview

The wheels env set command allows you to set or update environment variables in .env files. This command provides a quick and safe way to modify environment configuration files directly from the command line, supporting both creation of new variables and updating of existing ones.

Synopsis

wheels env set KEY=VALUE [KEY2=VALUE2 ...] [--file=filename]

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Named parameters: KEY=VALUE (e.g., DB_HOST=localhost, API_KEY=secret)

  • Flag parameters: --flag=value (e.g., --file=.env.production)

Parameter Mixing Rules:

✅ ALLOWED:

  • Named KEY=VALUE pairs: wheels env set DB_HOST=localhost DB_PORT=3306

  • Named + file flag: wheels env set DB_HOST=localhost --file=.env.production

  • Multiple variables: wheels env set KEY1=value1 KEY2=value2 KEY3=value3

❌ NOT ALLOWED:

  • Positional parameters: This command does not support positional parameters

Recommendation: Use KEY=VALUE format with optional --file flag: wheels env set DB_HOST=localhost --file=.env.production

Parameters

Parameter
Type
Required
Description

KEY=VALUE

KEY=VALUE

Yes

One or more environment variable assignments in KEY=VALUE format

--file

string

No

The .env file to update (default: .env)

Basic Usage Examples

Set a Single Variable

wheels env set DB_HOST=localhost

Sets DB_HOST to localhost in the .env file

Set Multiple Variables

wheels env set DB_PORT=3306 DB_NAME=myapp DB_USER=root

Sets multiple database-related variables in a single command

Update Specific File

wheels env set --file=.env.production API_KEY=secret

Updates the .env.production file instead of the default .env

Complex Values

wheels env set DATABASE_URL="mysql://user:pass@localhost:3306/db"
wheels env set API_ENDPOINT=https://api.example.com/v1

Sets variables with complex values including special characters

How It Works

File Handling

The command intelligently handles different scenarios:

  1. Existing File: Updates or adds variables to the existing file

  2. Non-existent File: Creates a new file with the specified variables

  3. Format Detection: Automatically detects and preserves the file format (properties or JSON)

Supported File Formats

Properties Format (Standard .env)

DATABASE_HOST=localhost
DATABASE_PORT=3306
API_KEY=your-secret-key

JSON Format

{
  "DATABASE_HOST": "localhost",
  "DATABASE_PORT": "3306",
  "API_KEY": "your-secret-key"
}

Update Behavior

  • Existing Variables: Overwrites the current value with the new one

  • New Variables: Appends to the end of the file (for properties format)

  • Comments: Preserves existing comments and empty lines

  • Line Format: Maintains the original file structure and formatting

Special Value Handling

  • Trailing Commas: Automatically removes trailing commas from values

  • Equal Signs: Values can contain = signs (everything after the first = is the value)

  • Whitespace: Trims leading and trailing whitespace from keys and values

  • Special Characters: Properly handles URLs, connection strings, and other complex values

Security Features

Sensitive Value Masking

When displaying updated variables, the command automatically masks sensitive values:

  • Variables containing password, secret, key, or token (case-insensitive)

  • Masked values appear as ***MASKED*** in the output

  • Actual values are still written correctly to the file

Git Security Warning

The command checks if your .env file is listed in .gitignore:

  • Displays a warning if the file is not ignored

  • Recommends adding it to prevent committing secrets

  • Only checks when working with files containing .env in the name

Output Information

After successful execution, the command displays:

  • Confirmation message with the filename

  • List of all updated/added variables

  • Masked display for sensitive values

  • Git security warning (if applicable)

Sample Output

Environment variables updated in .env:
  DB_HOST = localhost
  DB_PORT = 3306
  DB_PASSWORD = ***MASKED***
  API_KEY = ***MASKED***

Warning: .env is not in .gitignore!
  Add it to .gitignore to prevent committing secrets.

Common Use Cases

Initial Setup

# Create a new .env file with basic configuration
wheels env set APP_NAME=MyApp APP_ENV=development DEBUG=true

Database Configuration

# Set all database variables at once
wheels env set DB_HOST=localhost DB_PORT=5432 DB_NAME=myapp DB_USER=appuser DB_PASSWORD=secret

API Configuration

# Configure API endpoints and keys
wheels env set API_BASE_URL=https://api.example.com API_KEY=abc123 API_TIMEOUT=30

Environment-Specific Settings

# Development settings
wheels env set --file=.env.development DEBUG=true LOG_LEVEL=debug

# Production settings
wheels env set --file=.env.production DEBUG=false LOG_LEVEL=error

Updating Existing Values

# Change database host from localhost to production server
wheels env set DB_HOST=db.production.com

# Update multiple values
wheels env set APP_ENV=production DEBUG=false

File Creation Behavior

When creating a new file, the command adds:

  • Header comment indicating it was generated by the command

  • Timestamp comment (optional)

  • All specified variables

Example of a newly created file:

# Wheels Environment Configuration
# Generated by wheels env set command

APP_NAME=MyApplication
APP_ENV=development
DEBUG=true

Error Handling

The command will display an error and stop if:

  • No KEY=VALUE pairs are provided

  • File write permissions are insufficient

  • The file path is invalid

  • File system errors occur (disk full, etc.)

Error Messages

# No arguments provided
Error: No key=value pairs provided. Usage: wheels env set KEY=VALUE

# File write failure
Error: Failed to update .env file: [specific error message]

Best Practices

  1. Use quotes for complex values containing spaces or special characters:

    wheels env set CONNECTION_STRING="Server=localhost;Database=myapp;User=root"
  2. Update multiple related variables together to maintain consistency:

    wheels env set DB_HOST=newhost DB_PORT=3306 DB_NAME=newdb
  3. Keep sensitive values in separate files not tracked by version control:

    wheels env set --file=.env.local API_SECRET=very-secret-key
  4. Always check .gitignore to ensure sensitive files are not committed:

    echo ".env" >> .gitignore
    echo ".env.local" >> .gitignore
  5. Use environment-specific files for different deployments:

    wheels env set --file=.env.production APP_ENV=production
    wheels env set --file=.env.staging APP_ENV=staging

Tips and Tricks

Batch Updates

Update multiple variables from different categories in one command:

wheels env set \
  APP_NAME=MyApp \
  DB_HOST=localhost \
  DB_PORT=5432 \
  CACHE_DRIVER=redis \
  MAIL_HOST=smtp.example.com

Quick Environment Switch

# Switch to production settings
wheels env set APP_ENV=production DEBUG=false LOG_LEVEL=error

# Switch back to development
wheels env set APP_ENV=development DEBUG=true LOG_LEVEL=debug

Creating Templates

# Create a template file for new projects
wheels env set --file=.env.example \
  APP_NAME=YourAppName \
  APP_ENV=development \
  DB_HOST=localhost \
  DB_PORT=3306 \
  DB_NAME=your_database \
  DB_USER=your_user \
  DB_PASSWORD=your_password

Important Notes

  1. File Format Preservation: The command preserves the original format (JSON or properties)

  2. Comment Preservation: Existing comments and empty lines are maintained

  3. Atomic Updates: All variables are updated in a single operation

  4. No Validation: The command doesn't validate variable values

  5. Case Sensitive: Variable names are case-sensitive

  6. Overwrite Behavior: Existing values are always overwritten

  7. Trailing Comma Removal: Automatically cleans trailing commas from values

Security Recommendations

  1. Never commit .env files containing real credentials

  2. Use .env.example files as templates with dummy values

  3. Keep production secrets in secure vaults or CI/CD systems

  4. Rotate credentials regularly using this command

  5. Review git history before pushing to ensure no secrets were committed

wheels plugin search

Search for available Wheels plugins on ForgeBox.

Synopsis

wheels plugin search [query] [--format=<format>] [--orderBy=<field>]

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Positional parameters: wheels plugin search bcrypt (search query)

  • Named parameters: query=value (e.g., query=auth, format=json)

  • Flag parameters: --flag=value (e.g., --format=json, --orderBy=downloads)

Parameter Mixing Rules:

✅ ALLOWED:

  • Positional: wheels plugin search bcrypt

  • Positional + flags: wheels plugin search auth --format=json

  • All named: query=bcrypt format=json orderBy=downloads

  • Named + flags: query=auth --format=json

❌ NOT ALLOWED:

  • Positional + named for same param: wheels plugin search bcrypt query=other

Recommendation: Use positional for query, flags for options: wheels plugin search auth --format=json --orderBy=downloads

Parameters

Parameter
Required
Type
Options
Default
Description

query

No

string

-

(empty)

Search term to filter plugins

format

No

string

table, json

table

Output format for the results

orderBy

No

string

name, downloads, updated

downloads

Sort results by specified field

Description

The plugin search command searches ForgeBox for available cfwheels-plugins type packages. You can search for all plugins or filter by keywords. Results can be sorted by name, downloads, or last updated date.

Features

  • Searches only cfwheels-plugins type packages

  • Filters results by search term

  • Multiple sort options

  • Color-coded, formatted output

  • JSON export support

  • Dynamic column widths

Examples

Search all plugins

wheels plugin search

Output:

===========================================================
  Searching ForgeBox for Wheels Plugins
===========================================================

Found 5 plugins:

Name                          Version     Downloads   Description
-------------------------------------------------------------------------------
cfwheels-bcrypt               1.0.2       4393        CFWheels 2.x plugin helper meth...
shortcodes                    0.0.4       189         Shortcodes Plugin for CFWheels
cfwheels-authenticateThis     2.0.0       523         Adds bCrypt authentication helpe...
cfwheels-jwt                  2.1.0       412         CFWheels plugin for encoding and...
cfwheels-htmx-plugin          1.0.0       678         HTMX Plugin for CFWheels

-----------------------------------------------------------

Commands:
  wheels plugin install <name>    Install a plugin
  wheels plugin info <name>       View plugin details

Search for specific plugin

wheels plugin search bcrypt

Output:

===========================================================
  Searching ForgeBox for Wheels Plugins
===========================================================

Search term: bcrypt

Found 1 plugin:

Name                    Version     Downloads   Description
-----------------------------------------------------------------------
cfwheels-bcrypt         1.0.2       4393        CFWheels 2.x plugin helper meth...

-----------------------------------------------------------

Commands:
  wheels plugin install <name>    Install a plugin
  wheels plugin info <name>       View plugin details

No results found

wheels plugin search nonexistent

Output:

===========================================================
  Searching ForgeBox for Wheels Plugins
===========================================================

Search term: nonexistent

No plugins found matching 'nonexistent'

Try:
  wheels plugin search <different-keyword>
  wheels plugin list --available

Sort by name

wheels plugin search --orderBy=name

Results will be sorted alphabetically by plugin name.

Sort by last updated

wheels plugin search --orderBy=updated

Results will be sorted by most recently updated plugins first.

Export as JSON

wheels plugin search --format=json

Output:

{
  "plugins": [
    {
      "name": "CFWheels bCrypt",
      "slug": "cfwheels-bcrypt",
      "version": "1.0.2",
      "description": "CFWheels 2.x plugin helper methods for the bCrypt Java Lib",
      "author": "neokoenig",
      "downloads": 4393,
      "updateDate": "2022-05-30T02:09:07+00:00"
    },
    {
      "name": "Shortcodes",
      "slug": "shortcodes",
      "version": "0.0.4",
      "description": "Shortcodes Plugin for CFWheels",
      "author": "neokoenig",
      "downloads": 189,
      "updateDate": "2017-05-16T09:03:02+00:00"
    }
  ],
  "count": 2,
  "query": ""
}

How It Works

  1. Execute ForgeBox Command: Runs forgebox show type=cfwheels-plugins to get all plugins

  2. Parse Output: Scans the formatted output for lines containing Slug: "plugin-slug"

  3. Extract Slugs: Uses regex to extract slug values from quoted strings

  4. Filter by Query: If search term provided, only processes slugs containing that term

  5. Fetch Details: For each matching slug, calls forgebox.getEntry(slug) to get:

    • Plugin title and description

    • Latest version (from latestVersion.version)

    • Author username (from user.username)

    • Download count (from hits)

    • Last updated date

  6. Sort Results: Sorts plugins by specified order (downloads, name, or updated date)

  7. Format Output: Displays in table or JSON format with dynamic column widths

Sort Options

downloads (default)

Sorts by number of downloads, most popular first. Best for finding widely-used plugins.

name

Sorts alphabetically by plugin name. Best for browsing all available plugins.

updated

Sorts by last update date, most recent first. Best for finding actively maintained plugins.

Search Tips

  1. Broad Search: Start with general terms like "auth" or "cache"

  2. Case Insensitive: Search is case-insensitive

  3. Partial Matching: Matches plugins containing the search term anywhere in the slug

  4. Popular First: Default sort shows most downloaded plugins first

  5. Empty Query: Run without query to see all available plugins

Output Formats

Table Format (Default)

  • Color-coded columns (cyan names, green versions, yellow downloads)

  • Dynamic column widths based on content

  • Truncated descriptions with ellipsis

  • Clear section headers and dividers

  • Helpful command suggestions

JSON Format

  • Structured data for programmatic use

  • Includes plugin count

  • Includes search query

  • Complete plugin information

Integration with Other Commands

After finding plugins:

# View detailed information
wheels plugin info cfwheels-bcrypt

# Install directly
wheels plugin install cfwheels-bcrypt

# List installed plugins
wheels plugin list

Performance Notes

  • Fetches all cfwheels-plugins from ForgeBox

  • Filters results client-side

  • Queries detailed info for each matching plugin

  • May take a few seconds for large result sets

  • Results are not cached (always fresh)

Error Handling

If ForgeBox cannot be reached:

[ERROR] Error searching for plugins
Error: Connection timeout

If no plugins of type cfwheels-plugins exist:

No plugins found

Try:
  wheels plugin search <different-keyword>
  wheels plugin list --available

Notes

  • Only searches cfwheels-plugins type packages

  • Requires internet connection to query ForgeBox

  • Search is performed against plugin slugs

  • Results include version, downloads, and description

  • Dynamic table formatting adjusts to content

  • Some plugins may not have complete metadata

  • Plugins without valid metadata are skipped

See Also

  • wheels plugin info - View detailed plugin information

  • wheels plugin install - Install a plugin

  • wheels plugin list - List installed plugins

  • wheels plugin outdated - Check for plugin updates

wheels plugin update

Update a Wheels plugin to the latest or a specified version from ForgeBox.

Usage

wheels plugin update <name> [--version=<version>] [--force]

Parameters

Parameter
Required
Type
Description

name

Yes

string

Name, slug, or folder name of the plugin to update

version

No

string

Specific version to update to (default: latest)

force

No

boolean

Force update even if already at latest version

Description

The plugin update command updates an installed Wheels plugin from the /plugins folder. It checks ForgeBox for the latest version, compares with the installed version, and performs the update if needed.

Features

  • Smart version checking (prevents unnecessary updates)

  • Flexible plugin matching (by name, slug, or folder name)

  • Real-time version queries from ForgeBox

  • Clean removal and reinstallation process

  • Helpful success and error messages

  • Beautiful, color-coded output

Examples

Update to latest version

wheels plugin update bcrypt

Output:

===========================================================
  Updating Plugin: bcrypt
===========================================================

Plugin:          bcrypt
Current version: 0.0.3
Latest version:  0.0.4

Target version: 0.0.4
===========================================================

Removing old version...
Installing new version...

[CommandBox installation output...]

===========================================================

[OK] Plugin 'bcrypt' updated successfully!

Commands:
  wheels plugin info bcrypt   View plugin details
  wheels plugin list            View all installed plugins

Plugin already up to date

wheels plugin update bcrypt

Output:

===========================================================
  Updating Plugin: bcrypt
===========================================================

Plugin:          bcrypt
Current version: 0.0.4
Latest version:  0.0.4

===========================================================

[OK] Plugin is already at the latest version (0.0.4)

Use --force to reinstall anyway:
  wheels plugin update bcrypt --force

Update to specific version

wheels plugin update bcrypt --version=0.0.3

Output:

===========================================================
  Updating Plugin: bcrypt
===========================================================

Plugin:          bcrypt
Current version: 0.0.4
Latest version:  0.0.4

Target version: 0.0.3
===========================================================

Removing old version...
Installing new version...

[CommandBox installation output...]

===========================================================

[OK] Plugin 'bcrypt' updated successfully!

Commands:
  wheels plugin info bcrypt   View plugin details
  wheels plugin list            View all installed plugins

Force reinstall

wheels plugin update bcrypt --force

Output:

===========================================================
  Updating Plugin: bcrypt
===========================================================

Plugin:          bcrypt
Current version: 0.0.4
Latest version:  0.0.4

Target version: 0.0.4
===========================================================

Removing old version...
Installing new version...

[CommandBox installation output...]

===========================================================

[OK] Plugin 'bcrypt' updated successfully!

Commands:
  wheels plugin info bcrypt   View plugin details
  wheels plugin list            View all installed plugins

Plugin not installed

wheels plugin update nonexistent-plugin

Output:

===========================================================
  Updating Plugin: nonexistent-plugin
===========================================================

[ERROR] Plugin not found

Plugin 'nonexistent-plugin' is not installed

Install this plugin:
  wheels plugin install nonexistent-plugin

Cannot reach ForgeBox

wheels plugin update bcrypt

Output (network error):

===========================================================
  Updating Plugin: bcrypt
===========================================================

Plugin:          bcrypt
Current version: 0.0.4

Error checking ForgeBox: Connection timeout

Unable to verify if update is needed

Options:
  - Specify a version:
    wheels plugin update bcrypt --version=x.x.x
  - Force reinstall:
    wheels plugin update bcrypt --force

Update Process

  1. Find Plugin: Searches /plugins folder for matching plugin by name, slug, or folder name

  2. Read Metadata: Extracts current version and slug from plugin's box.json

  3. Query ForgeBox: Uses forgebox show command to get latest version

  4. Version Comparison: Cleans and compares versions to check if update needed

  5. Skip if Up-to-Date: Exits early if already at target version (unless --force)

  6. Remove Old Version: Deletes the plugin folder

  7. Install New Version: Uses PackageService to download and install from ForgeBox

  8. Verify Location: Moves plugin to /plugins if installed elsewhere

  9. Confirm Success: Displays success message with helpful commands

Plugin Matching

The command uses a smart matching algorithm to find plugins by:

  1. Folder name (e.g., bcrypt)

  2. Slug from box.json (e.g., cfwheels-bcrypt)

  3. Name from box.json (e.g., bcrypt, CFWheels Bcrypt)

  4. Normalized variations (strips cfwheels- and wheels- prefixes)

This means you can update using any of these:

wheels plugin update bcrypt
wheels plugin update cfwheels-bcrypt
wheels plugin update "CFWheels Bcrypt"

Version Comparison

The command cleans versions before comparison:

  • Removes all non-numeric characters except dots

  • Example: v0.0.4 becomes 0.0.4

  • Example: 0.0.4-beta becomes 0.0.4

  • Compares cleaned strings for equality

If versions match, the plugin is considered up-to-date.

Force Flag

Use --force to reinstall even when already up-to-date. Useful for:

  • Recovering from corrupted installations

  • Forcing cache refresh

  • Testing installation process

  • Reinstalling after manual modifications

Error Handling

Plugins Directory Not Found

[ERROR] Plugins directory not found
Plugin 'bcrypt' is not installed

Plugin Not Installed

[ERROR] Plugin not found
Plugin 'bcrypt' is not installed

Install this plugin:
  wheels plugin install bcrypt

ForgeBox Query Failed

Error checking ForgeBox: [error message]

Unable to verify if update is needed

Options:
  - Specify a version:
    wheels plugin update bcrypt --version=x.x.x
  - Force reinstall:
    wheels plugin update bcrypt --force

Installation Failed

[ERROR] Error updating plugin
Error: [error message]

Best Practices

  1. Check First: Use wheels plugin outdated to see which plugins need updates

  2. Update One at a Time: Test each plugin update individually

  3. Read Release Notes: Check ForgeBox for breaking changes

  4. Test in Development: Update in dev environment before production

  5. Keep Backups: Commit your code before updating plugins

Notes

  • Only updates plugins from /plugins folder

  • Only works with cfwheels-plugins type packages

  • Removes old version completely before installing new version

  • Uses ForgeBox slug for installation to ensure correct package

  • Requires internet connection to query ForgeBox

  • Version check is performed in real-time (not cached)

  • After update, plugin may require application reload

  • If installation fails, the old version is already removed (no automatic rollback)

  • The command uses CommandBox's PackageService for downloading

See Also

  • wheels plugin update:all - Update all plugins

  • wheels plugin outdated - Check for outdated plugins

  • wheels plugin info - View plugin details

  • wheels plugin list - List installed plugins

Quick Start Guide

Get up and running with Wheels CLI in minutes. Learn installation, creating your first application, and common development tasks.

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

# 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

Install Wheels CLI

box install wheels-cli

Creating Your First Application

1. Generate Application

wheels new blog
cd blog

This creates a new Wheels application with:

  • Complete directory structure

  • Configuration files

  • Sample code

2. Configure Database

Edit /config/settings.cfm:

<cfset set(dataSourceName="blog_development")>

Or use H2 embedded database:

wheels new blog --setupH2

Create the database:

# If using external database (MySQL, PostgreSQL, etc.)
wheels db create

3. Start Server

box server start

Visit http://localhost:3000

Creating Your First Feature

Let's create a blog post feature:

1. Generate Scaffold

wheels generate scaffold name=post properties=title:string,content:text,published:boolean

This generates:

  • Model with validations

  • Controller with CRUD actions

  • Views for all actions

  • Database migration

  • Test files

2. Run Migration

wheels dbmigrate latest

3. Add Routes

Edit /config/routes.cfm:

<cfscript>
    // Add this line
    resources("posts");
</cfscript>

4. Reload Application

wheels reload

5. Test Your Feature

Visit http://localhost:3000/posts

You now have a fully functional blog post management system!

Development Workflow

Running Tests

# Run all tests
wheels test run

# Watch mode
wheels test run --watch

# Specific tests
wheels test run tests/models/PostTest.cfc

Adding Relationships

Let's add comments to posts:

# Generate comment model
wheels generate model comment --properties="author:string,content:text,postId:integer" \
  --belongsTo="post"

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

# Generate comments controller
wheels generate controller comments --rest

# Run migration
wheels dbmigrate latest

Common Tasks

Adding Authentication

# 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

Adding API Endpoints

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

# Or convert existing to API
wheels generate controller api/posts --api

Working with Views

# Generate specific views
wheels generate view posts featured
wheels generate view users profile

# Add layouts
echo '<cfoutput><!DOCTYPE html>...</cfoutput>' > views/layout.cfm

Best Practices

1. Use Migrations

Always use migrations for database changes:

# 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

2. Write Tests

Generate tests for your code:

# After creating a model
wheels generate test model post

# After creating a controller
wheels generate test controller posts

3. Use Environment Configuration

# Development
wheels reload development

# Testing
wheels reload testing

# Production
wheels reload production

4. Version Control

git init
git add .
git commit -m "Initial Wheels application"

Add to .gitignore:

/db/sql/
/logs/
/temp/
.env

Debugging

Check Logs

tail -f logs/wheels.log

Enable Debug Mode

In /config/settings.cfm:

<cfset set(showDebugInformation=true)>

Common Issues

Port already in use:

box server start port=3001

Database connection failed:

# Check datasource
box server info
box server show

Migration failed:

# Check status
wheels db status

# Run specific migration
wheels dbmigrate exec 20240120000000

# Or rollback and try again
wheels db rollback

Need to reset database:

# Complete reset (careful - destroys all data!)
wheels db reset --force

Access database directly:

# CLI shell
wheels db shell

# Web console (H2 only)
wheels db shell --web

Next Steps

  1. Read the Guides:

    • Service Architecture

    • Testing Guide

    • Migration Guide

  2. Explore Commands:

    • wheels --help

    • wheels generate --help

    • wheels dbmigrate --help

  3. Join the Community:

    • Wheels Documentation

    • GitHub Discussions

    • CFML Slack #wheels channel

Example: Complete Blog Application

Here's a complete blog setup:

# 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 \
  --belongsTo=post

# Update associations
wheels generate property post authorId:integer --belongsTo=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

# Visit http://localhost:3000/posts

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

wheels config dump

Overview

The wheels config dump command exports your Wheels application configuration settings for inspection, backup, or migration purposes. It can display configurations in multiple formats and optionally mask sensitive values for security.

Basic Usage

wheels config dump

This displays the current environment's configuration in a formatted table in the console.

Command Syntax

wheels config dump [environment] [--format=<type>] [--output=<file>] [--noMask]

Arguments and Options

Positional Arguments

Argument
Required
Description
Default

environment

No

The environment to dump (development, testing, production)

Auto-detects from WHEELS_ENV or defaults to "development"

Options

Option
Description
Values
Default

--format=<type>

Output format for the configuration

table, json, env, cfml

Console: table File: json

--output=<file>

File path to save the configuration

Any valid file path

None (displays to console)

--noMask

Don't mask sensitive values (passwords, keys, tokens, etc.)

Flag (true/false)

false (sensitive values are masked)

Option Details

--format

Specifies the output format for the configuration dump:

  • table: Formatted tables organized by category (best for console viewing)

  • json: Structured JSON format (best for programmatic use and file storage)

  • env: Environment variables format (.env compatible)

  • cfml: Wheels set() statements format

--output

When specified, saves the configuration to a file instead of displaying to console. If no format is explicitly specified with --output, JSON format is automatically used for better file compatibility.

--noMask

⚠️ Security Warning: This option exposes sensitive configuration data including passwords, API keys, and tokens. Only use when absolutely necessary and ensure the output is stored securely.

Output Formats

Table Format (Console Default)

Displays configuration in organized categories with formatted tables:

  • DATABASE Settings

  • CACHING Settings

  • SECURITY Settings

  • ENVIRONMENT Settings

  • OTHER Settings

JSON Format (File Default)

Exports configuration as structured JSON:

{
  "datasource": "myapp",
  "cacheQueries": false,
  "environment": "development",
  "_environment": {
    "WHEELS_ENV": "development"
  }
}

Environment Variables Format (.env)

Exports as environment variables compatible with .env files:

## Application Settings
DATASOURCE=myapp
CACHE_QUERIES=false
ENVIRONMENT=development

CFML Format

Exports as Wheels set() statements:

// Wheels Configuration Export
set(datasource = "myapp");
set(cacheQueries = false);
set(environment = "development");

Common Use Cases

1. Quick Configuration Review

# View current configuration
wheels config dump

# View production configuration
wheels config dump production

2. Backup Configuration

# Backup as JSON (default for files)
wheels config dump --output=config-backup.json

# Backup with timestamp
wheels config dump --output="backup/config-$(date +%Y%m%d).json"

3. Environment Comparison

# Export different environments
wheels config dump development --output=dev-config.json
wheels config dump production --output=prod-config.json

# Then compare using diff tools
diff dev-config.json prod-config.json

4. Generate Environment Files

# Create .env file template
wheels config dump --format=env --output=.env.template

# Create environment-specific files
wheels config dump production --format=env --output=.env.production

5. Migration Between Servers

# Export from source server (masked)
wheels config dump --output=config-export.json

# Export with sensitive data (be careful!)
wheels config dump --noMask --output=config-complete.json

6. Generate CFML Settings

# Create settings file for another environment
wheels config dump --format=cfml --output=config/staging/settings.cfm

Security Considerations

Automatic Masking

By default, the command masks sensitive values containing these keywords:

  • password

  • secret

  • key

  • token

  • apikey / api_key

  • private

  • credential

  • auth

  • passphrase

  • salt

  • pwd

Masked values appear as: ***MASKED***

Using --noMask

Only use --noMask when:

  • You need complete configuration for migration

  • Output is being saved to a secure location

  • You're in a development environment

Never commit unmasked configuration files to version control!

Configuration Sources

The command loads configuration from multiple sources in order:

  1. Base Settings: /config/settings.cfm

  2. Environment Settings: /config/[environment]/settings.cfm

  3. Environment Variables: .env file (if exists)

Environment detection checks (in order):

  1. .env file: WHEELS_ENV variable

  2. System environment: WHEELS_ENV variable

  3. System environment: ENVIRONMENT variable

  4. Default: development

File Output Behavior

When using --output:

  • No format specified: Automatically uses JSON format

  • Format specified: Uses the specified format

  • Notification: Displays which format was used in the success message

# These save as JSON
wheels config dump --output=config.json
wheels config dump --output=settings.txt

# This saves as ENV format
wheels config dump --format=env --output=settings.env

# This explicitly saves as table format
wheels config dump --format=table --output=config.txt

Examples

Basic Examples

# View current configuration
wheels config dump

# View testing environment configuration
wheels config dump testing

# Export as JSON to console
wheels config dump --format=json

Advanced Examples

# Complete production backup (unmasked)
wheels config dump production --noMask --output=prod-complete.json

# Generate environment file for Docker
wheels config dump --format=env --output=docker/.env

# Create CFML settings for new environment
wheels config dump --format=cfml --output=config/custom/settings.cfm

# Quick masked backup with date
wheels config dump --output="backups/config-$(date +%Y%m%d-%H%M%S).json"

Pipeline Integration

# CI/CD configuration validation
wheels config dump --format=json | jq '.datasource'

# Environment variable generation for deployment
wheels config dump production --format=env --output=/tmp/app.env

Troubleshooting

No settings.cfm file found

Error: "No settings.cfm file found in config directory" Solution: Ensure you're running the command from your Wheels application root directory

Invalid format specified

Error: "Invalid format: [format]. Valid formats are: table, json, env, cfml" Solution: Use one of the supported formats: table, json, env, or cfml

File write permissions

Error: "Failed to write file" Solution: Ensure you have write permissions for the specified output directory

Environment not detected

Issue: Always shows "development" environment Solution: Set the WHEELS_ENV environment variable or create a .env file with WHEELS_ENV=production

Best Practices

  1. Regular Backups: Schedule regular configuration exports as part of your backup strategy

  2. Version Control: Store masked configuration exports in version control for tracking changes

  3. Environment Validation: Use dumps to verify configuration changes before deployment

  4. Security First: Always use masked output unless absolutely necessary

  5. Documentation: Keep exported configurations with deployment documentation

Support

For issues or questions about the wheels config dump command:

  1. Check the Wheels documentation

  2. Verify your Wheels and CommandBox versions are compatible

  3. Ensure proper file permissions and paths

  4. Review the command output for specific error messages

wheels generate property

Add properties to existing model files with database migrations and view updates.

Synopsis

Parameter Syntax

CommandBox supports multiple parameter formats:

  • Named parameters: name=value (e.g., name=User, columnName=email)

  • Flag parameters: --flag equals flag=true (e.g., --allowNull equals allowNull=true)

  • Flag with value: --flag=value equals flag=value (e.g., --dataType=boolean)

Note: Flag syntax (--flag) avoids positional/named parameter conflicts and is recommended for boolean options.

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
Valid Values
Default

Data Type Options

Type
Database Type
Description

Examples

Basic string property

Creates a string property called firstName on the User model.

Boolean property with default

Creates a boolean property with default value of 0 (false).

Datetime property

Creates a datetime property on the User model.

Decimal property with precision

Creates a decimal property with 10 total digits and 2 decimal places.

String with character limit

Creates a required string property with maximum 50 characters.

What the Command Does

The wheels generate property command performs these actions:

  1. Creates Database Migration: Generates a migration file to add the column to the database

  2. Updates Form View: Adds the property to _form.cfm if it exists

  3. Updates Index View: Adds the property to index.cfm table if it exists

  4. Updates Show View: Adds the property to show.cfm if it exists

  5. Offers Migration: Prompts to run the migration immediately

Generated Files

Database Migration

File: app/migrator/migrations/[timestamp]_add_[columnName]_to_[tableName].cfc

View Updates

When views exist, the command adds the new property to them:

Form View (_form.cfm): Adds appropriate input field

Index View (index.cfm): Adds column to table

Show View (show.cfm): Adds property display

Special Behaviors

Boolean Default Values

When adding boolean properties without a default value, the command automatically sets default=0:

Model File Check

The command checks if the corresponding model file exists and asks for confirmation if it doesn't:

Lowercase Column Names

Column names are automatically converted to lowercase in migrations (following Wheels conventions):

Command Workflow

  1. Validation: Checks if model file exists (optional)

  2. Migration: Creates database migration using wheels dbmigrate create column

  3. Form Update: Adds form field to _form.cfm (if exists)

  4. Index Update: Adds column to index.cfm table (if exists)

  5. Show Update: Adds property display to show.cfm (if exists)

  6. Migration Prompt: Asks if you want to run migration immediately

Troubleshooting

Property Not Added to Views

  • Check that view files exist (_form.cfm, index.cfm, show.cfm)

  • View files must be in /app/views/[modelPlural]/ directory

Migration Fails

  • Ensure model name matches existing table

  • Check that column doesn't already exist

  • Verify database permissions

Boolean Values

  • Boolean properties automatically get default=0 if no default specified

  • Use --default=1 for true default values

Best Practices

  1. Run migrations immediately when prompted to keep database in sync

  2. Use semantic names for properties (firstName, not fname)

  3. Set appropriate defaults for boolean and numeric fields

  4. Consider null constraints based on business logic

  5. Add one property at a time for better change tracking

See Also

  • - Generate models

  • - Create columns

  • - Generate tests

wheels plugin update:all

Update all installed Wheels plugins to their latest versions from ForgeBox.

Usage

Parameters

Parameter
Required
Type
Description

Description

The plugin update:all command checks all installed plugins in the /plugins folder against ForgeBox and updates any outdated plugins to their latest versions. It provides a clear summary of what will be updated and handles each plugin update sequentially.

Features

  • Checks all plugins for updates in one command

  • Color-coded status indicators for each plugin

  • Detailed update summary

  • dryRun mode to preview changes

  • Individual plugin update tracking

  • Helpful error reporting

Examples

Update all plugins

Output (with updates available):

All plugins up to date

Output:

dryRun mode (preview only)

Output:

With some failures

Output:

No plugins installed

Output:

Update Process

  1. Display Header: Shows command is checking for updates

  2. Plugin Discovery: Scans /plugins folder for installed plugins

  3. Version Checking: Queries ForgeBox for each plugin's latest version

  4. Status Display: Shows color-coded status for each plugin

  5. Update List: Displays table of plugins that need updating

  6. Sequential Updates: Updates each plugin one at a time

  7. Progress Tracking: Shows success/failure for each update

  8. Summary Report: Displays final update statistics

  9. Helpful Commands: Suggests next steps

Status Indicators

During checking, each plugin displays:

  • [OUTDATED] (yellow) - Update available, will be updated

  • [OK] (green) - Already at latest version

  • [ERROR] (red) - Could not check version

During updates:

  • [UPDATE] (yellow) - Plugin will be updated

  • [OK] (green) - Update completed successfully

  • [ERROR] (red) - Update failed

dryRun Mode

Use --dryRun to preview updates without actually performing them. This is useful for:

  • Checking what would be updated before committing

  • Testing in CI/CD pipelines

  • Reviewing changes before production updates

  • Planning maintenance windows

The dryRun mode:

  • Checks all plugins for updates

  • Shows what would be updated

  • Does NOT download or install anything

  • Provides command to perform actual updates

Update Strategy

The command updates plugins sequentially:

  1. One plugin at a time (safer than parallel)

  2. Continues updating even if one fails

  3. Tracks success/failure for each plugin

  4. Provides detailed summary at the end

Error Handling

Version Check Failures

Plugins where version cannot be checked are listed separately and skipped for updates.

Update Failures

If a plugin update fails:

  • The failure is tracked

  • Other updates continue

  • Error is reported in summary

  • Plugin can be updated individually later

Network Issues

If ForgeBox cannot be reached:

Best Practices

  1. Regular Updates: Run weekly or monthly to stay current

  2. Test First: Always test updates in development before production

  3. Use dryRun: Preview updates with --dryRun before applying

  4. Read Release Notes: Check ForgeBox for breaking changes

  5. Commit First: Commit your code before updating plugins

  6. Update Individually: For critical plugins, use wheels plugin update <name>

Integration with Other Commands

Check Before Updating

Update Individual Plugins

Notes

  • Only updates plugins from /plugins folder

  • Only works with cfwheels-plugins type packages

  • Updates are performed sequentially (not in parallel)

  • Each update is independent - failures don't affect other updates

  • Requires internet connection to query ForgeBox and download updates

  • Version checks are performed in real-time (not cached)

  • Progress is shown for each plugin update

  • After updates, plugins may require application reload

  • Failed updates can be retried individually with wheels plugin update <name>

  • The command does NOT update plugins that are already at latest version

See Also

  • - Update a single plugin

  • - Check for outdated plugins

  • - List installed plugins

  • - View plugin details

wheels env show

Overview

The wheels env show command displays environment variables from .env files in your Wheels project. This command provides a convenient way to view your application's configuration, with intelligent grouping, security masking, and multiple output formats. It helps you understand what environment variables are available and how they're organized.

Command Syntax

Parameters

Optional Parameters

Parameter
Description
Default

Basic Usage Examples

Show All Variables (Default)

Displays all environment variables from .env in a grouped, readable table format

Show Specific Variable

Shows only the DB_HOST variable and its value

Show in JSON Format

Outputs all variables as formatted JSON

Show from Different File

Displays variables from .env.production instead of .env

Advanced Usage Examples

Development vs Production Comparison

Check Specific Configuration

Output Formats

Table Format (Default)

The table format groups variables by prefix and displays them in an organized, readable way:

JSON Format

Clean JSON output suitable for processing or integration:

Features

Intelligent Grouping

Variables are automatically grouped by prefix for better organization:

  • DB_* variables (database configuration)

  • API_* variables (API settings)

  • WHEELS_* variables (framework settings)

  • Other Variables (ungrouped items)

Security Masking

Sensitive values are automatically masked when displayed:

  • Variables containing password → ********

  • Variables containing secret → ********

  • Variables containing key → ********

The actual values remain unchanged in your files - only the display is masked.

Supported File Formats

Properties Format (Standard .env)

JSON Format

Quote Handling

The command automatically handles quoted values:

  • Double quotes: KEY="value with spaces"

  • Single quotes: KEY='another value'

  • Quotes are stripped from displayed values

Error Handling and Validation

Project Validation

The command ensures you're in a valid Wheels project:

Missing File Handling

If the specified .env file doesn't exist, you'll see helpful guidance:

Key Not Found

When requesting a specific key that doesn't exist:

Common Use Cases

Configuration Review

Debugging Configuration Issues

Environment Setup Verification

Documentation and Export

Integration with Wheels Framework

The command provides helpful tips on how to use the variables in your Wheels application:

Best Practices

1. Regular Configuration Review

2. Environment-Specific Checks

3. Security Verification

4. Documentation Generation

5. Troubleshooting Workflow

Integration Tips

With Other Wheels Commands

CI/CD Integration

Development Workflow

Tips and Shortcuts

  • Grouped display makes it easy to understand related configurations

  • Security masking protects sensitive data during demos or screen sharing

  • JSON output is perfect for automation and integration scripts

  • Helpful error messages guide you when files are missing or keys don't exist

  • Project validation ensures you're running the command in the right location

  • Multiple file support lets you easily compare different environment configurations

wheels get settings

Overview

The wheels get settings command displays the current Wheels application settings for your environment. It shows all configuration settings that are active, including defaults and any custom overrides from your configuration files. You can view all settings or filter to see specific ones.

Command Syntax

Parameters

Optional Parameters

  • settingName - Optional setting name or pattern to filter results. Can be a partial match.

Basic Usage Examples

Display All Settings

Shows all active settings for the current environment

Filter Specific Setting

Shows only the cacheQueries setting

Filter by Pattern

Shows all settings containing "cache" in their name (e.g., cacheQueries, cachePages, cacheImages)

How It Works

Settings Resolution Order

The command resolves settings in the same order as Wheels:

  1. Default Wheels Settings - Built-in framework defaults

  2. Application Settings - From /config/settings.cfm

  3. Environment Settings - From /config/[environment]/settings.cfm

Each level overrides the previous one, with environment-specific settings having the highest priority.

Environment Detection

The command automatically detects the current environment using the same logic as wheels get environment:

  • Checks .env file for WHEELS_ENV

  • Checks system environment variable

  • Checks server.json

  • Defaults to development

Settings Parsing

The command parses set() function calls in your settings files:

Output Examples

All Settings Display

Filtered Settings Display

Single Setting Display

No Matches Found

Common Wheels Settings

Caching Settings

  • cacheActions - Cache action output

  • cacheQueries - Cache database query results

  • cachePages - Cache entire page output

  • cachePartials - Cache partial/template output

  • cacheImages - Cache generated images

  • cacheRoutes - Cache routing configuration

  • cacheModelConfig - Cache model configurations

  • cacheControllerConfig - Cache controller configurations

  • cacheViewConfig - Cache view configurations

  • cacheDatabaseSchema - Cache database schema information

  • cacheFileChecking - Check for file changes when caching

Database Settings

  • dataSourceName - Primary datasource name

  • useExpandedColumnAliases - Use expanded column aliases in queries

  • useTimestampsOnDeletedColumn - Add timestamps to soft-deleted records

  • migratorTableName - Table name for migration versions

Error Handling Settings

  • showDebugInformation - Display debug information

  • showErrorInformation - Display error details

  • sendEmailOnError - Send email notifications on errors

  • errorEmailAddress - Email address for error notifications

  • errorEmailServer - SMTP server for error emails

  • errorEmailSubject - Subject line for error emails

  • includeErrorInEmailSubject - Include error details in email subject

URL Settings

  • URLRewriting - URL rewriting mode (none, partial, full)

Plugin Settings

  • overwritePlugins - Allow plugin overwrites

  • deletePluginDirectories - Delete plugin directories on uninstall

  • loadIncompatiblePlugins - Load plugins with version mismatches

Common Use Cases

Development vs Production Comparison

Verify Database Configuration

Check All Caching Settings

Debugging Configuration Issues

Pre-deployment Verification

Settings File Examples

Basic Application Settings

Environment-Specific Settings

Data Type Support

The command correctly interprets different data types:

Boolean Values

Numeric Values

String Values

Complex Values

Arrays and structs are displayed with summary information:

Error Handling

The command will show an error if:

  • Not run from a Wheels application directory

  • Settings files cannot be read

  • Settings files contain syntax errors

Not a Wheels Application

Read Error

Best Practices

  1. Environment-Specific Configs - Keep environment-specific settings in separate files (/config/[environment]/settings.cfm)

  2. Document Custom Settings - Comment your custom settings in the configuration files

  3. Use Consistent Naming - Follow Wheels naming conventions for custom settings

  4. Verify Before Deployment - Always check settings for the target environment before deploying

  5. Sensitive Data - Keep sensitive settings (API keys, passwords) in environment variables or .env files

Integration with Other Commands

Works well with other Wheels CLI commands:

Tips

  • Setting names are case-insensitive when filtering

  • The filter matches any part of the setting name

  • Settings are displayed in alphabetical order

  • Boolean values display as true or false

  • Complex values (arrays, structs) show a summary

  • The command shows which environment's settings are being displayed

Troubleshooting

Settings Not Showing Expected Values

  1. Check which environment is active: wheels get environment

  2. Verify the settings file exists in the correct location

  3. Check for syntax errors in your settings files

  4. Ensure settings use the correct set() function syntax

Missing Settings

If expected settings are missing:

  • Verify they're defined using set() function

  • Check file paths: /config/settings.cfm and /config/[environment]/settings.cfm

  • Ensure no syntax errors prevent parsing

Filter Not Working

  • Remember the filter is case-insensitive

  • The filter matches any part of the setting name

  • Use more specific terms for precise filtering

Limitations

  • The command parses settings files statically and may not capture all dynamic settings

  • Complex CFML expressions in settings may not be fully evaluated

  • Settings defined outside of set() function calls may not be detected

  • Runtime settings modifications are not reflected

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.

  • wheels init - Bootstrap existing app for CLI

  • wheels info - Display version information

  • wheels reload - Reload application

  • wheels deps - Manage dependencies

  • wheels destroy [type] [name] - Remove generated code

Code Generation

Commands for generating application code and resources.

  • wheels generate app (alias: wheels new) - Create new application

  • wheels generate app-wizard - Interactive app creation

  • wheels generate controller (alias: wheels g controller) - Generate controller

  • wheels generate model (alias: wheels g model) - Generate model

  • wheels generate view (alias: wheels g view) - Generate view

  • wheels generate property - Add model property

  • wheels generate route - Generate route

  • wheels generate test - Generate tests

  • wheels generate snippets - Code snippets

  • wheels generate scaffold - Complete CRUD

Generator Options

Common options across generators:

  • --force - Overwrite existing files

  • --help - Show command help

Database Commands

Commands for managing database schema and migrations.

Database Operations

  • wheels db create - Create database

  • wheels db drop - Drop database

Migration Management

  • wheels dbmigrate info - Show migration status

  • wheels dbmigrate latest - Run all pending migrations

  • wheels dbmigrate up - Run next migration

  • wheels dbmigrate down - Rollback last migration

  • wheels dbmigrate reset - Reset all migrations

  • wheels dbmigrate exec [version] - Run specific migration

Migration Creation

  • wheels dbmigrate create blank [name] - Create empty migration

  • wheels dbmigrate create table [name] - Create table migration

  • wheels dbmigrate create column [table] [column] - Add column migration

  • wheels dbmigrate remove table [name] - Drop table migration

Testing Commands

Commands for running and managing tests.

  • wheels test run - Run tests

  • wheels test all - Run all tests

  • wheels test coverage - Run coverage tests

  • wheels test integration - Run integration tests

  • wheels test unit - Run unit tests

  • wheels test watch - Rerun tests on any change

Environment Management

Commands for managing development environments and application context.

  • wheels env setup [name] - Setup environment

  • wheels env list - List environments

  • wheels env merge - Merge env files

  • wheels env set - Set env variable

  • wheels env show - Show env variables

Code Analysis

Commands for analyzing code quality and patterns.

  • wheels analyze code - Analyze code quality

  • wheels analyze performance - Performance analysis

  • wheels analyze security - Security analysis

Docker Commands

Commands for Docker container management and deployment.

  • wheels docker init - Initialize Docker configuration

  • wheels docker deploy - Deploy using Docker

Command Patterns

Command Aliases

Many commands have shorter aliases:

Common Workflows

Creating a new feature:

Starting development:

Deployment preparation:

Environment Variables

Variable
Description
Default

Exit Codes

Code
Description

See Also

wheels env show [options]

--key

Show a specific environment variable by key name

-

--format

Output format: table or json

table

--file

Specific .env file to read

.env

wheels env show
wheels env show --key=DB_HOST
wheels env show --format=json
wheels env show --file=.env.production
# View development variables
wheels env show --file=.env.development

# View production variables  
wheels env show --file=.env.production
# Check database configuration
wheels env show --key=DB_NAME
wheels env show --key=DB_HOST

# Check API settings
wheels env show --key=API_KEY
Environment Variables Viewer

Environment Variables from .env:

╔════════╤══════════════════════════╤═══════════════════════════╗
║ Source │ Variable                 │ Value                     ║
╠════════╪══════════════════════════╪═══════════════════════════╣
║ .env   │ DB_HOST                  │ localhost                 ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ DB_NAME                  │ myapp                     ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ DB_PASSWORD              │ ********                  ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ DB_PORT                  │ 3306                      ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ DB_USER                  │ wheels                    ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ API_BASE_URL             │ https://api.example.com   ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ API_KEY                  │ ********                  ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ API_TIMEOUT              │ 30                        ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ WHEELS_ENV               │ development               ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ WHEELS_RELOAD_PASSWORD   │ ********                  ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ APP_NAME                 │ My Application            ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ DEBUG_MODE               │ true                      ║
╟────────┼──────────────────────────┼───────────────────────────╢
║ .env   │ PORT                     │ 3000                      ║
╚════════╧══════════════════════════╧═══════════════════════════╝

Tip: Access these in your app with application.env['KEY_NAME']
Or use them in config files: set(dataSourceName=application.env['DB_NAME'])
Wheels automatically loads .env on application start
{
  "API_BASE_URL": "https://api.example.com",
  "API_KEY": "********",
  "APP_NAME": "My Application",
  "DB_HOST": "localhost",
  "DB_NAME": "myapp",
  "DB_PASSWORD": "********",
  "DB_PORT": "3306",
  "DEBUG_MODE": "true",
  "WHEELS_ENV": "development"
}
## Database Configuration
DB_HOST=localhost
DB_PORT=3306
DB_NAME=myapp
DB_USER=wheels
DB_PASSWORD="secret123"

## Application Settings  
WHEELS_ENV=development
DEBUG_MODE=true
{
  "DB_HOST": "localhost",
  "DB_PORT": "3306",
  "DB_NAME": "myapp",
  "WHEELS_ENV": "development",
  "DEBUG_MODE": "true"
}
This command must be run from a Wheels project root directory
No .env file found in project root

Create a .env file with key=value pairs, for example:

## Database Configuration
DB_HOST=localhost
DB_PORT=3306
DB_NAME=myapp
DB_USER=wheels
DB_PASSWORD=secret

## Application Settings
WHEELS_ENV=development
WHEELS_RELOAD_PASSWORD=mypassword
Environment variable 'MISSING_KEY' not found

Available keys in .env:
  - API_KEY
  - DB_HOST
  - DB_NAME
  - DEBUG_MODE
  - WHEELS_ENV
# Review all current settings
wheels env show

# Check what's different between environments
wheels env show --file=.env.development
wheels env show --file=.env.production
# Check if a specific variable is set
wheels env show --key=DB_HOST

# Verify API configuration
wheels env show --key=API_BASE_URL
wheels env show --key=API_KEY
# Verify development setup
wheels env show --file=.env.development

# Check staging configuration
wheels env show --file=.env.staging
# Generate configuration documentation
wheels env show --format=json > docs/environment-config.json

# Create environment template
wheels env show --file=.env.example
<!-- In your Wheels application -->
<cfset dataSource = application.env['DB_NAME']>
<cfset apiKey = application.env['API_KEY']>
<cfset debugMode = application.env['DEBUG_MODE']>

<!-- In config files -->
<cfset set(dataSourceName=application.env['DB_NAME'])>
<cfset set(URLRewriting=application.env['URL_REWRITING'])>
# Regularly review your environment configuration
wheels env show
# Always verify environment-specific settings
wheels env show --file=.env.production --key=WHEELS_ENV
wheels env show --file=.env.development --key=DEBUG_MODE
# Check that sensitive values are properly set
wheels env show --key=API_KEY
wheels env show --key=DB_PASSWORD
# Generate configuration documentation
wheels env show --format=json > config-docs.json
# When debugging configuration issues:
# 1. Check if variable exists
wheels env show --key=PROBLEMATIC_VAR

# 2. Review all variables for typos
wheels env show

# 3. Compare against working environment
wheels env show --file=.env.working
# View current config, then update if needed
wheels env show --key=DB_HOST
wheels env set DB_HOST=newhost.com

# Check merged configuration
wheels env merge .env.base .env.local --dry-run
wheels env show --file=.env.merged
# In deployment scripts
wheels env show --file=.env.production --format=json | jq '.DB_HOST'
# Quick environment check during development
wheels env show --key=WHEELS_ENV
wheels env show --key=DEBUG_MODE
wheels get settings [settingName]
wheels get settings
wheels get settings cacheQueries
wheels get settings cache
// config/settings.cfm
set(dataSourceName="myapp_db");
set(cacheQueries=true);
set(errorEmailAddress="[email protected]");
Wheels Settings (development environment):

allowConcurrentRequestScope:   false
cacheActions:                  false
cacheCullInterval:              5
cacheCullPercentage:            10
cacheDatabaseSchema:            false
cacheFileChecking:              false
cacheImages:                    false
cacheModelConfig:               false
cachePages:                     false
cachePartials:                  false
cacheQueries:                   false
cacheRoutes:                    false
dataSourceName:                 myapp_db
errorEmailAddress:              [email protected]
showDebugInformation:           true
showErrorInformation:           true
URLRewriting:                   partial

Total settings: 17
wheels get settings cache
Wheels Settings (development environment):

cacheActions:                   false
cacheCullInterval:              5
cacheCullPercentage:            10
cacheDatabaseSchema:            false
cacheFileChecking:              false
cacheImages:                    false
cacheModelConfig:               false
cachePages:                     false
cachePartials:                  false
cacheQueries:                   true
cacheRoutes:                    false

Total settings: 11
wheels get settings dataSourceName
Wheels Settings (production environment):

dataSourceName:                 production_db

Total settings: 1
wheels get settings nonexistent
No settings found matching 'nonexistent'
# Check development settings
WHEELS_ENV=development wheels get settings cache

# Check production settings
WHEELS_ENV=production wheels get settings cache
wheels get settings dataSourceName
wheels get settings cache
# See all current settings
wheels get settings

# Check specific problematic setting
wheels get settings showDebugInformation
# Verify production settings
WHEELS_ENV=production wheels get settings
// config/settings.cfm
set(dataSourceName="myapp");
set(URLRewriting="partial");
set(errorEmailAddress="[email protected]");
// config/production/settings.cfm
set(cacheQueries=true);
set(cachePages=true);
set(cachePartials=true);
set(showDebugInformation=false);
set(showErrorInformation=false);
set(sendEmailOnError=true);
// config/development/settings.cfm
set(cacheQueries=false);
set(showDebugInformation=true);
set(showErrorInformation=true);
set(sendEmailOnError=false);
set(cacheQueries=true);
set(showDebugInformation=false);
set(cacheCullInterval=5);
set(cacheCullPercentage=10);
set(dataSourceName="myapp_db");
set(errorEmailAddress="[email protected]");
complexSetting:                 [array with 5 items]
structSetting:                  {3 items}
Error: This command must be run from a Wheels application directory
Error reading settings: [specific error message]
Details: [additional error details if available]
# Check environment, then settings
wheels get environment
wheels get settings

# Verify cache settings before clearing cache
wheels get settings cache
wheels clear cache

# Check database settings before running migrations
wheels get settings dataSourceName
wheels db migrate
wheels generate property name=<modelName> columnName=<propertyName> [options]

#Can also be used as:
wheels g property name=<modelName> columnName=<propertyName> [options]

name

Model name (table name)

Required

columnName

Name of column to add

Required

dataType

Type of column

biginteger, binary, boolean, date, datetime, decimal, float, integer, string, text, time, timestamp, uuid

string

default

Default value for column

Any valid default value

""

allowNull

Whether to allow null values

true, false

true

limit

Character or integer size limit

Numeric value

0

precision

Precision for decimal columns

Numeric value

0

scale

Scale for decimal columns

Numeric value

0

biginteger

BIGINT

Large integer values

binary

BLOB

Binary data

boolean

BOOLEAN

Boolean (true/false) values

date

DATE

Date only

datetime

DATETIME

Date and time

decimal

DECIMAL

Decimal numbers with precision/scale

float

FLOAT

Floating point numbers

integer

INTEGER

Integer values

string

VARCHAR

Variable character strings

text

TEXT

Long text content

time

TIME

Time only

timestamp

TIMESTAMP

Timestamp values

uuid

VARCHAR(35)

UUID/GUID strings

wheels generate property name=User columnName=firstName
wheels generate property name=User columnName=isActive --dataType=boolean --default=0
wheels generate property name=User columnName=lastLoggedIn --dataType=datetime
wheels generate property name=Product columnName=price --dataType=decimal --precision=10 --scale=2
wheels generate property name=User columnName=username --dataType=string --limit=50 --allowNull=false
component extends="wheels.migrator.Migration" {

    function up() {
        transaction {
            addColumn(
                table = "users",
                columnName = "firstName",
                columnType = "string",
                limit = 255,
                allowNull = true
            );
        }
    }

    function down() {
        transaction {
            removeColumn(table = "users", columnName = "firstName");
        }
    }
}
<!-- Added to _form.cfm -->
#textField(objectName="user", property="firstName")#
<!-- Added to table header -->
<th>First Name</th>

<!-- Added to table body -->
<td>#user.firstName#</td>
<!-- Added to show.cfm -->
<p><strong>First Name:</strong> #user.firstName#</p>
wheels generate property name=User columnName=isActive --dataType=boolean
# Automatically gets default=0
Hold On! We couldn't find a corresponding Model at /app/models/User.cfc:
are you sure you wish to add the property 'email' to users? [y/n]
# Input: columnName=firstName
# Migration: columnName=firstname
wheels generate model
wheels dbmigrate create column
wheels generate test
wheels plugin update:all [--dryRun]

dryRun

No

boolean

Preview updates without actually installing them

wheels plugin update:all
===========================================================
  Checking for Plugin Updates
===========================================================

  bcrypt                                  [OUTDATED] 0.0.3 -> 0.0.4
  shortcodes                              [OUTDATED] 0.0.3 -> 0.0.4
  wheels-test                             [OK] v1.0.0

===========================================================

Found 2 outdated plugins

Updating Plugins:

Plugin                Current         Latest          Status
---------------------------------------------------------------
bcrypt                0.0.3           0.0.4           [UPDATE]
shortcodes            0.0.3           0.0.4           [UPDATE]

-----------------------------------------------------------

Updating bcrypt from 0.0.3 to 0.0.4...
[CommandBox installation output...]
[OK] bcrypt updated successfully

Updating shortcodes from 0.0.3 to 0.0.4...
[CommandBox installation output...]
[OK] shortcodes updated successfully

===========================================================
  Update Summary
===========================================================

[OK] 2 plugins updated successfully

Updated plugins:
  - bcrypt (0.0.3 -> 0.0.4)
  - shortcodes (0.0.3 -> 0.0.4)

Commands:
  wheels plugin list              View all installed plugins
  wheels plugin outdated          Check for more updates
wheels plugin update:all
===========================================================
  Checking for Plugin Updates
===========================================================

  bcrypt                                  [OK] v0.0.4
  shortcodes                              [OK] v0.0.4
  wheels-test                             [OK] v1.0.0

===========================================================

[OK] All plugins are already up to date!

No updates required.

Commands:
  wheels plugin list              View all installed plugins
  wheels plugin outdated          Check for updates
wheels plugin update:all --dryRun
===========================================================
  Checking for Plugin Updates (DRY RUN)
===========================================================

  bcrypt                                  [OUTDATED] 0.0.3 -> 0.0.4
  shortcodes                              [OUTDATED] 0.0.3 -> 0.0.4
  wheels-test                             [OK] v1.0.0

===========================================================

Found 2 outdated plugins

Would Update:

Plugin                Current         Latest
---------------------------------------------------------------
bcrypt                0.0.3           0.0.4
shortcodes            0.0.3           0.0.4

-----------------------------------------------------------

[DRY RUN] No updates performed

To perform these updates:
  wheels plugin update:all
wheels plugin update:all
===========================================================
  Checking for Plugin Updates
===========================================================

  bcrypt                                  [OUTDATED] 0.0.3 -> 0.0.4
  problematic-plugin                      [ERROR] Could not check version
  shortcodes                              [OK] v0.0.4

===========================================================

Found 1 outdated plugin

Updating Plugins:

Plugin                Current         Latest          Status
---------------------------------------------------------------
bcrypt                0.0.3           0.0.4           [UPDATE]

-----------------------------------------------------------

Updating bcrypt from 0.0.3 to 0.0.4...
[CommandBox installation output...]
[OK] bcrypt updated successfully

===========================================================
  Update Summary
===========================================================

[OK] 1 plugin updated successfully

Updated plugins:
  - bcrypt (0.0.3 -> 0.0.4)

Could not check 1 plugin:
  - problematic-plugin

Commands:
  wheels plugin list              View all installed plugins
  wheels plugin outdated          Check for more updates
wheels plugin update:all
===========================================================
  Checking for Plugin Updates
===========================================================

No plugins installed in /plugins folder
Install plugins with: wheels plugin install <plugin-name>
wheels plugin update:all --dryRun
===========================================================
  Checking for Plugin Updates
===========================================================

  bcrypt                                  [ERROR] Could not check version
  shortcodes                              [ERROR] Could not check version

===========================================================

[ERROR] Unable to check for updates

Could not check 2 plugins due to network issues.
Please check your internet connection and try again.
# See which plugins are outdated
wheels plugin outdated

# Update all outdated plugins
wheels plugin update:all
# Update all plugins except one
wheels plugin update:all --dryRun  # See what would update
wheels plugin update plugin1        # Update individually
wheels plugin update plugin2
wheels plugin update
wheels plugin outdated
wheels plugin list
wheels plugin info

wheels generate app [name]

Create new application

wheels generate scaffold [name]

Generate complete CRUD

wheels dbmigrate latest

Run database migrations

wheels test run

Run application tests

wheels reload

Reload application

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 generate scaffold name=product properties=name:string,price:decimal
wheels dbmigrate latest
wheels test run
wheels reload            # Reload the application
wheels test run          # Run tests
wheels test run
wheels analyze security
wheels analyze performance
wheels dbmigrate info

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

Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Documentation
Quick Start Guide
CLI Development Guides
Service Architecture
Migrations Guide
Testing Guide

wheels config check

Overview

The wheels config check command validates your Wheels application configuration settings across different environments. It performs comprehensive checks on security settings, database configuration, environment-specific settings, and more, helping you identify potential issues before deployment.

Command Syntax

wheels config check [environment] [--verbose] [--fix]

Parameters

Parameter
Type
Required
Description

environment

string

No

The environment to check (development, testing, production). If not specified, detects from current configuration

--verbose

flag

No

Show detailed validation information including fix suggestions

--fix

flag

No

Attempt to automatically fix certain issues

Basic Usage

Check Current Environment

wheels config check

Check Specific Environment

wheels config check production

Check with Detailed Output

wheels config check --verbose

Auto-fix Issues

wheels config check --fix

Combined Options

wheels config check production --verbose --fix

What Gets Checked

The command performs validation across multiple configuration areas:

1. Configuration Files

  • Verifies existence of config/settings.cfm

  • Checks for environment-specific settings files

  • Validates file structure and syntax

2. Required Settings

  • Datasource Configuration: Ensures a datasource is configured

  • Core Settings: Validates essential Wheels configuration parameters

3. Security Configuration

  • Sensitive Values: Detects hardcoded passwords, API keys, tokens

  • Debug Mode: Ensures debug is disabled in production

  • Error Emails: Checks error notification setup for production

  • Reload Password: Validates reload password strength

  • SSL/HTTPS: Verifies SSL enforcement in production

  • Session Security: Checks session timeout settings

  • Error Information: Ensures error details are hidden in production

4. Database Configuration

  • Datasource Validity: Verifies datasource exists and is accessible

  • Migration Settings: Checks auto-migration configuration

  • Connection Settings: Validates database connection parameters

5. Environment Settings

  • Environment Directory: Checks for environment-specific config directories

  • Caching Configuration: Validates cache settings for production

  • Performance Settings: Reviews optimization configurations

6. .env File Configuration

  • File Existence: Checks for .env file presence

  • File Permissions: Validates security permissions

  • Git Ignore: Ensures .env is in .gitignore

  • Environment Variables: Verifies WHEELS_ENV or Environment variable

7. Production-Specific (when checking production)

  • SSL Enforcement: Validates forceSSL setting

  • Session Management: Reviews session timeout configuration

  • Error Handling: Ensures proper error information hiding

  • Cache Settings: Verifies all caching is enabled

Output Format

The command provides real-time status as it performs checks:

========================================
Configuration Validation
Environment: development
========================================

Checking configuration files... [OK]
Checking required settings... [OK]
Checking security configuration... [WARNING]
Checking database configuration... [OK]
Checking environment-specific settings... [WARNING]
Checking .env file configuration... [FAILED]

========================================

Status Indicators

  • [OK] - Check passed successfully

  • [WARNING] - Non-critical issues found

  • [FAILED] - Critical errors detected

  • [FIXED] - Issue was automatically fixed

Results Display

Error Output

[ERRORS] (2):
   - Missing config/settings.cfm file
   - Datasource 'myapp_db' not found

Warning Output

[WARNINGS] (3):
   - Possible hardcoded sensitive value in 'apiKey'
   - No environment-specific config directory for 'production'
   - Automatic database migration is enabled

Fixed Issues Output

[FIXED] Issues:
   - Created sample .env file
   - Added .env to .gitignore

Summary Output

[PASSED] Configuration validation successful!
  All checks completed successfully.

Or with issues:

[FAILED] Configuration check failed
  Found: 2 errors, 3 warnings
  
  Tip: Run with --verbose flag for detailed fix suggestions

Verbose Mode

When using --verbose, each issue includes detailed fix suggestions:

wheels config check --verbose

Output:

[ERRORS] (1):
   - Debug mode is enabled in production
     --> Fix: Set showDebugInformation = false in config/production/settings.cfm

Auto-Fix Feature

The --fix flag attempts to automatically resolve certain issues:

What Can Be Auto-Fixed

  • Create sample .env file if missing

  • Add .env to .gitignore

  • Create basic configuration templates

  • Set default secure values

Example

wheels config check --fix

Output:

[FIXED] Issues:
   - Created sample .env file
   - Added .env to .gitignore

Environment Detection

The command detects the current environment in the following priority:

  1. Command parameter (if specified)

  2. WHEELS_ENV in .env file

  3. Environment in .env file

  4. WHEELS_ENV system environment variable

  5. Environment system environment variable

  6. Default to 'development'

Common Use Cases

Pre-Deployment Check

# Check production configuration before deployment
wheels config check production --verbose

Development Setup Validation

# Ensure development environment is properly configured
wheels config check development --fix

CI/CD Pipeline Integration

# In your CI/CD script
wheels config check production
if [ $? -ne 0 ]; then
    echo "Configuration validation failed!"
    exit 1
fi

Security Audit

# Check for security issues across all environments
wheels config check development --verbose
wheels config check testing --verbose
wheels config check production --verbose

Exit Codes

The command returns different exit codes for scripting:

  • 0 - All checks passed (may have warnings)

  • 1 - One or more errors found

Best Practices

  1. Run Before Deployment - Always validate production configuration before deploying

  2. Use in CI/CD - Include configuration checks in your automated pipelines

  3. Regular Audits - Periodically check all environments for security issues

  4. Fix Warnings - While warnings don't fail the check, addressing them improves security and performance

  5. Version Control - After using --fix, review and commit the changes

  6. Environment-Specific Configs - Create separate configuration directories for each environment

Troubleshooting

Command Not Found

Error: This command must be run from a Wheels application directory

Solution: Run the command from your Wheels application root directory

Cannot Read Configuration

Error reading configuration: [error message]

Solution: Check file permissions and ensure configuration files are valid CFML

Datasource Not Found

Datasource 'myapp_db' not found

Solution: Configure the datasource in your CFML administrator or Application.cfc

Permission Issues with .env

.env file has overly permissive permissions

Solution: Run chmod 600 .env to restrict file permissions

Configuration File Examples

Basic config/settings.cfm

<cfscript>
// Application settings
set(dataSourceName = application.env["DB_NAME"]);
set(reloadPassword = application.env["RELOAD_PASSWORD"]);

// Security settings
set(showDebugInformation = false);
set(showErrorInformation = false);

// Performance settings
set(cacheQueries = true);
set(cachePartials = true);
</cfscript>

Environment-specific config/production/settings.cfm

<cfscript>
// Production overrides
set(showDebugInformation = false);
set(sendEmailOnError = true);
set(errorEmailAddress = "[email protected]");
set(forceSSL = true);

// Enable all caching
set(cacheControllerConfig = true);
set(cacheDatabaseSchema = true);
set(cacheFileChecking = true);
set(cacheImages = true);
set(cacheModelConfig = true);
set(cachePartials = true);
set(cacheQueries = true);
set(cacheRoutes = true);
</cfscript>

Sample .env file

# Wheels Environment Configuration
WHEELS_ENV=development

# Database Configuration
DB_HOST=localhost
DB_PORT=3306
DB_NAME=myapp_development
DB_USER=dbuser
DB_PASSWORD=secure_password

# Application Settings
RELOAD_PASSWORD=change_this_password
SECRET_KEY=your_secret_key_here
API_KEY=your_api_key

Related Commands

  • wheels get environment - Display current environment setting

  • wheels env merge - Merge environment configurations

  • wheels db create - Create database for current environment

  • wheels test - Run tests in current environment

Security Considerations

  1. Never commit .env files - Always keep .env in .gitignore

  2. Use environment variables - Don't hardcode sensitive values

  3. Restrict file permissions - Set appropriate permissions on configuration files

  4. Different passwords per environment - Use unique credentials for each environment

  5. Enable SSL in production - Always force SSL for production environments

  6. Hide error details - Never show debug information in production

Tips

  • Run with --verbose first to understand all issues before using --fix

  • Create environment-specific directories even if empty for better organization

  • Use the command as part of your deployment checklist

  • Keep configuration files well-commented for team members

  • Regularly update and review security settings

  • Use strong, unique reload passwords for each environment

  • Document any custom configuration requirements for your team

Configuration Management

Learn how to manage configuration in Wheels using environment variables, settings files, and CLI tools for configuration management.

This guide covers configuration management in Wheels, including working with environment variables, settings files, and the CLI tools for managing configuration.

Overview

Wheels provides flexible configuration management through:

  • Environment-specific settings files

  • Environment variables (.env files)

  • CLI commands for configuration management

  • Security best practices

Environment Variables

.env File Format

Wheels supports two formats for .env files:

Properties Format (Recommended)

# Database Configuration
DB_HOST=localhost
DB_PORT=3306
DB_NAME=myapp_development
DB_USER=wheels
DB_PASSWORD=secretpassword

# Application Settings
WHEELS_ENV=development
RELOAD_PASSWORD=myreloadpassword
SECRET_KEY=a1b2c3d4e5f6g7h8i9j0

# Feature Flags
DEBUG_MODE=true
CACHE_ENABLED=false

JSON Format

{
  "DB_HOST": "localhost",
  "DB_PORT": 3306,
  "DB_NAME": "myapp_development",
  "DB_USER": "wheels",
  "DB_PASSWORD": "secretpassword",
  "WHEELS_ENV": "development",
  "DEBUG_MODE": true
}

Environment-Specific Files

Wheels automatically loads environment-specific .env files:

  1. .env - Base configuration (always loaded first)

  2. .env.{environment} - Environment-specific overrides (loaded second)

Example structure:

myapp/
├── .env                 # Base configuration
├── .env.development     # Development overrides
├── .env.testing        # Testing overrides
├── .env.production     # Production settings
└── .gitignore          # MUST exclude .env files!

Variable Interpolation

Use ${VAR} syntax to reference other variables:

# .env
APP_NAME=MyWheelsApp
APP_ENV=development

# Database URLs with interpolation
DB_HOST=localhost
DB_PORT=3306
DB_NAME=${APP_NAME}_${APP_ENV}
DB_URL=mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}

# API endpoints
API_BASE_URL=https://api.example.com
API_USERS_URL=${API_BASE_URL}/users
API_ORDERS_URL=${API_BASE_URL}/orders

Type Casting

Wheels automatically casts certain values:

# Booleans (cast to true/false)
DEBUG_MODE=true
CACHE_ENABLED=false

# Numbers (cast to numeric values)
MAX_CONNECTIONS=100
CACHE_TTL=3600
REQUEST_TIMEOUT=30

# Strings (remain as strings)
APP_NAME=MyApp
API_KEY=abc123def456

Using Environment Variables in Settings

Access environment variables in your settings files:

// config/settings.cfm
// Using application.env struct
set(dataSourceName = application.env['DB_NAME']);
set(dataSourceUserName = application.env['DB_USER']);
set(dataSourcePassword = application.env['DB_PASSWORD']);

// With defaults
set(cacheQueries = application.env['CACHE_ENABLED'] ?: false);
set(reloadPassword = application.env['RELOAD_PASSWORD'] ?: 'defaultpassword');

// Environment-specific logic
if (application.env['WHEELS_ENV'] == 'production') {
    set(showDebugInformation = false);
    set(cacheFileChecking = true);
}

Configuration Files

Directory Structure

config/
├── settings.cfm           # Base configuration
├── development/
│   └── settings.cfm      # Development overrides
├── testing/
│   └── settings.cfm      # Testing overrides
├── production/
│   └── settings.cfm      # Production overrides
└── maintenance/
    └── settings.cfm      # Maintenance mode settings

Settings Precedence

Settings are loaded in this order (later overrides earlier):

  1. Framework defaults

  2. config/settings.cfm

  3. config/{environment}/settings.cfm

  4. Plugin settings

  5. Runtime set() calls

Common Configuration Patterns

Database Configuration

// config/settings.cfm
set(dataSourceName = application.env['DB_NAME'] ?: 'wheelstutorial');
set(dataSourceUserName = application.env['DB_USER'] ?: 'root');
set(dataSourcePassword = application.env['DB_PASSWORD'] ?: '');

Environment-Specific Settings

// config/production/settings.cfm
// Production optimizations
set(cacheQueries = true);
set(cachePartials = true);
set(cachePages = true);
set(cacheActions = true);
set(cacheImages = true);
set(cacheModelConfig = true);
set(cacheControllerConfig = true);
set(cacheRoutes = true);
set(cacheDatabaseSchema = true);
set(cacheFileChecking = false);

// Security
set(showDebugInformation = false);
set(showErrorInformation = false);

// Error handling
set(sendEmailOnError = true);
set(errorEmailAddress = application.env['ERROR_EMAIL']);

CLI Configuration Commands

Dumping Configuration

Export current configuration:

# View current config (masked)
wheels config dump

# Export production config as JSON
wheels config dump production --format=json --output=prod-config.json

# Export as .env format
wheels config dump --format=env --output=config.env

# Export without masking (careful!)
wheels config dump --no-mask

Checking Configuration

Validate configuration for issues:

# Basic check
wheels config check

# Check production with fixes
wheels config check production --fix

# Verbose output with suggestions
wheels config check --verbose

Common checks performed:

  • Missing required settings

  • Hardcoded sensitive values

  • Production security settings

  • Database configuration

  • Caching optimizations

Comparing Environments

Find differences between environments:

# Full comparison
wheels config diff development production

# Show only differences
wheels config diff development production --changes-only

# Export as JSON
wheels config diff testing production --format=json > diff.json

Managing Secrets

Generate secure secrets:

# Generate and display
wheels secret
wheels secret --type=base64 --length=48

# Save to .env
wheels secret --save-to-env=SECRET_KEY
wheels secret --type=uuid --save-to-env=API_KEY

# Multiple secrets
wheels secret --save-to-env=SESSION_SECRET
wheels secret --save-to-env=CSRF_TOKEN
wheels secret --save-to-env=ENCRYPTION_KEY

Environment Variable Management

Setting Variables

# Set single variable
wheels env set DB_HOST=localhost

# Set multiple
wheels env set DB_HOST=localhost DB_PORT=3306 DB_NAME=myapp

# Update production file
wheels env set --file=.env.production API_URL=https://api.example.com

Validating Files

# Basic validation
wheels env validate

# Check required variables
wheels env validate --required=DB_HOST,DB_USER,DB_PASSWORD

# Validate production
wheels env validate --file=.env.production --verbose

Merging Files

# Merge for deployment
wheels env merge .env .env.production --output=.env.deployed

# Preview merge
wheels env merge .env.defaults .env.local --dry-run

Security Best Practices

1. Never Commit Secrets

Always add .env files to .gitignore:

# Environment files
.env
.env.*
!.env.example
!.env.defaults

2. Use Strong Secrets

Generate cryptographically secure values:

# For session secrets
wheels secret --type=hex --length=64 --save-to-env=SESSION_SECRET

# For API keys
wheels secret --type=base64 --length=48 --save-to-env=API_SECRET

# For passwords
wheels secret --type=alphanumeric --length=32

3. Validate Production Config

Run checks before deployment:

# Full production check
wheels config check production --verbose

# Required checks
wheels env validate --file=.env.production \
  --required=DB_HOST,DB_USER,DB_PASSWORD,SECRET_KEY,RELOAD_PASSWORD

4. Mask Sensitive Output

Always use masking in logs/output:

// In your code
writeOutput("Database: #application.env['DB_NAME']#");
writeOutput("Password: ***MASKED***"); // Never output passwords

5. Environment-Specific Secrets

Use different secrets per environment:

# .env.development
SECRET_KEY=dev_secret_key_only_for_local

# .env.production
SECRET_KEY=prod_0a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0

Common Patterns

Multi-Environment Setup

# 1. Create base config
wheels env set WHEELS_ENV=development DB_NAME=myapp_dev

# 2. Create production config
wheels env set --file=.env.production \
  WHEELS_ENV=production \
  DB_NAME=myapp_prod \
  DB_HOST=prod.database.com

# 3. Create testing config
wheels env set --file=.env.testing \
  WHEELS_ENV=testing \
  DB_NAME=myapp_test

# 4. Validate all
wheels env validate
wheels env validate --file=.env.production
wheels env validate --file=.env.testing

Deployment Configuration

# 1. Merge configs for deployment
wheels env merge .env.defaults .env.production --output=.env.deploy

# 2. Validate merged config
wheels env validate --file=.env.deploy \
  --required=DB_HOST,DB_USER,DB_PASSWORD,SECRET_KEY

# 3. Check security
wheels config check production --verbose

Local Development

# 1. Copy example file
cp .env.example .env

# 2. Set local values
wheels env set DB_HOST=localhost DB_USER=root DB_PASSWORD=

# 3. Generate secrets
wheels secret --save-to-env=SECRET_KEY
wheels secret --save-to-env=RELOAD_PASSWORD

# 4. Validate
wheels env validate

Troubleshooting

Environment Variables Not Loading

  1. Check file exists and is readable

  2. Verify format (properties vs JSON)

  3. Check for syntax errors:

    wheels env validate

Wrong Environment Loading

  1. Check WHEELS_ENV variable:

    wheels env show --key=WHEELS_ENV
  2. Verify environment detection order:

    • .env file WHEELS_ENV

    • System environment WHEELS_ENV

    • Default to 'development'

Interpolation Not Working

  1. Ensure variables are defined before use

  2. Check syntax: ${VAR_NAME}

  3. Maximum 10 interpolation passes (prevent loops)

Sensitive Values Exposed

  1. Run security check:

    wheels config check --verbose
  2. Move hardcoded values to .env

  3. Use application.env references

Best Practices Summary

  1. Use .env files for all environment-specific values

  2. Never commit secrets - use .gitignore

  3. Generate strong secrets with wheels secret

  4. Validate configuration before deployment

  5. Use interpolation to reduce duplication

  6. Environment-specific files for overrides

  7. Check security regularly with CLI tools

  8. Document required variables in .env.example

  9. Mask sensitive values in output

  10. Test configuration changes in development first

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. If the datasource doesn't exist, the command offers an interactive wizard to create it for you, supporting MySQL, PostgreSQL, SQL Server, Oracle, and H2 databases.

Key Features

  • Automatic .env file reading: Reads actual database credentials from .env.{environment} files using generic DB_* variable names

  • Interactive datasource creation: Prompts for credentials when datasource doesn't exist

  • Environment validation: Checks if environment exists before prompting for credentials

  • Smart error handling: Single, clear error messages without duplication

  • Post-creation setup: Automatically creates environment files and writes datasource to app.cfm after successful database creation

Options

Option
Type
Default
Description

Examples:

Examples

Basic Usage

Create database using default datasource:

Specific Datasource

Create database for development:

Create database for testing:

Custom Database Name

Create database with specific name:

Force Recreation

Drop existing database and recreate:

Interactive Datasource Creation

If the specified datasource doesn't exist, the command will prompt you to create it interactively:

The datasource will be saved to both /config/app.cfm and CFConfig.json.

Database-Specific Behavior

MySQL/MariaDB

  • Creates database with UTF8MB4 character set

  • Uses utf8mb4_unicode_ci collation

  • Connects to information_schema system database

  • Supports MySQL 5.x, MySQL 8.0+, and MariaDB drivers

  • Default port: 3306

PostgreSQL

  • Creates database with UTF8 encoding

  • Uses en_US.UTF-8 locale settings

  • Terminates active connections before dropping (when using --force)

  • Connects to postgres system database

  • Default port: 5432

SQL Server

  • Creates database with default settings

  • Connects to master system database

  • Supports Microsoft SQL Server JDBC driver

  • Default port: 1433

Oracle

  • Creates a USER/schema (Oracle's equivalent of a database)

  • Grants CONNECT and RESOURCE privileges automatically

  • Connects using SID (e.g., FREE, ORCL, XE)

  • Supports Oracle 12c+ with Container Database (CDB) architecture

  • Uses _ORACLE_SCRIPT session variable for non-C## users

  • Important: Database names cannot contain hyphens (use underscores)

  • Default port: 1521

  • Default SID: FREE (Oracle XE)

H2

  • Embedded database - no server required

  • Database file created automatically on first connection

  • Only prompts for database name and optional credentials

  • No host/port configuration needed

  • Ideal for development and testing

Output Format

The command provides real-time, formatted output showing each step:

Prerequisites

⚠️ Note: This command depends on configuration values. Please verify your database configuration before executing it.

  1. Datasource Configuration: The datasource can be configured in /config/app.cfm or created interactively

  2. Database Privileges: The database user must have CREATE DATABASE privileges (CREATE USER for Oracle)

  3. Network Access: The database server must be accessible

  4. JDBC Drivers: Appropriate JDBC drivers must be available in the classpath

Oracle JDBC Driver Installation

If you see "Driver not found" error for Oracle, you need to manually install the Oracle JDBC driver:

Steps to install Oracle JDBC driver:

  1. Download the driver from Oracle's official website:

    • Visit:

    • Download the appropriate ojdbc JAR file (e.g., ojdbc11.jar or ojdbc8.jar)

  2. Place the JAR file in CommandBox's JRE library directory:

    Common paths:

    • Windows: C:\Program Files\CommandBox\jre\lib\

    • Mac/Linux: /usr/local/lib/CommandBox/jre/lib/

  3. Restart CommandBox completely:

    • Important: Close ALL CommandBox instances

    • Don't just run reload - fully exit and restart CommandBox

    • This ensures the JDBC driver is properly loaded into the classpath

  4. Verify installation:

    You should see: [OK] Driver found: oracle.jdbc.OracleDriver

Note: Other database drivers (MySQL, PostgreSQL, MSSQL, H2) are typically included with CommandBox/Lucee by default.

Error Messages

"No datasource configured"

No datasource was specified and none could be found in your Wheels configuration. Use the datasource= parameter or set dataSourceName in settings.

"Datasource not found"

The specified datasource doesn't exist in your server configuration. The command will prompt you to create it interactively.

"Driver not found" (Oracle-specific)

Oracle JDBC driver is not installed in CommandBox.

Fix: Follow the instructions above.

Quick steps:

  1. Download ojdbc11.jar from

  2. Place it in [CommandBox path]/jre/lib/

  3. Close ALL CommandBox instances and restart (don't just reload)

  4. Try the command again

"Database already exists"

The database already exists. Use --force flag to drop and recreate it:

"Access denied"

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

"Connection failed"

Common causes:

  1. Database server is not running

  2. Wrong server/port configuration

  3. Invalid credentials

  4. Network/firewall issues

  5. For PostgreSQL: pg_hba.conf authentication issues

  6. For Oracle: TNS listener not running or incorrect SID

"Invalid Oracle identifier" (Oracle-specific)

Database name contains invalid characters. Oracle usernames can only contain letters, numbers, and underscores.

Fix: Use underscores instead of hyphens:

"ORA-65096: common user or role name must start with C##" (Oracle-specific)

Oracle Container Database (CDB) requires common users to start with C## prefix, or the connecting user needs privileges to set the _ORACLE_SCRIPT session variable.

Fix: Either use a C## prefixed name or grant additional privileges:

"ORA-28014: cannot drop administrative user" (Oracle-specific)

Attempting to drop an Oracle system user (SYS, SYSTEM, etc.). Choose a different database name.

Fix: Use a non-system username:

Configuration Detection

The command intelligently detects datasource configuration from multiple sources:

Priority Order:

  1. .env.{environment} file (highest priority - NEW!)

    • Reads actual credential values using generic DB_* variable names

    • Example: DB_HOST=localhost, DB_USER=sa, DB_PASSWORD=MyPass123!

    • Solves the issue where app.cfm contains unresolved placeholders like ##this.env.DB_HOST##

  2. Datasource definitions in /config/app.cfm

    • Falls back to parsing connection strings if .env file doesn't exist

    • Maintains backward compatibility

  3. Environment-specific settings: /config/[environment]/settings.cfm

    • Detects datasource name from set(dataSourceName="...")

  4. General settings: /config/settings.cfm

    • Global datasource configuration

What It Extracts:

  • Database driver type (MySQL, PostgreSQL, MSSQL, Oracle, H2)

  • Connection details:

    • Host and port

    • Database name

    • Username and password

    • Oracle SID (if applicable)

Generic Variable Names

All database types now use consistent DB_* variable names in .env files:

This makes it easy to switch database types without changing variable names.

Related Commands

  • - Drop an existing database

  • - Create and setup database

  • - Run migrations after creating database

wheels env validate

Overview

The wheels env validate command validates the format and content of .env files in your Wheels project. This command helps ensure your environment configuration is properly formatted, contains required variables, and follows best practices. It's essential for catching configuration errors before deployment and maintaining consistent environment setups across different environments.

Command Syntax

Options

Option
Description
Default

Basic Usage Examples

Validate Default .env File

Validates the .env file in your project root

Validate Specific File

Validates the .env.production file

Check Required Variables

Validates that specific required variables are present

Detailed Validation

Shows detailed information about all variables found

Advanced Usage Examples

Production Deployment Validation

Ensures production environment has all critical variables

Multi-Environment Validation

Required Variables by Environment

Comprehensive Validation

Combines required variable checking with detailed output

Validation Checks

Format Validation

The command validates several aspects of your .env file format:

1. File Format Detection

  • Properties format: Standard KEY=VALUE pairs

  • JSON format: Valid JSON structure

  • Automatic detection based on content

2. Syntax Validation

  • Missing equals sign: KEY VALUE (invalid)

  • Empty key names: =value (invalid)

  • Valid key=value format: KEY=value (valid)

3. Key Name Standards

  • Valid characters: Letters, numbers, underscores only

  • Standard format: UPPER_SNAKE_CASE recommended

  • Non-standard warnings: Mixed case, special characters

Content Validation

1. Required Variables

Ensures specified variables are present and have values

2. Duplicate Key Detection

Identifies when the same key appears multiple times:

3. Placeholder Detection

Identifies common placeholder values in sensitive variables:

  • your_password

  • your_secret

  • change_me

  • xxx

  • TODO

Sample Output

Successful Validation

Validation with Warnings

Validation with Errors

Verbose Output

Error Types and Solutions

Format Errors

Missing Equals Sign

Solution: Add equals sign: DB_HOST=localhost

Empty Key Name

Solution: Provide a key name: MY_KEY=value

Invalid JSON

Solution: Fix JSON syntax or convert to properties format

Content Errors

Required Key Missing

Solution: Add the missing variable: API_KEY=your-api-key

Empty Required Value

Solution: Provide a value: DB_PASSWORD=your-password

Warning Types

Non-Standard Key Name

Recommendation: Use DB_HOST instead of dbHost

Placeholder Value

Recommendation: Replace placeholder with actual value

Duplicate Key

Recommendation: Remove duplicate or rename one key

Common Use Cases

Pre-Deployment Validation

Development Workflow

CI/CD Integration

Environment Setup Verification

Configuration Auditing

Best Practices

1. Regular Validation

2. Environment-Specific Requirements

3. Pre-Commit Validation

4. Deployment Pipeline Integration

5. Use Verbose Mode for Documentation

Integration with Other Commands

With Environment Setup

With File Merging

With Configuration Display

Exit Codes

The command returns different exit codes for automation:

  • 0: Validation passed (may have warnings)

  • 1: Validation failed (has errors)

This makes it perfect for use in scripts and CI/CD pipelines:

Tips and Recommendations

Security Best Practices

  • Use the validation to catch placeholder values in sensitive variables

  • Regularly validate that required security-related variables are present

  • Use --verbose mode carefully in CI/CD (sensitive values are masked but logs might be visible)

Development Workflow

  • Validate environment files before committing changes

  • Use different required variable lists for different environments

  • Set up validation as part of your local development setup script

Team Collaboration

  • Include validation in project setup documentation

  • Use validation to ensure consistent environment setup across team members

  • Define standard required variables for your project type

Automation

  • Integrate validation into deployment pipelines

  • Use exit codes to fail deployments when validation errors occur

  • Set up regular validation checks for configuration drift detection

wheels plugin init

Initialize a new CFWheels plugin in the /plugins directory.

Usage

Parameters

Parameter
Required
Type
Options
Default
Description

Description

The plugin init command creates a new CFWheels plugin following the standard CFWheels plugin structure. The plugin is created directly in your application's /plugins directory and includes all necessary files to get started.

Features

  • Creates plugin in /plugins directory

  • Follows CFWheels plugin conventions

  • Includes mixin="global" for framework-wide availability

  • Generates documentation files

  • Includes test suite

  • Ready for ForgeBox publishing

Examples

Basic plugin initialization

Output:

With full metadata

Quick initialization

Generated Structure

The command creates the following structure in /plugins/pluginName/:

File Templates

myHelper.cfc (Main Plugin Component)

Key Features:

  • mixin="global" makes functions available everywhere in Wheels

  • Functions documented with Wheels doc format [section: Plugins]

  • Version tracking via this.version

index.cfm (Documentation Page)

box.json (Package Metadata)

Development Workflow

1. Initialize Plugin

2. Add Your Functions

Edit /plugins/myHelper/myHelper.cfc and add your plugin methods:

3. Update Documentation

Edit index.cfm and README.md with usage examples and function descriptions.

4. Test Your Plugin

Then in your Wheels application:

5. Add Tests

Edit /plugins/myHelper/tests/myHelperTest.cfc:

Run tests:

6. Publish to ForgeBox

Plugin Types

Common CFWheels plugin categories:

  • Data Helpers - String manipulation, date formatting, validation

  • Authentication - User authentication, session management, encryption

  • API Tools - REST helpers, JSON formatting, API clients

  • Database - Query helpers, soft delete, auditing

  • UI Components - Form helpers, tables, pagination

  • Email - Email formatting, templates, sending

  • Caching - Cache management, warming, invalidation

  • Testing - Test helpers, fixtures, mocking

Best Practices

  1. Naming Convention: Always prefix with wheels- (automatic)

  2. Function Naming: Use clear, descriptive names

  3. Documentation: Document all public functions with Wheels format

  4. Testing: Include comprehensive test coverage

  5. Versioning: Follow semantic versioning (MAJOR.MINOR.PATCH)

  6. Dependencies: Minimize external dependencies

  7. Compatibility: Test with supported Wheels versions

How Plugin Loading Works

  1. Wheels scans /plugins directory on startup

  2. Each plugin's main CFC is instantiated

  3. With mixin="global", functions become available in:

    • Controllers

    • Models

    • Views

    • Other plugins

  4. Call wheels reload to reload plugins after changes

Error Handling

Plugin Already Exists

Solution: Choose a different name or remove the existing plugin first.

No Wheels Application

The command must be run from within a Wheels application directory.

Notes

  • Plugin is created directly in /plugins directory

  • Plugin name automatically prefixed with wheels- if not present

  • Folder name uses simple plugin name (without wheels- prefix)

  • Use mixin="global" to make functions available everywhere

  • Restart or reload Wheels after creating plugin

  • Plugin functions documented with [section: Plugins] format

  • Type must be cfwheels-plugins for ForgeBox categorization

See Also

  • - Install plugins from ForgeBox

  • - List installed plugins

  • - Reload application

  • - Full plugin development guide

wheels env validate [options]

--file

Name of the .env file to validate

.env

--required

Comma-separated list of required keys that must be present

empty

--verbose

Show detailed validation information including all variables

false

wheels env validate
wheels env validate --file=.env.production
wheels env validate --required=DB_HOST,DB_USER,DB_PASSWORD
wheels env validate --verbose
wheels env validate --file=.env.production --required=DB_HOST,DB_NAME,DB_USER,DB_PASSWORD,API_KEY
# Validate all environment files
wheels env validate --file=.env.development
wheels env validate --file=.env.staging  
wheels env validate --file=.env.production
# Development requirements
wheels env validate --file=.env.development --required=DB_HOST,WHEELS_ENV

# Production requirements
wheels env validate --file=.env.production --required=DB_HOST,DB_NAME,DB_USER,DB_PASSWORD,API_KEY,WHEELS_ENV
wheels env validate --file=.env.production --required=DB_HOST,API_KEY --verbose
wheels env validate --required=DB_HOST,API_KEY
Line 15: Duplicate key: 'DB_HOST' (previous value will be overwritten)
Validating: .env

Summary:
  Total variables: 12

Validation passed with no issues!
Validating: .env.development

Warnings:
  Line 5: Non-standard key name: 'dbHost' (should contain only letters, numbers, and underscores)
  Line 12: Placeholder value detected for 'API_KEY'

Summary:
  Total variables: 8

Validation passed with 2 warnings
Validating: .env.production

Errors found:
  Line 3: Invalid format (missing '='): DB_HOST localhost
  Required key missing: 'API_KEY'
  Line 8: Empty key name

Warnings:
  Line 10: Duplicate key: 'DB_PORT' (previous value will be overwritten)

Summary:
  Total variables: 6

Validation failed with 3 errors
Validating: .env

Summary:
  Total variables: 10

Environment Variables:

  DB:
    DB_HOST = localhost
    DB_NAME = myapp
    DB_PASSWORD = ***MASKED***
    DB_PORT = 3306
    DB_USER = admin

  API:
    API_BASE_URL = https://api.example.com
    API_KEY = ***MASKED***
    API_TIMEOUT = 30

  Other:
    APP_NAME = My Application
    WHEELS_ENV = development

Validation passed with no issues!
Error: Line 5: Invalid format (missing '='): DB_HOST localhost
Error: Line 8: Empty key name
Error: Invalid JSON format: Unexpected character at position 15
Error: Required key missing: 'API_KEY'
Warning: Required key has empty value: 'DB_PASSWORD'
Warning: Line 3: Non-standard key name: 'dbHost' (should contain only letters, numbers, and underscores)
Warning: Line 7: Placeholder value detected for 'API_KEY'
Warning: Line 12: Duplicate key: 'DB_PORT' (previous value will be overwritten)
# Validate production config before deployment
wheels env validate --file=.env.production --required=DB_HOST,DB_NAME,DB_USER,DB_PASSWORD,API_KEY

# Check staging environment
wheels env validate --file=.env.staging --required=DB_HOST,API_KEY
# Quick validation during development
wheels env validate

# Detailed check when debugging
wheels env validate --verbose
# In your deployment pipeline
wheels env validate --file=.env.production --required=DB_HOST,API_KEY
if [ $? -ne 0 ]; then
    echo "Environment validation failed!"
    exit 1
fi
# Validate new team member's setup
wheels env validate --required=DB_HOST,WHEELS_ENV,API_KEY

# Check if all environments are properly configured
for env in development staging production; do
    wheels env validate --file=.env.$env
done
# Regular audit of all environment files
wheels env validate --file=.env.development --verbose
wheels env validate --file=.env.production --verbose
# Add to your development routine
wheels env validate
# Define different requirements for different environments
wheels env validate --file=.env.development --required=DB_HOST,WHEELS_ENV
wheels env validate --file=.env.production --required=DB_HOST,DB_NAME,DB_USER,DB_PASSWORD,API_KEY
# Add to git pre-commit hooks
#!/bin/sh
wheels env validate --file=.env.example
# In CI/CD pipeline
wheels env validate --file=.env.production --required=DB_HOST,API_KEY
# Generate configuration documentation
wheels env validate --verbose > config-validation-report.txt
# Set variables then validate
wheels env set DB_HOST=localhost DB_PORT=3306
wheels env validate --required=DB_HOST,DB_PORT
# Merge files then validate result
wheels env merge .env.base .env.local --output=.env.merged
wheels env validate --file=.env.merged --required=DB_HOST,API_KEY
# Validate then show configuration
wheels env validate --verbose
wheels env show
if wheels env validate --file=.env.production; then
    echo "Production config is valid"
    # Continue with deployment
else
    echo "Production config has errors"
    exit 1
fi
wheels db create [--datasource=<name>] [--environment=<env>] [--database=<dbname>] [--force]

datasource

string

Current datasource

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

environment

string

Current environment

Specify the environment to use. Defaults to the current environment (development if not set).

database

string

wheels_dev

Specify the database name to create. Note for Oracle: Database names cannot contain hyphens. Use underscores instead (e.g., myapp_dev not myapp-dev).

force

boolean

false

Drop the existing database if it already exists and recreate it. Without this flag, the command will error if the database already exists.

# Use specific datasource
wheels db create --datasource=myapp_dev

# Specify environment
wheels db create --environment=testing

# Custom database name
wheels db create --database=myapp_production

# Force recreation
wheels db create --force
wheels db create
wheels db create datasource=myapp_dev
wheels db create datasource=myapp_test --environment=testing
wheels db create --database=myapp_v2
wheels db create --force
Datasource 'myapp_dev' not found in server configuration.

Would you like to create this datasource now? [y/n]: y

=== Interactive Datasource Creation ===

Select database type:
  1. MySQL
  2. PostgreSQL
  3. MSSQL
  4. Oracle
  5. H2

Select database type [1-5]: 1
Selected: MySQL

Enter connection details:
Host [localhost]:
Port [3306]:
Database name [wheels_dev]: myapp_dev
Username [root]:
Password: ****

Review datasource configuration:
  Datasource Name: myapp_dev
  Database Type: MySQL
  Host: localhost
  Port: 3306
  Database: myapp_dev
  Username: root
  Connection String: jdbc:mysql://localhost:3306/myapp_dev

Create this datasource? [y/n]: y
==================================================================
  Database Creation Process
==================================================================
  Datasource:         myapp_dev
  Environment:        development
------------------------------------------------------------------
  Database Type:      MySQL
  Database Name:      myapp_development
------------------------------------------------------------------

>> Initializing MySQL database creation...
  [OK] Driver found: com.mysql.cj.jdbc.Driver
  [OK] Connected successfully to MySQL server!

>> Checking if database exists...
>> Creating MySQL database 'myapp_development'...
  [OK] Database 'myapp_development' created successfully!

>> Verifying database creation...
  [OK] Database 'myapp_development' verified successfully!
------------------------------------------------------------------
  [OK] MySQL database creation completed successfully!
[CommandBox installed path]/jre/lib/
wheels db create datasource=myOracleDS
wheels db create --force
# Wrong
wheels db create --database=my-app-dev

# Correct
wheels db create --database=my_app_dev
wheels db create --database=C##MYAPP
wheels db create --database=myapp_dev --force
DB_TYPE=mssql           # Database type
DB_HOST=localhost       # Host (not MSSQL_HOST)
DB_PORT=1433            # Port (not MSSQL_PORT)
DB_DATABASE=wheels_dev  # Database name (not MSSQL_DATABASE)
DB_USER=sa              # Username (not MSSQL_USER)
DB_PASSWORD=Pass123!    # Password (not MSSQL_PASSWORD)
DB_DATASOURCE=wheels_dev
official Oracle JDBC Driver download page
Oracle JDBC Driver Installation
Oracle's website
wheels db drop
wheels db setup
wheels dbmigrate latest
wheels plugin init <name> [--author=<name>] [--description=<text>] [--version=<version>] [--license=<type>]

name

Yes

string

-

-

Plugin name (will be prefixed with 'wheels-')

author

No

string

-

""

Plugin author name

description

No

string

-

""

Plugin description

version

No

string

-

"1.0.0"

Initial version number

license

No

string

MIT, Apache-2.0, GPL-3.0, BSD-3-Clause, ISC, Proprietary

MIT

License type

wheels plugin init my-helper
===========================================================
  Initializing Wheels Plugin: wheels-my-helper
===========================================================

Creating plugin in /plugins/myHelper/...

===========================================================

[OK] Plugin created successfully in /plugins/myHelper/

Files Created:
  myHelper.cfc      Main plugin component
  index.cfm         Documentation page
  box.json          Package metadata
  README.md         Project documentation

Next Steps:
  1. Edit myHelper.cfc to add your plugin functions
  2. Update index.cfm and README.md with usage examples
  3. Test: wheels reload (then call your functions)
  4. Publish: box login && box publish
wheels plugin init authentication \
  --author="Jane Smith" \
  --description="Authentication and authorization system" \
  --version="0.1.0" \
  --license=MIT
wheels plugin init api-tools --author="DevTeam"
plugins/
└── myHelper/
    ├── box.json           Package configuration
    ├── myHelper.cfc       Main plugin component (mixin="global")
    ├── index.cfm          Plugin documentation page
    ├── README.md          Project documentation
    ├── .gitignore         Git ignore file
    └── tests/             Test suite
        └── myHelperTest.cfc   TestBox tests
component hint="wheels-my-helper" output="false" mixin="global" {

    public function init() {
        this.version = "1.0.0";
        return this;
    }

    /**
     * Example function - Add your plugin methods here
     *
     * [section: Plugins]
     * [category: myHelper]
     *
     * @param1 Description of parameter
     */
    public function myHelperExample(required string param1) {
        // Your plugin logic here
        return arguments.param1;
    }

}
<h1>wheels-my-helper</h1>
<p>Plugin description</p>

<h3>Installation</h3>
<pre>
wheels plugin install wheels-my-helper
</pre>

<h3>Usage</h3>
<h4>Example Function</h4>
<pre>
// Call the example function
result = myHelperExample("test");
</pre>
{
    "name": "wheels-my-helper",
    "version": "1.0.0",
    "author": "Your Name",
    "slug": "wheels-my-helper",
    "type": "cfwheels-plugins",
    "keywords": "cfwheels,wheels,plugin",
    "homepage": "",
    "shortDescription": "Plugin description",
    "private": false
}
wheels plugin init my-helper --author="Your Name"
public function formatCurrency(required numeric amount) {
    return dollarFormat(arguments.amount);
}

public function slugify(required string text) {
    return lCase(reReplace(arguments.text, "[^a-zA-Z0-9]+", "-", "all"));
}
wheels reload
// Your functions are now available everywhere
formatted = formatCurrency(1234.56);  // Returns "$1,234.56"
slug = slugify("My Blog Post");       // Returns "my-blog-post"
it("should format currency correctly", function() {
    var plugin = createObject("component", "myHelper").init();
    var result = plugin.formatCurrency(1234.56);
    expect(result).toInclude("1,234");
});
box testbox run
cd plugins/myHelper
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/username/wheels-my-helper.git
git push -u origin main

box login
box publish
[ERROR] Plugin already exists

Plugin 'myHelper' already exists in /plugins folder
wheels plugin install
wheels plugin list
wheels reload
Developing Plugins

wheels generate app-wizard

Interactive wizard for creating a new Wheels application with guided setup.

Synopsis

wheels generate app-wizard [options]

#Can also be used as:
wheels g app-wizard [options]
wheels new [options]

Parameter Syntax

CommandBox supports multiple parameter formats:

  • Named parameters: name=value (e.g., name=MyApp, template=wheels-base-template@BE)

  • Flag parameters: --flag equals flag=true (e.g., --expert equals expert=true)

  • Flag with value: --flag=value equals flag=value (e.g., --skipInstall=true)

Note: Flag syntax (--flag) avoids positional/named parameter conflicts and is recommended for boolean 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 configuration options with helpful prompts, making it ideal for beginners or when you want to explore available options.

Arguments

Argument
Description
Default

name

Application name (optional - will prompt if not provided)

Prompted

Options

Option
Description
Valid Values
Default

template

App template to use

ForgeBox endpoint or slug

wheels-base-template@BE

directory

Directory to create app in

Valid directory path

{current directory}/{name}

reloadPassword

Reload password for the app

Any string

changeMe

datasourceName

Database datasource name

Valid datasource name

{app name}

cfmlEngine

CFML engine for server.json

lucee, adobe, lucee6, lucee5, adobe2023, etc.

lucee

useBootstrap

Add Bootstrap to the app

true, false

false

setupH2

Setup H2 database for development

true, false

true

init

Initialize directory as a package

true, false

false

force

Force installation into non-empty directory

true, false

false

nonInteractive

Run without prompts using defaults

true, false

false

expert

Show advanced configuration options

true, false

false

skipInstall

Skip dependency installation after creation

true, false

false

Interactive Wizard Steps

Step 1: Application Name

Please enter a name for your application: MyWheelsApp
  • Validates name format (alphanumeric, underscores, hyphens)

  • Checks for reserved names

  • Creates directory with this name

Step 2: Template Selection

Which Wheels Template shall we use?
❯ 3.0.x - Wheels Base Template - Bleeding Edge
  2.5.x - Wheels Base Template - Stable Release
  Wheels Template - HTMX - Alpine.js - Simple.css
  Wheels Starter App
  Wheels - TodoMVC - HTMX - Demo App
  Enter a custom template endpoint

Step 3: Reload Password

Please enter a 'reload' password for your application: changeMe
  • Used for ?reload=true&password=xxx functionality

  • Secures application reload via URL

Step 4: Database Configuration

Please enter a datasource name if different from MyWheelsApp: MyWheelsApp
  • Sets datasource name in configuration files

  • You'll configure the actual datasource in your CFML server admin

Step 5: CFML Engine Selection

Please select your preferred CFML engine?
❯ Lucee (Latest)
  Adobe ColdFusion (Latest)
  Lucee 6.x
  Lucee 5.x
  Adobe ColdFusion 2023
  Adobe ColdFusion 2021
  Adobe ColdFusion 2018
  Enter a custom engine endpoint

Step 6: H2 Database Setup (Lucee Only, if skipInstall=false)

H2 Java embedded SQL database for development? [y,n]
  • Only shown when using Lucee engine

  • Only asked if skipInstall=false

  • Sets up embedded H2 database for development

Step 7: Dependencies (if skipInstall=false)

========= Dependencies ======================
Configure dependencies and plugins for your application.

Would you like us to setup some default Bootstrap settings? [y/n]
  • Only shown if skipInstall=false

  • Configures Bootstrap CSS framework

  • Additional dependencies may be added here

Step 8: Package Initialization

Finally, shall we initialize your application as a package
by creating a box.json file? [y,n]
  • Creates box.json for ForgeBox package management

  • Always asked regardless of skipInstall setting

Step 9: Expert Mode (if expert=true)

========= Expert Mode: Advanced Configuration ==========
Configure advanced options for your application.

Custom server port (leave empty for default 8080): 8080
Custom JVM settings (e.g. -Xmx512m -Xms256m):
Setup custom environment configurations (dev, staging, production)? [y,n]
Enable advanced routing features (nested resources, constraints)? [y,n]
Custom plugin repositories (comma-separated URLs):
Build tool integration? [None/Apache Ant/Gradle/Maven/NPM Scripts]

Step 10: Configuration Review

+-----------------------------------------------------------------------------------+
| Great! Think we're all good to go. We're going to create a Wheels application for |
| you with the following parameters.                                                |
+-----------------------+-----------------------------------------------------------+
| Template              | wheels-base-template@BE                                   |
| Application Name      | MyWheelsApp                                              |
| Install Directory     | D:\path\to\MyWheelsApp                                   |
| Reload Password       | changeMe                                                 |
| Datasource Name       | MyWheelsApp                                              |
| CF Engine             | lucee                                                    |
| Setup H2 Database     | true                                                     | (if applicable)
| Setup Bootstrap       | false                                                    | (if applicable)
| Initialize as Package | true                                                     |
| Force Installation    | false                                                    |
| Skip Dependency Install | false                                                  |
+-----------------------+-----------------------------------------------------------+

Sound good? [y/n]

skipInstall Parameter Behavior

The skipInstall parameter significantly changes the wizard experience:

When skipInstall=false (default)

  • ✅ Asks about H2 database setup (if using Lucee)

  • ✅ Asks about Bootstrap dependencies

  • ✅ Shows "Dependencies" section

  • ✅ Includes dependency settings in summary table

  • ✅ Installs dependencies after app creation

When skipInstall=true

  • ❌ Skips H2 database question (even with Lucee)

  • ❌ Skips Bootstrap dependency question

  • ✅ Shows "Dependencies Skipped" message with explanation

  • ✅ Excludes dependency settings from summary table

  • ✅ Still asks about package initialization (box.json creation)

  • ✅ Still asks about core configuration (name, template, passwords, etc.)

  • ❌ Skips dependency installation after app creation

Dependencies Skipped Message

========= Dependencies Skipped ================
Dependency installation is disabled (skipInstall=true).
Dependencies like Bootstrap and H2 database will not be configured or installed.

Examples

Basic Interactive Wizard

wheels generate app-wizard

Runs full interactive wizard with all prompts.

Pre-configured App Name

wheels generate app-wizard name=MyApp

Skips name prompt, asks for other configuration.

Skip All Dependencies

wheels generate app-wizard --skipInstall

Runs wizard but skips H2 database and Bootstrap questions.

Expert Mode

wheels generate app-wizard --expert

Includes advanced configuration options like custom ports and JVM settings.

Non-Interactive Mode

wheels generate app-wizard --nonInteractive

Uses all defaults, no prompts. Creates app immediately.

Fully Configured

wheels generate app-wizard name=MyApp template=wheels-base-template@BE --skipInstall --expert --force

Pre-configured with expert mode and skipped dependencies.

Expert Mode Options

When --expert is enabled, additional configuration options are available:

Server Configuration

  • Custom server port: Override default port 8080

  • JVM settings: Custom memory and performance settings

Environment Setup

  • Custom environment configurations: Setup dev, staging, production configs

  • Advanced routing features: Enable nested resources and route constraints

Development Tools

  • Custom plugin repositories: Additional ForgeBox endpoints

  • Build tool integration: Ant, Gradle, Maven, or NPM integration

Non-Interactive Mode

Use --nonInteractive to bypass all prompts:

Default Values Used

  • Name: MyWheelsApp

  • Template: wheels-base-template@BE

  • Reload Password: changeMe

  • Datasource Name: Same as app name

  • CFML Engine: lucee

  • Directory: {current directory}/{name}

Override Defaults

wheels generate app-wizard --nonInteractive name=CustomApp template=wheels-starter-app --cfmlEngine=adobe

Post-Creation Steps

After successful creation:

Model generation complete!

Next steps:
   1. cd MyWheelsApp
   2. Review generated configuration files
   3. Configure your datasource in CFML server admin
   4. box server start (to start development server)
   5. Visit http://localhost:8080

Additional commands:
   - wheels generate model User name:string,email:string
   - wheels generate controller Users
   - wheels dbmigrate up (run database migrations)
   - wheels test run (run tests)

Validation Rules

Application Name

  • Must start with a letter

  • Can contain letters, numbers, underscores, and hyphens

  • Cannot contain spaces or special characters

  • Cannot exceed 50 characters

  • Cannot be a reserved name (con, prn, aux, nul, wheels, etc.)

Directory Path

  • Must be a valid file system path

  • Parent directory must exist and be writable

  • Will warn if target directory is not empty (unless --force used)

Error Handling

Common Issues and Solutions

Invalid application name:

'123app' is not valid. Application name must start with a letter.
Please try again: MyApp

Directory not empty:

Target directory is not empty. Use --force to overwrite, or choose a different location.

Missing dependencies:

Warning: Some dependencies could not be installed.
Run 'box install' in your application directory to install them manually.

Best Practices

  1. Use descriptive names: Choose clear, project-specific application names

  2. Review configuration: Check the summary table before confirming

  3. Consider skipInstall: Use --skipInstall for custom dependency management

  4. Expert mode for production: Use --expert for production-ready configurations

  5. Save time with non-interactive: Use --nonInteractive in automated scripts

  6. Template selection: Choose templates that match your project requirements

Common Patterns

API-Only Application

wheels generate app-wizard name=MyAPI template=wheels-base-template@BE --skipInstall --expert

Full-Stack Web Application

wheels generate app-wizard name=MyWebApp --useBootstrap --setupH2

Team Development Setup

wheels generate app-wizard --nonInteractive name=TeamProject template=wheels-starter-app --cfmlEngine=lucee --force

CI/CD Pipeline

wheels generate app-wizard --nonInteractive --skipInstall name=BuildApp template=wheels-base-template@BE

Troubleshooting

Wizard Hangs or Freezes

  1. Check terminal compatibility

  2. Try --nonInteractive mode

  3. Ensure adequate system resources

Installation Failures

  1. Verify internet connection for template downloads

  2. Check CommandBox version compatibility

  3. Try --skipInstall and install dependencies manually

  4. Check file permissions in target directory

Configuration Issues

  1. Review generated server.json file

  2. Verify datasource configuration in CFML admin

  3. Check application settings in /config/app.cfm

See Also

  • wheels generate app - Non-interactive app generation

  • wheels generate controller - Generate controllers

  • wheels generate model - Generate models

  • wheels scaffold - Generate complete CRUD

wheels config diff

Overview

The wheels config diff command compares configuration settings and environment variables between two environments. It helps identify differences in both Wheels settings files and environment-specific .env files, making it easier to understand configuration variations across development, testing, and production environments.

Command Syntax

wheels config diff <env1> <env2> [--changesOnly] [--format=<format>] [--env] [--settings]

Parameters

Parameter
Type
Required
Description

env1

string

Yes

First environment to compare (e.g., development, testing, production)

env2

string

Yes

Second environment to compare

--changesOnly

flag

No

Only show differences, hide identical values

--format

string

No

Output format: table (default) or json

--env

flag

No

Compare only environment variables

--settings

flag

No

Compare only Wheels settings

Comparison Modes

The command can operate in three modes:

  1. Both (Default) - Compares both settings and environment variables

  2. Environment Variables Only - Use --env flag

  3. Settings Only - Use --settings flag

Basic Usage

Compare All Configurations

# Compare everything between development and production
wheels config diff development production

Compare Only Differences

# Show only the differences, hide identical values
wheels config diff development production --changesOnly

Compare Environment Variables Only

# Compare only .env files between environments
wheels config diff development production --env

Compare Settings Only

# Compare only Wheels settings files
wheels config diff development production --settings

JSON Output Format

# Output comparison as JSON for parsing
wheels config diff development production --format=json

What Gets Compared

Settings Configuration

The command compares:

  • Base settings from config/settings.cfm

  • Environment-specific overrides from config/{environment}/settings.cfm

  • All set() function calls are parsed and compared

Environment Variables

The command compares:

  • Environment-specific files: .env.{environment}

  • Falls back to .env for development environment if .env.development doesn't exist

  • All KEY=VALUE pairs are parsed with proper handling of:

    • Comments (lines starting with #)

    • Inline comments

    • Quoted values (single or double quotes)

    • Whitespace trimming

File Locations

Settings Files

config/
├── settings.cfm                 # Base settings
├── development/
│   └── settings.cfm             # Development overrides
├── testing/
│   └── settings.cfm             # Testing overrides
└── production/
    └── settings.cfm             # Production overrides

Environment Files

project_root/
├── .env                         # Base/development environment variables
├── .env.development             # Development-specific variables
├── .env.testing                 # Testing-specific variables
└── .env.production              # Production-specific variables

Output Format

Table Format (Default)

The table output is organized into clear sections:

========================================
Configuration Comparison: development vs production
========================================

[SETTINGS CONFIGURATION]

Different Values:
┌──────────────────────┬────────────┬────────────┐
│ Setting              │ development│ production │
├──────────────────────┼────────────┼────────────┤
│ showDebugInformation │ true       │ false      │
│ cacheQueries         │ false      │ true       │
└──────────────────────┴────────────┴────────────┘

Only in development:
┌─────────────────┬──────────┐
│ Setting         │ Value    │
├─────────────────┼──────────┤
│ debugPlugin     │ true     │
└─────────────────┴──────────┘

Only in production:
┌─────────────────┬──────────┐
│ Setting         │ Value    │
├─────────────────┼──────────┤
│ forceSSL        │ true     │
└─────────────────┴──────────┘

[ENVIRONMENT VARIABLES]

Different Values:
┌──────────────┬────────────────┬────────────────┐
│ Variable     │ development    │ production     │
├──────────────┼────────────────┼────────────────┤
│ DB_NAME      │ app_dev        │ app_prod       │
│ DEBUG_MODE   │ true           │ false          │
└──────────────┴────────────────┴────────────────┘

========================================
SUMMARY
========================================
Settings:
  Total: 25
  Identical: 20
  Different: 2
  Unique: 3

Environment Variables:
  Total: 15
  Identical: 10
  Different: 2
  Unique: 3

Overall:
  Total configurations: 40
  Identical: 30
  Different: 4
  Unique: 6
  Similarity: 75%

JSON Format

{
  "env1": "development",
  "env2": "production",
  "comparisons": {
    "settings": {
      "identical": [...],
      "different": [...],
      "onlyInFirst": [...],
      "onlyInSecond": [...]
    },
    "env": {
      "identical": [...],
      "different": [...],
      "onlyInFirst": [...],
      "onlyInSecond": [...]
    }
  },
  "summary": {
    "settings": {
      "totalSettings": 25,
      "identical": 20,
      "different": 2,
      "onlyInFirst": 1,
      "onlyInSecond": 2
    },
    "env": {
      "totalVariables": 15,
      "identical": 10,
      "different": 2,
      "onlyInFirst": 1,
      "onlyInSecond": 2
    },
    "overall": {
      "total": 40,
      "identical": 30,
      "different": 4,
      "unique": 6,
      "similarity": 75
    }
  }
}

Security Features

Automatic Masking

The command automatically masks sensitive values containing these keywords:

  • password

  • secret

  • key

  • token

  • apikey/api_key

  • private

  • credential

  • auth

  • passphrase

  • salt

Masked values appear as ***MASKED*** in the output.

Example

┌──────────────┬────────────────┬────────────────┐
│ Variable     │ development    │ production     │
├──────────────┼────────────────┼────────────────┤
│ DB_PASSWORD  │ ***MASKED***   │ ***MASKED***   │
│ API_KEY      │ ***MASKED***   │ ***MASKED***   │
└──────────────┴────────────────┴────────────────┘

Common Use Cases

Pre-Deployment Verification

# Verify configuration differences before deploying to production
wheels config diff testing production --changesOnly

Environment Synchronization Check

# Check if development and testing have similar configurations
wheels config diff development testing

Security Audit

# Review all security-related settings between environments
wheels config diff development production --settings | grep -i "debug\|error\|ssl"

CI/CD Pipeline Integration

# In your deployment script
wheels config diff staging production --format=json > config-diff.json
# Parse JSON to validate critical settings match expectations

Environment Variable Validation

# Ensure all required environment variables exist in production
wheels config diff development production --env --changesOnly

Quick Similarity Check

# Get a quick overview of configuration similarity
wheels config diff development testing | grep "Similarity:"

Examples

Example 1: Basic Comparison

wheels config diff development production

Shows all differences and similarities between development and production configurations.

Example 2: Changes Only

wheels config diff testing production --changesOnly

Shows only the differences, useful for quick reviews.

Example 3: Environment Variables Focus

wheels config diff development staging --env --changesOnly

Shows only environment variable differences between development and staging.

Example 4: JSON for Automation

wheels config diff development production --format=json | jq '.summary.overall.similarity'

Outputs similarity percentage for automated checks.

Example 5: Settings Validation

wheels config diff development production --settings --changesOnly

Validates only Wheels settings differences.

Error Handling

Environment Not Found

Warning: Settings for environment 'staging' not found!

The command continues with available data and shows warnings for missing files.

No Configuration File

Warning: No settings.cfm file found in config directory

The command will still compare environment variables if available.

Same Environment Comparison

Error: Cannot compare an environment to itself

You must specify two different environments.

Invalid Format

Error: Invalid format: xml. Valid formats are: table, json

Only table and json formats are supported.

Best Practices

  1. Regular Comparisons - Run comparisons before each deployment to catch unintended changes

  2. Use changesOnly for Reviews - Focus on differences during code reviews

  3. Automate with JSON - Use JSON output in CI/CD pipelines for automated validation

  4. Document Differences - Keep a record of intentional differences between environments

  5. Security First - Always review security-related settings (debug, error handling, SSL)

  6. Version Control - Track both settings files and environment files in version control (except sensitive .env files)

Tips

  • Use --changesOnly to quickly identify configuration drift

  • Pipe JSON output to jq for advanced filtering and processing

  • Create aliases for common comparisons (e.g., alias cfgdiff='wheels config diff')

  • Review the similarity percentage as a quick health check

  • Use the command before and after configuration changes to verify impact

  • Combine with wheels config check for comprehensive configuration validation

File Format Examples

Settings File (config/production/settings.cfm)

<cfscript>
// Production-specific settings
set(showDebugInformation = false);
set(showErrorInformation = false);
set(sendEmailOnError = true);
set(errorEmailAddress = "[email protected]");
set(cacheQueries = true);
set(forceSSL = true);
</cfscript>

Environment File (.env.production)

# Production Environment Variables
WHEELS_ENV=production

# Database Configuration
DB_HOST=prod.database.com
DB_PORT=3306
DB_NAME=app_production
DB_USER=prod_user
DB_PASSWORD=secure_password_here

# Application Settings
DEBUG_MODE=false
LOG_LEVEL=error
SESSION_TIMEOUT=30

# API Keys
API_KEY=prod_api_key_here
SECRET_KEY=prod_secret_key

Troubleshooting

Files Not Being Read

  • Ensure files exist in the correct locations

  • Check file permissions (must be readable)

  • Verify file naming conventions (.env.{environment})

Parsing Issues

  • Check for syntax errors in settings.cfm files

  • Ensure .env files use proper KEY=VALUE format

  • Remove any special characters that might cause parsing errors

Missing Comparisons

  • If using --env or --settings, ensure you're not filtering out the data you want

  • Check that both environments have the respective files

Performance Issues

  • Large configuration files may take time to parse

  • Consider using --changesOnly to reduce output

  • Use JSON format for faster processing in scripts

Related Commands

  • wheels config check - Validate configuration for issues

  • wheels get environment - Display current environment

  • wheels env merge - Merge environment configurations

  • wheels env set - Set environment variables

Summary

The wheels config diff command is an essential tool for managing multi-environment Wheels applications. It provides comprehensive comparison capabilities for both application settings and environment variables, helping teams maintain consistency and catch configuration drift before it causes issues in production.

wheels test run

Run TestBox tests for your Wheels application using the TestBox CLI integration.

Note: This command replaces the deprecated wheels test command.

Prerequisites

Install TestBox CLI

box install testbox-cli --global

Synopsis

wheels test run [spec] [options]

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Named parameters: name=value (e.g., format=json, filter="User")

  • Flag parameters: --flag equals flag=true (e.g., --coverage equals coverage=true)

  • Flag with value: --flag=value equals flag=value (e.g., --format=json)

Parameter Mixing Rules:

✅ ALLOWED:

  • All named: wheels test run format=json verbose=true

  • All flags: wheels test run --verbose --coverage

  • Named + flags: wheels test run format=json --coverage

❌ NOT ALLOWED:

  • Positional + named: Not applicable for this command (no positional parameters)

Recommendation: Use named parameters for specific values, flags for boolean options: wheels test run format=json --coverage

Description

The wheels test run command executes your application's TestBox test suite with support, filtering, and various output formats. This is the primary command for running your application tests (as opposed to framework tests).

Options

Option
Type
Default
Description

type

string

app

Type of tests to run

format

string

txt

Test output format: txt, junit, json

bundles

string

-

The path or list of paths of the spec bundle CFCs to run and test ONLY

directory

string

-

The directory to use to discover test bundles and specs to test

recurse

boolean

true

Recurse the directory mapping or not

verbose

boolean

true

Display extra details including passing and skipped tests

servername

string

-

Server name for test execution

filter

string

-

Filter tests by pattern or name

labels

string

-

The list of labels that a suite or spec must have in order to execute

coverage

boolean

false

Enable code coverage with FusionReactor

Examples

Run all tests

wheels test run

Filter tests by pattern

# Named parameter (recommended for string values)
wheels test run filter="User"
wheels test run filter="test_user_validation"

Run specific bundles

# Named parameter (recommended)
wheels test run bundles="tests.models"
wheels test run bundles="tests.models,tests.controllers"

Run tests with specific labels

# Named parameter (recommended)
wheels test run labels="unit"
wheels test run labels="critical,auth"

Generate coverage report

# Flag (recommended for boolean)
wheels test run --coverage

# OR named
wheels test run coverage=true

Use different output format

# Named (recommended)
wheels test run format=json
wheels test run format=junit

# OR flag with value
wheels test run --format=json

Run tests from specific directory

# Named parameters (recommended)
wheels test run directory="tests/specs"
wheels test run directory="tests/specs/unit" recurse=false

Verbose output with coverage

# Flags + named (recommended)
wheels test run --verbose --coverage format=txt

# OR all named
wheels test run verbose=true coverage=true format=txt

Run tests for different type

# Named (recommended)
wheels test run type=core
wheels test run type=app

Test Structure

Standard test directory layout:

/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

Writing Tests

Model Test Example

component extends="wheels.Testbox" {

    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");
            });

        });
    }

}

Controller Test Example

component extends="wheels.Testbox" {

    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");
            });

        });
    }

}

Test Configuration

/tests/Application.cfc

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,
        format = "simple",
        labels = "",
        options = {}
    };
}

Reporters

txt (Default)

wheels test run format=txt
  • Plain txt output

  • Good for CI systems

  • No colors

JSON

wheels test run format=json
√ tests.specs.functions.Example (3 ms)
[Passed: 1] [Failed: 0] [Errors: 0] [Skipped: 0] [Suites/Specs: 1/1]

    √ Tests that DummyTest
        √ is Returning True (1 ms)
╔═════════════════════════════════════════════════════════════════════╗
║ Passed  ║ Failed  ║ Errored ║ Skipped ║ Bundles ║ Suites  ║ Specs   ║
╠═════════════════════════════════════════════════════════════════════╣
║ 1       ║ 0       ║ 0       ║ 0       ║ 1       ║ 1       ║ 1       ║
╚═════════════════════════════════════════════════════════════════════╝

JUnit

wheels test run format=junit
  • JUnit XML format

  • For CI integration

  • Jenkins compatible

Filtering Tests

By Bundle

# Run only model tests
wheels test run bundles=tests.models

# Run multiple bundles
wheels test run bundles=tests.models,tests.controllers

By Label

component extends="wheels.Testbox" labels="label title"
# Run only critical tests
wheels test run labels="label title"

# Run auth OR api tests
wheels test run labels=auth,api

By Name Filter

# Run tests matching pattern
wheels test run filter="user"
wheels test run filter="validate*"

Benefits:

  • Faster execution

  • Better CPU utilization

  • Finds concurrency issues

Code Coverage

Generate coverage reports:

wheels test run --coverage coverageOutputDir=coverage/

View report:

open coverage/index.html

Test Helpers

Create reusable test utilities:

// /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;
    }

}

Database Strategies

Transaction Rollback

function beforeAll() {
    transaction action="begin";
}

function afterAll() {
    transaction action="rollback";
}

Database Cleaner

function beforeEach() {
    queryExecute("DELETE FROM users");
    queryExecute("DELETE FROM products");
}

Fixtures

function loadFixtures() {
    var users = deserializeJSON(
        fileRead("/tests/fixtures/users.json")
    );

    for (var userData in users) {
        model("User").create(userData);
    }
}

CI/CD Integration

GitHub Actions

- name: Run tests
  run: |
    wheels test run  format=junit outputFile=test-results.xml

- name: Upload results
  uses: actions/upload-artifact@v4
  with:
    name: test-results
    path: test-results.xml

Pre-commit Hook

#!/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

Common Issues

Out of Memory

# Increase memory
box server set jvm.heapSize=1024
box server restart

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

  • wheels test - Run framework tests

  • wheels test coverage - Generate coverage

  • wheels test debug - Debug tests

  • wheels generate test - Generate test files

wheels env switch

Switch to a different environment in your Wheels application.

Synopsis

wheels env switch [name] [options]

Description

The wheels env switch command changes the active environment for your Wheels application by updating the wheels_env variable in the .env file. It validates the environment configuration, optionally creates backups, and can restart the application server.

Arguments

Argument
Description
Default

name

Target environment name

Required

Options

Option
Description
Default

--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

Examples

Switch to staging

wheels env switch staging

Switch with application restart

wheels env switch production --restart

Force switch without validation

wheels env switch testing --force

Switch with backup

wheels env switch production --backup

Quiet switch for scripts

wheels env switch development --quiet

Combine multiple options

wheels env switch production --backup --restart --check

What It Does

  1. Validates Target Environment (if --check is enabled):

    • Checks for .env.[environment] file

    • Checks for config/[environment]/settings.cfm file

    • Validates environment configuration

  2. Creates Backup (if --backup is enabled):

    • Backs up current .env file

    • Backs up server.json if it exists

    • Creates timestamped backup files

  3. Updates Configuration:

    • Updates or creates wheels_env variable in .env

    • Falls back to environment variable if wheels_env doesn't exist

    • Updates server.json profile if file exists

  4. Restarts Application (if --restart is enabled):

    • Stops and starts CommandBox server if server.json exists

    • Falls back to wheels reload command

Output Example

Environment Switch
==================================================

Current Environment: development
Target Environment:  staging

Validating target environment... [OK]
Creating backup... [OK]
  Backup saved: .env.backup-20240115-103045
Switching environment... [OK]
Updated environment variable... [OK]

==================================================
[SUCCESS] Environment switched successfully!

Environment Details:
- Environment: staging
- Database:    wheels_staging
- Debug Mode:  Enabled
- Cache:       Partial

IMPORTANT:
- Restart your application server for changes to take effect
- Run 'wheels reload' if using Wheels development server
- Or use 'wheels env switch staging --restart' next time

Environment File Updates

.env File

The command updates or creates the wheels_env variable:

Before:

wheels_env=development
# or
environment=development

After:

wheels_env=staging

If no environment variable exists, it adds:

wheels_env=staging

Validation Process

The validation checks for required environment files:

  1. Environment Configuration File:

    • Checks: .env.[environment]

    • Location: Project root

  2. Wheels Settings File:

    • Checks: config/[environment]/settings.cfm

    • Location: Project config directory

Validation Rules:

  • Valid: Both files exist

  • Valid with warning: One file exists

  • Invalid: Neither file exists (unless --force is used)

Options Details

--check (default: true)

Validates the target environment before switching:

# With validation (default)
wheels env switch production

# Skip validation
wheels env switch production --no-check

--backup (default: false)

Creates timestamped backups before switching:

wheels env switch production --backup
# Creates: .env.backup-20240115-103045
# Creates: server.json.backup-20240115-103045 (if exists)

--restart (default: false)

Automatically restarts the application:

wheels env switch production --restart

--force (default: false)

Bypasses validation and confirmation prompts:

# Force switch even if validation fails
wheels env switch production --force

# Combine with other options
wheels env switch production --force --no-check

--quiet (default: false)

Minimal output for scripting:

wheels env switch production --quiet
# Output: Environment switched to production

Production Safety

When switching to production from another environment:

  • Displays warning about production implications

  • Requires confirmation (unless --force or --quiet)

  • Shows warnings about debug mode, caching, and error handling

Warning message:

WARNING: Switching to PRODUCTION environment
   This will:
   - Disable debug mode
   - Enable full caching
   - Hide detailed error messages

Are you sure you want to continue? (yes/no):

Error Handling

If the switch fails:

  • Displays error message

  • Provides troubleshooting suggestions

  • Sets exit code 1 for scripting

Example error output:

[X] Failed to switch environment
  Error: Environment 'invalid' is not configured

Suggestions:
- Check if you have write permissions for .env file
- Ensure the environment name is valid
- Try running with administrator/sudo privileges if needed
- Use --force to bypass validation checks

Backup Files

The --backup option creates timestamped backup files:

Created Files:

  • .env.backup-YYYYMMDD-HHMMSS - Backup of current .env file

  • server.json.backup-YYYYMMDD-HHMMSS - Backup of server.json (if exists)

Manual Restore:

If you need to restore from a backup:

# Restore .env file
cp .env.backup-20240115-103045 .env

# Restore server.json
cp server.json.backup-20240115-103045 server.json

# Reload application
wheels reload

Service Management

With --restart Option

When using --restart, the command will:

  1. If server.json exists:

    • Stop CommandBox server

    • Start CommandBox server

  2. If server.json doesn't exist:

    • Execute wheels reload command

Manual Restart

If --restart is not used, you need to manually restart:

# CommandBox server
server restart

# Or Wheels development server
wheels reload

Environment-Specific Configuration

The command reads additional configuration from .env.[environment] files if they exist:

Supported Variables:

  • database - Database connection name

  • debug - Debug mode (true/false)

  • cache - Cache configuration

Example .env.production:

database=wheels_production
debug=false
cache=full

Integration Examples

CI/CD Pipeline

- name: Switch to staging
  run: |
    wheels env switch staging --check
    wheels test run
    wheels deploy exec staging

Deployment Scripts

#!/bin/bash
# deploy.sh

# Switch environment with backup
wheels env switch $1 --backup

# Run migrations
wheels dbmigrate latest

# Clear caches
wheels cache clear

# Verify environment
wheels env current

Automated Testing

# Switch to testing environment quietly
wheels env switch testing --quiet --force

# Run tests
wheels test run

# Switch back to development
wheels env switch development --quiet

Troubleshooting

Switch Failed

  • Check validation errors in output

  • Verify .env.[environment] file exists

  • Verify config/[environment]/settings.cfm exists

  • Use --force to bypass validation

Application Not Responding After Switch

  • Ensure server was restarted

  • Check .env file for correct wheels_env value

  • Review application logs for errors

  • Manually restart services if needed

Permission Issues

  • Check write permissions for .env file

  • Run with appropriate privileges

  • Ensure backup directory is writable

Validation Warnings

  • Warning appears if only one configuration file exists

  • Environment may work but might not be fully configured

  • Check both .env.[environment] and config/[environment]/settings.cfm

Best Practices

  1. Always validate: Keep --check enabled for production switches

  2. Create backups: Use --backup for critical environment changes

  3. Test first: Switch in staging before production

  4. Use --restart: Automatically restart to apply changes immediately

  5. Document changes: Log environment switches in deployment notes

Security Considerations

  • Production switches require explicit confirmation

  • Backup files contain sensitive configuration

  • .env files should be in .gitignore

  • Use --quiet mode carefully in automated scripts

  • Review environment-specific configurations regularly

Notes

  • The command modifies the .env file in place

  • Creates wheels_env variable if it doesn't exist

  • Falls back to updating environment variable if found

  • Some changes require application restart to take effect

  • Database connections may need to be reset after switching

  • Cached data should be cleared after environment switch

Exit Codes

  • 0 - Success

  • 1 - Failure (validation error, write error, or user cancellation)

See Also

  • wheels env - Environment management overview

  • wheels env list - List available environments

  • wheels env setup - Setup new environments

  • wheels env current - Show current environment

  • wheels reload - Reload application

wheels generate view

Generate view files for controllers.

Synopsis

wheels generate view [objectName] [name] [template]
wheels g view [objectName] [name] [template]

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Positional parameters: wheels generate view user show (most common for this command)

  • Named parameters: objectName=value name=value (e.g., objectName=user name=show)

  • Flag parameters: --flag equals flag=true (e.g., --force equals force=true)

Parameter Mixing Rules:

✅ ALLOWED:

  • All positional: wheels generate view user show

  • All positional + flags: wheels generate view user show --force

  • All named: objectName=user name=show template=crud/show

❌ NOT ALLOWED:

  • Positional + named: wheels generate view user name=show (causes error)

Recommendation: Use positional parameters for objectName and name, flags for options: wheels generate view user show --force

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

objectName

View path folder (e.g., user)

Required

name

Name of the file to create (e.g., edit)

Required

template

Optional template to use

Options

Option
Description
Default

--force

Overwrite existing code

false

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)

# Positional (recommended)
wheels generate view user show

# OR all named
wheels g view objectName=user name=show

Creates: /views/users/show.cfm with empty content

View with CRUD template

# Positional with template (recommended)
wheels generate view user show crud/show

# OR all named
wheels g view objectName=user name=show template=crud/show

Creates: /views/users/show.cfm using the show template

Edit form with template

# Positional (recommended)
wheels generate view user edit crud/edit

Creates: /views/users/edit.cfm using the edit template

Form partial

# Positional (recommended)
wheels generate view user _form crud/_form

Creates: /views/users/_form.cfm using the form partial template

Index view

# Positional (recommended)
wheels generate view product index crud/index

Creates: /views/products/index.cfm using the index template

Force overwrite existing file

# Positional + flag (recommended)
wheels generate view user show --force

# OR all named
wheels g view objectName=user name=show force=true

Overwrites existing /views/users/show.cfm

Generated Code Examples

Without Template (blank file)

<!--- View file created by wheels generate view --->

With CRUD Index Template

<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>

Form View (new.cfm)

<h1>New Product</h1>

#includePartial("/products/form")#

Form Partial (_form.cfm)

#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()#

Show View (show.cfm)

<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>

View Templates

Available Templates

Template
Description
Use Case

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

Template Structure

Templates are located in:

~/.commandbox/cfml/modules/wheels-cli/templates/views/
├── default/
│   ├── index.cfm
│   ├── show.cfm
│   ├── new.cfm
│   ├── edit.cfm
│   └── _form.cfm
├── bootstrap/
└── custom/

Partial Views

Naming Convention

Partials start with underscore:

  • _form.cfm - Form partial

  • _item.cfm - List item partial

  • _sidebar.cfm - Sidebar partial

Generate Partials

wheels generate view shared header,footer,navigation --partial

Using Partials

<!--- In layout or view --->
#includePartial("/shared/header")#
#includePartial("/products/form", product=product)#
#includePartial(partial="item", query=products)#

Layout Integration

With Layout (default)

<!--- Generated view assumes layout wrapper --->
<h1>Page Title</h1>
<p>Content here</p>

Without Layout

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>

Custom Formats

HTML Format

wheels generate view products index --format=html

Creates: /views/products/index.html

Custom Extensions

wheels generate view emails welcome --format=txt

Creates: /views/emails/welcome.txt

Ajax Views

Generate AJAX View

wheels generate view products search --template=ajax

AJAX Template Example

<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>

Form Helpers

Standard Form

#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()#

File Upload Form

#startFormTag(action="upload", multipart=true)#
    #fileFieldTag(name="productImage", label="Product Image", accept="image/*", class="form-control")#
    #submitTag(value="Upload", class="btn btn-primary")#
#endFormTag()#

Responsive Design

Mobile-First Template

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>

Localization

Generate Localized Views

wheels generate view products index --locale=es

Creates: /views/products/index_es.cfm

Localized Content

<h1>#l("products.title")#</h1>
<p>#l("products.description")#</p>

#linkTo(text=l("buttons.new"), action="new", class="btn btn-primary")#

Testing Views

Generate View Tests

wheels generate view products index
wheels generate test view products/index

View Test Example

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);
    }
    
}

Performance Optimization

Caching Views

<cfcache action="cache" timespan="#CreateTimeSpan(0,1,0,0)#">
    <!--- Expensive view content --->
    #includePartial("products/list", products=products)#
</cfcache>

Lazy Loading

<div class="products-container" data-lazy-load="/products/more">
    <!--- Initial content --->
</div>

<script>
// Implement lazy loading
</script>

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

<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>

Loading State

<div class="loading-spinner" style="display: none;">
    <i class="fa fa-spinner fa-spin"></i> Loading...
</div>

Error State

<cfif structKeyExists(variables, "error")>
    <div class="alert alert-danger">
        <strong>Error:</strong> #error.message#
    </div>
</cfif>

See Also

  • wheels generate controller - Generate controllers

  • wheels scaffold - Generate complete CRUD

  • wheels generate test - Generate view tests

Service Architecture

Understanding the Wheels CLI service layer architecture.

Overview

The Wheels CLI uses a service-oriented architecture that separates concerns and provides reusable functionality across commands. This architecture makes the CLI more maintainable, testable, and extensible.

Architecture Diagram

┌─────────────────┐
│    Commands     │  ← User interacts with
├─────────────────┤
│    Services     │  ← Business logic
├─────────────────┤
│     Models      │  ← Data and utilities
├─────────────────┤
│   Templates     │  ← Code generation
└─────────────────┘

Core Components

1. Commands (/commands/wheels/)

Commands are the user-facing interface:

// commands/wheels/generate/model.cfc
component extends="wheels-cli.models.BaseCommand" {
    
    property name="codeGenerationService" inject="CodeGenerationService@wheels-cli";
    property name="migrationService" inject="MigrationService@wheels-cli";
    
    function run(
        required string name,
        boolean migration = true,
        string properties = "",
        boolean force = false
    ) {
        // Delegate to services
        var result = codeGenerationService.generateModel(argumentCollection=arguments);
        
        if (arguments.migration) {
            migrationService.createTableMigration(arguments.name, arguments.properties);
        }
        
        print.greenLine("✓ Model generated successfully");
    }
}

2. Services (/models/)

Services contain reusable business logic:

// models/CodeGenerationService.cfc
component accessors="true" singleton {
    
    property name="templateService" inject="TemplateService@wheels-cli";
    property name="fileService" inject="FileService@wheels-cli";
    
    function generateModel(
        required string name,
        string properties = "",
        struct associations = {},
        boolean force = false
    ) {
        var modelName = helpers.capitalize(arguments.name);
        var template = templateService.getTemplate("model");
        var content = templateService.populateTemplate(template, {
            modelName: modelName,
            properties: parseProperties(arguments.properties),
            associations: arguments.associations
        });
        
        return fileService.writeFile(
            path = "/models/#modelName#.cfc",
            content = content,
            force = arguments.force
        );
    }
}

3. Base Classes

BaseCommand (/models/BaseCommand.cfc)

All commands extend from BaseCommand:

component extends="commandbox.system.BaseCommand" {
    
    property name="print" inject="print";
    property name="fileSystemUtil" inject="FileSystem";
    
    function init() {
        // Common initialization
        return this;
    }
    
    // Common helper methods
    function ensureDirectoryExists(required string path) {
        if (!directoryExists(arguments.path)) {
            directoryCreate(arguments.path, true);
        }
    }
    
    function resolvePath(required string path) {
        return fileSystemUtil.resolvePath(arguments.path);
    }
}

Service Catalog

Core Services

TemplateService

Manages code generation templates with override system:

  • Template Loading: Searches app snippets first, then CLI templates

  • Variable Substitution: Replaces placeholders with actual values

  • Custom Template Support: Apps can override any CLI template

  • Path Resolution: app/snippets/ overrides /cli/templates/

  • Dynamic Content: Generates form fields, validations, relationships

Key features:

  • Template hierarchy allows project customization

  • Preserves markers for future CLI additions

  • Supports conditional logic in templates

  • Handles both simple and complex placeholders

See Template System Guide for detailed documentation.

MigrationService

Handles database migrations:

  • Generate migration files

  • Track migration status

  • Execute migrations

TestService

Testing functionality:

  • Run TestBox tests

  • Generate coverage reports

  • Watch mode support

CodeGenerationService

Centralized code generation:

  • Generate models, controllers, views

  • Handle associations

  • Manage validations

Feature Services

AnalysisService

Code analysis tools:

  • Complexity analysis

  • Code style checking

  • Dependency analysis

SecurityService

Security scanning:

  • SQL injection detection

  • XSS vulnerability scanning

  • Hardcoded credential detection

OptimizationService

Performance optimization:

  • Cache analysis

  • Query optimization

  • Asset optimization

PluginService

Plugin management:

  • Install/remove plugins

  • Version management

  • Dependency resolution

EnvironmentService

Environment management:

  • Environment switching

  • Configuration management

  • Docker/Vagrant support

Dependency Injection

Services use WireBox for dependency injection:

// In ModuleConfig.cfc
function configure() {
    // Service mappings
    binder.map("TemplateService@wheels-cli")
        .to("wheels.cli.models.TemplateService")
        .asSingleton();
    
    binder.map("MigrationService@wheels-cli")
        .to("wheels.cli.models.MigrationService")
        .asSingleton();
}

Creating a New Service

1. Create Service Component

// models/MyNewService.cfc
component accessors="true" singleton {
    
    // Inject dependencies
    property name="fileService" inject="FileService@wheels-cli";
    property name="print" inject="print";
    
    function init() {
        return this;
    }
    
    function doSomething(required string input) {
        // Service logic here
        return processInput(arguments.input);
    }
    
    private function processInput(required string input) {
        // Private helper methods
        return arguments.input.trim();
    }
}

2. Register Service

In /ModuleConfig.cfc:

binder.map("MyNewService@wheels-cli")
    .to("wheels.cli.models.MyNewService")
    .asSingleton();

3. Use in Commands

component extends="wheels-cli.models.BaseCommand" {
    
    property name="myNewService" inject="MyNewService@wheels-cli";
    
    function run(required string input) {
        var result = myNewService.doSomething(arguments.input);
        print.line(result);
    }
}

Service Patterns

1. Singleton Pattern

Most services are singletons:

component singleton {
    // Shared instance across commands
}

2. Factory Pattern

For creating multiple instances:

component {
    function createGenerator(required string type) {
        switch(arguments.type) {
            case "model":
                return new ModelGenerator();
            case "controller":
                return new ControllerGenerator();
        }
    }
}

3. Strategy Pattern

For different implementations:

component {
    function setStrategy(required component strategy) {
        variables.strategy = arguments.strategy;
    }
    
    function execute() {
        return variables.strategy.execute();
    }
}

Testing Services

Unit Testing Services

component extends="wheels.Testbox" {
    
    function beforeAll() {
        // Create service instance
        variables.templateService = createMock("wheels.cli.models.TemplateService");
    }
    
    function run() {
        describe("TemplateService", function() {
            
            it("loads templates correctly", function() {
                var template = templateService.getTemplate("model");
                expect(template).toBeString();
                expect(template).toInclude("component extends=""Model""");
            });
            
            it("substitutes variables", function() {
                var result = templateService.populateTemplate(
                    "Hello {{name}}",
                    {name: "World"}
                );
                expect(result).toBe("Hello World");
            });
            
        });
    }
}

Mocking Dependencies

function beforeAll() {
    // Create mock
    mockFileService = createEmptyMock("FileService");
    
    // Define behavior
    mockFileService.$("writeFile").$results(true);
    
    // Inject mock
    templateService.$property(
        propertyName = "fileService",
        mock = mockFileService
    );
}

Best Practices

1. Single Responsibility

Each service should have one clear purpose:

// Good: Focused service
component name="ValidationService" {
    function validateModel() {}
    function validateController() {}
}

// Bad: Mixed responsibilities
component name="UtilityService" {
    function validateModel() {}
    function sendEmail() {}
    function generatePDF() {}
}

2. Dependency Injection

Always inject dependencies:

// Good: Injected dependency
property name="fileService" inject="FileService@wheels-cli";

// Bad: Direct instantiation
variables.fileService = new FileService();

3. Error Handling

Services should handle errors gracefully:

function generateFile(required string path) {
    try {
        // Attempt operation
        fileWrite(arguments.path, content);
        return {success: true};
    } catch (any e) {
        // Log error
        logError(e);
        // Return error info
        return {
            success: false,
            error: e.message
        };
    }
}

4. Configuration

Services should be configurable:

component {
    property name="settings" inject="coldbox:modulesettings:wheels-cli";
    
    function getTimeout() {
        return variables.settings.timeout ?: 30;
    }
}

Service Communication

Event-Driven

Services can emit events:

// In service
announce("wheels-cli:modelGenerated", {model: modelName});

// In listener
function onModelGenerated(event, data) {
    // React to event
}

Direct Communication

Services can call each other:

component {
    property name="validationService" inject="ValidationService@wheels-cli";
    
    function generateModel() {
        // Validate first
        if (!validationService.isValidModelName(name)) {
            throw("Invalid model name");
        }
        // Continue generation
    }
}

Extending Services

Creating Service Plugins

// models/plugins/MyServicePlugin.cfc
component implements="IServicePlugin" {
    
    function enhance(required component service) {
        // Add new method
        arguments.service.myNewMethod = function() {
            return "Enhanced!";
        };
    }
}

Service Decorators

component extends="BaseService" {
    
    property name="decoratedService";
    
    function init(required component service) {
        variables.decoratedService = arguments.service;
        return this;
    }
    
    function doSomething() {
        // Add behavior
        log("Calling doSomething");
        // Delegate
        return variables.decoratedService.doSomething();
    }
}

Performance Considerations

1. Lazy Loading

Load services only when needed:

function getTemplateService() {
    if (!structKeyExists(variables, "templateService")) {
        variables.templateService = getInstance("TemplateService@wheels-cli");
    }
    return variables.templateService;
}

2. Caching

Cache expensive operations:

component {
    property name="cache" default={};
    
    function getTemplate(required string name) {
        if (!structKeyExists(variables.cache, arguments.name)) {
            variables.cache[arguments.name] = loadTemplate(arguments.name);
        }
        return variables.cache[arguments.name];
    }
}

3. Async Operations

Use async for long-running tasks:

function analyzeLargeCodebase() {
    thread name="analysis-#createUUID()#" {
        // Long running analysis
    }
}

Debugging Services

Enable Debug Logging

component {
    property name="log" inject="logbox:logger:{this}";
    
    function doSomething() {
        log.debug("Starting doSomething with args: #serializeJSON(arguments)#");
        // ... logic ...
        log.debug("Completed doSomething");
    }
}

Service Inspection

# In CommandBox REPL
repl> getInstance("TemplateService@wheels-cli")
repl> getMetadata(getInstance("TemplateService@wheels-cli"))

Future Enhancements

Planned service architecture improvements:

  1. Service Mesh: Inter-service communication layer

  2. Service Discovery: Dynamic service registration

  3. Circuit Breakers: Fault tolerance patterns

  4. Service Metrics: Performance monitoring

  5. API Gateway: Unified service access

See Also

  • Creating Custom Commands

  • Template System

  • Testing Guide

  • Contributing Guide

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.

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:

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.

Datasource Configuration Methods

In Wheels applications, you typically configure which datasource to use in /config/settings.cfm. The actual datasource definitions should be added in /config/app.cfm.

Wheels will look for the datasource defined in settings.cfm and match it against what you’ve defined in app.cfm or through your CFML engine’s administrator.


Option 1: Datasource Configuration via Administrator (Adobe & Lucee)

You can manage datasources through the Administrator interface of your CFML engine:

  1. Access your CFML administrator (Adobe ColdFusion Administrator or Lucee Server/Web Administrator).

  2. Navigate to the Datasource section.

  3. Create a new datasource with the exact same name as the one you’ve set in settings.cfm (dataSourceName).

  4. Provide connection details (JDBC driver, connection string, username, password).

  5. Configure optional features like connection pooling and validation queries.

This method lets you manage database connectivity centrally in the engine’s admin console.


Option 2: Datasource Configuration via /config/app.cfm (Lucee & BoxLang)

You can also define datasources programmatically in your Wheels application using the this.datasources struct inside /config/app.cfm. This approach works in Lucee and BoxLang without needing Administrator access.

Regardless of which method you choose, ensure that the datasource name and configuration in your Wheels configuration matches exactly with the datasource created in your CFML engine.

Our Sample Data Structure

Wheels supports MySQL, SQL Server, PostgreSQL, Oracle 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

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 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:

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:

This will create URL endpoints for creating, reading, updating, and deleting user records:

Name
Method
URL Path
Description
  • 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:

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 function takes parameters similar to the 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 .

To end the form, we use the function. Easy enough.

The and helpers are similar. As you probably guessed, they create <input> elements with type="text" and type="password", respectively. And the function creates an <input type="submit" /> element.

One thing you'll notice is the and 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 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 method.

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:8080/users/new, we'll see the form with the fields that we defined.

The HTML generated by your application will look something like this:

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 method:

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 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:

This call to the model's 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

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 form helper function to add an "Edit" button to the form. This action expects a key as well.

Because in the 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:

The view at /app/views/users/edit.cfm looks almost exactly the same as the view for creating a user:

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:

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:

To update the user, simply call its method with the user struct passed from the form via params. It's that simple.

After the update, we'll add a success message 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:

We simply load the user using the model's method and then call the object's 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.

Upgrading

Instructions for upgrading Wheels applications

Wheels follows Semantic Versioning ():

  • Major releases (e.g., 2.x.x → 3.x.x) may include breaking changes and require code adjustments.

  • Minor releases (e.g., 3.0.x → 3.1.x) may add new features in a backwards-compatible way.

  • Patch releases (e.g., 3.0.1 → 3.0.2) focus on bug fixes.

Upgrading an existing application involves more than swapping the wheels folder — it often requires project restructuring, dependency updates, and code modifications.


General Upgrade Process for Existing Applications

1. Backup First

  • Create a full backup of your application code and database.

  • Commit all changes if using version control.

2. Read All Relevant Upgrade Notes

  • If skipping versions, review the upgrade notes for every version in between.

  • Some changes require manual adjustments outside the wheels folder.

3. Use a Safe Environment

  • Test upgrades in a local or staging environment before production.

4. Upgrade Steps

  1. Follow the version-specific changes listed below.

  2. Replace/update the wheels folder with the version you are upgrading to.

  3. Apply all required file moves, renames, and deletions.

  4. Install new dependencies (box install if using CommandBox).

  5. Update code for removed or renamed functions.

  6. Check plugins for compatibility.

5. Test Thoroughly

  • Run your test suite.

  • Fix routing, mappings, or missing dependency issues.

6. Deploy to Production

  • Clear caches and restart the CFML engine after deployment.


Upgrading to Wheels 3.0.0

This guide walks you through upgrading an existing Wheels application to version 3.0.0. It covers compatibility changes, the new folder structure, and the CommandBox (box.json) dependency management process for the vendor/ folder.

1. Compatibility Changes

  • No longer supported: Adobe ColdFusion 2016 and earlier.

  • Supported engines:

    • Adobe ColdFusion 2018+

    • Lucee 5+

    • BoxLang

2. New Folder Structure

Wheels 3.x adopts a more modern and organized structure. Update your application as follows:

2.1 Application Code

Move your app code into a new app/ directory at the root:

2.2 Configurations

Keep the config/ folder at the root.

2.3 Database Migrations

Keep the db/ folder at the root.

2.4 Public Assets

Keep the public/ folder at the root. Move all static assets into public/:

2.5 Updated Core Files

Inside public/, replace the following with the latest versions from Wheels 3.x:


3. Vendor Folder Changes — Managed with CommandBox

Wheels 3.x manages its core files and related libraries via CommandBox. This means no manually downloading or replacing vendor files.

3.1 Create or Update box.json

At your project root, define dependencies and where they should be installed:

Explanation:

  • dependencies: Lists the packages your app needs, with version ranges.

  • installPaths: Ensures each dependency installs into a specific folder inside vendor/.

3.2 Install Dependencies

Run:

This will:

  • Download wheels-core, wirebox, and testbox

  • Place them into the correct vendor/ subfolders based on installPaths

3.3 Check Application Mappings

Ensure Application.cfc includes mappings for the new locations:

Mappings must point to the correct folders inside vendor/ so Wheels can find its core files and libraries.

4. Before & After Folder Layout

Before (Wheels 2.x)

After (Wheels 3.x)

5. Final Steps After Upgrade

  1. Run box install to ensure all dependencies are fetched.

  2. Review and update Application.cfc mappings.

  3. Test your application thoroughly.

  4. Check the Wheels 3.x release notes for any breaking changes affecting your code.

  5. Commit the new structure and box.json so the setup is reproducible for all team members.

6. Benefits of the New Approach

  • No manual vendor updates — dependencies are versioned and reproducible.

  • Easier upgrades — box update replaces manual downloads.

  • Consistent environments — the same box.json ensures all developers run the same versions.


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 and 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 function has been removed in Wheels 2.0 in favor of a new routing API. See the 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 mapper method:

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 to the config method.

  2. Add a call to the 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 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 for more information.

Note: If you had previously installed the , 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 .

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).

Creating Commands

Learn how to extend Wheels CLI with your own custom commands.

Overview

Wheels CLI is built on CommandBox, making it easy to add custom commands. Commands can be simple scripts or complex operations using the service architecture.

Basic Command Structure

1. Create Command File

Create a new file in /commands/wheels/:

2. Run Your Command

Command Anatomy

Component Structure

Command Help

CommandBox generates help from your code:

Advanced Commands

1. Multi-Level Commands

Create nested command structure:

Usage:

2. Interactive Commands

Get user input:

3. Progress Indicators

Show progress for long operations:

Using Services

1. Inject Existing Services

2. Create Custom Service

File Operations

Reading Files

Writing Files

Directory Operations

Output Formatting

Colored Output

Tables

Trees

Error Handling

Basic Error Handling

Custom Error Messages

Command Testing

Unit Testing Commands

Integration Testing

Best Practices

1. Command Naming

  • Use verbs for actions: generate, create, deploy

  • Use nouns for resources: model, controller, migration

  • Be consistent with existing commands

2. Argument Validation

3. Provide Feedback

4. Make Commands Idempotent

Publishing Commands

1. Package as Module

Create box.json:

2. Module Structure

3. Publish to ForgeBox

Examples

Database Backup Command

Code Quality Command

See Also

/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://wheels.dev/3.0.0/guides/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.
		You can also uncomment the 2 "set" functions below them to set the username and password for the 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>
/config/settings.cfm
set(dataSourceName="back2thefuture");
// set(dataSourceUserName="marty");
// set(dataSourcePassword="mcfly");
/config/app.cfm
component {
    this.name = "WheelsApp";
    
    // Define a datasource for use in settings.cfm
    this.datasources["wheels-dev"] = {
        class: "com.mysql.cj.jdbc.Driver",
        connectionString: "yourConnectionString",
        username: "yourUsername",
        password: "yourPassword"
    };
}

id

int

auto increment

username

varchar(100)

email

varchar(255)

passwd

varchar(15)

/config/routes.cfm
mapper()
    .wildcard()
    .root(method = "get")
.end();
/config/routes.cfm
mapper()
    .resources("users")
    .wildcard()
    .root(method = "get")
.end();

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

/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>
/app/controllers/Users.cfc
component extends="Controller" {
    function config(){}

    function new() {
        user = model("user").new();
    }
}
/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>
/app/controllers/Users.cfc
function create() {
    user = model("user").create(params.user);

    redirectTo(
        route="users",
        success="User created successfully."
    );
}
/app/controllers/Users.cfc
function index() {
    users = model("user").findAll(order="username");
}
/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>
/app/controllers/Users.cfc
function edit() {
    user = model("user").findByKey(params.key);
}
/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>
/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>
/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."
    );
}
/app/controllers/Users.cfc
function delete() {
    user = model("user").findByKey(params.key);
    user.delete();

    redirectTo(
        route="users",
        success="User deleted successfully."
    );
}
conventions
startFormTag()
linkTo()
linkTo()
endFormTag()
textField()
passwordField()
submitTag()
textField()
passwordField()
model()
new()
create()
redirectTo()
findAll()
linkTo()
linkTo()
update()
using the Flash
findByKey()
delete()
app/controllers/
app/global/
app/migrator/
app/models/
app/views/
plugins/
public/images/
public/files/
public/javascripts/
public/miscellaneous/
public/stylesheets/
Application.cfc
index.cfm
urlrewrite.xml
{
    "name": "wheels-app",
    "version": "1.0.0",
    "dependencies": {
        "wirebox": "^7.0.0",
        "testbox": "^6.0.0",
        "wheels-core": "^3.0.0-SNAPSHOT"
    },
    "installPaths": {
        "wirebox": "vendor/wirebox/",
        "testbox": "vendor/testbox/",
        "wheels-core": "vendor/wheels/"
    }
}
box install
/app
/config
/db
/public
/vendor
  /wheels
  /wirebox
  /testbox
/tests
/controllers
/global
/migrator
/models
/plugins
/views
/config
/db
/public
/vendor (manual files)
Application.cfc
index.cfm
urlrewrite.xml
/app
    /controllers
    /global
    /migrator
    /models
    /plugins
    /views
/config
/db
/public
    Application.cfc
    index.cfm
    urlrewrite.xml
/vendor
    /wheels
    /wirebox
    /testbox
.env
box.json
server.json
/config/routes.cfm
mapper()
    .wildcard()
.end();
semver.org
JS Confirm
JS Disable
addRoute()
Routing
wildcard()
protectsFromForgery()
csrfMetaTags()
csrfMetaTags()
CSRF Protection Plugin
CSRF Protection plugin
http://www.domain.com/site-admin/edit-layout
// commands/wheels/hello.cfc
component extends="wheels-cli.models.BaseCommand" {
    
    /**
     * Say hello
     */
    function run(string name = "World") {
        print.line("Hello, #arguments.name#!");
    }
    
}
wheels hello
# Output: Hello, World!

wheels hello John
# Output: Hello, John!
component extends="wheels-cli.models.BaseCommand" {
    
    // Command metadata
    property name="name" default="mycommand";
    property name="description" default="Does something useful";
    
    // Service injection
    property name="myService" inject="MyService@wheels-cli";
    
    /**
     * Main command entry point
     * 
     * @name Name of something
     * @force Force overwrite
     * @name.hint The name to use
     * @force.hint Whether to force
     */
    function run(
        required string name,
        boolean force = false
    ) {
        // Command logic here
    }
    
}
wheels hello --help

NAME
  wheels hello

SYNOPSIS
  wheels hello [name]

DESCRIPTION
  Say hello

ARGUMENTS
  name = World
    Name to greet
// commands/wheels/deploy.cfc
component extends="wheels-cli.models.BaseCommand" {
    function run() {
        print.line("Usage: wheels deploy [staging|production]");
    }
}

// commands/wheels/deploy/staging.cfc
component extends="wheels-cli.models.BaseCommand" {
    function run() {
        print.line("Deploying to staging...");
    }
}

// commands/wheels/deploy/production.cfc
component extends="wheels-cli.models.BaseCommand" {
    function run() {
        print.line("Deploying to production...");
    }
}
wheels deploy staging
wheels deploy production
component extends="wheels-cli.models.BaseCommand" {
    
    function run() {
        // Simple input
        var name = ask("What's your name? ");
        
        // Masked input (passwords)
        var password = ask("Enter password: ", "*");
        
        // Confirmation
        if (confirm("Are you sure?")) {
            print.line("Proceeding...");
        }
        
        // Multiple choice
        var choice = multiselect()
            .setQuestion("Select features to install:")
            .setOptions([
                "Authentication",
                "API",
                "Admin Panel",
                "Blog"
            ])
            .ask();
    }
    
}
component extends="wheels-cli.models.BaseCommand" {
    
    function run() {
        // Progress bar
        var progressBar = progressBar.create(total=100);
        
        for (var i = 1; i <= 100; i++) {
            // Do work
            sleep(50);
            
            // Update progress
            progressBar.update(
                current = i,
                message = "Processing item #i#"
            );
        }
        
        progressBar.clear();
        print.greenLine("✓ Complete!");
        
        // Spinner
        var spinner = progressSpinner.create();
        spinner.start("Loading...");
        
        // Do work
        sleep(2000);
        
        spinner.stop();
    }
    
}
component extends="wheels-cli.models.BaseCommand" {
    
    property name="codeGenerationService" inject="CodeGenerationService@wheels-cli";
    property name="templateService" inject="TemplateService@wheels-cli";
    
    function run(required string name) {
        // Use services
        var template = templateService.getTemplate("custom");
        var result = codeGenerationService.generateFromTemplate(
            template = template,
            data = {name: arguments.name}
        );
        
        print.greenLine("Generated: #result.path#");
    }
    
}
// models/CustomService.cfc
component singleton {
    
    function processData(required struct data) {
        // Service logic
        return data;
    }
    
}

// Register in ModuleConfig.cfc
binder.map("CustomService@wheels-cli")
    .to("wheels.cli.models.CustomService")
    .asSingleton();
function run(required string file) {
    var filePath = resolvePath(arguments.file);
    
    if (!fileExists(filePath)) {
        error("File not found: #filePath#");
    }
    
    var content = fileRead(filePath);
    print.line(content);
}
function run(required string name) {
    var content = "Hello, #arguments.name#!";
    var filePath = resolvePath("output.txt");
    
    if (fileExists(filePath) && !confirm("Overwrite existing file?")) {
        return;
    }
    
    fileWrite(filePath, content);
    print.greenLine("✓ File created: #filePath#");
}
function run(required string dir) {
    // Create directory
    ensureDirectoryExists(arguments.dir);
    
    // List files
    var files = directoryList(
        path = resolvePath(arguments.dir),
        recurse = true,
        filter = "*.cfc"
    );
    
    for (var file in files) {
        print.line(file);
    }
}
function run() {
    // Basic colors
    print.line("Normal text");
    print.redLine("Error message");
    print.greenLine("Success message");
    print.yellowLine("Warning message");
    print.blueLine("Info message");
    
    // Bold
    print.boldLine("Important!");
    print.boldRedLine("Critical error!");
    
    // Inline colors
    print.line("This is #print.red('red')# and #print.green('green')#");
}
function run() {
    // Create table
    print.table([
        ["Name", "Type", "Size"],
        ["users.cfc", "Model", "2KB"],
        ["posts.cfc", "Model", "3KB"],
        ["comments.cfc", "Model", "1KB"]
    ]);
    
    // With headers
    var data = queryNew("name,type,size", "varchar,varchar,varchar", [
        ["users.cfc", "Model", "2KB"],
        ["posts.cfc", "Model", "3KB"]
    ]);
    
    print.table(
        data = data,
        headers = ["File Name", "Type", "File Size"]
    );
}
function run() {
    print.tree([
        {
            label: "models",
            children: [
                {label: "User.cfc"},
                {label: "Post.cfc"},
                {label: "Comment.cfc"}
            ]
        },
        {
            label: "controllers",
            children: [
                {label: "Users.cfc"},
                {label: "Posts.cfc"}
            ]
        }
    ]);
}
function run(required string file) {
    try {
        var content = fileRead(arguments.file);
        processFile(content);
        print.greenLine("✓ Success");
    } catch (any e) {
        print.redLine("✗ Error: #e.message#");
        
        if (arguments.verbose ?: false) {
            print.line(e.detail);
            print.line(e.stacktrace);
        }
        
        // Set exit code
        return 1;
    }
}
function run(required string name) {
    // Validation
    if (!isValidName(arguments.name)) {
        error("Invalid name. Names must be alphanumeric.");
    }

    // Warnings
    if (hasSpecialChars(arguments.name)) {
        print.yellowLine("Warning: Special characters detected");
    }

    // Success
    print.greenLine("Name is valid");
}

private function error(required string message) {
    print.redLine("#arguments.message#");
    exit(1);
}
// tests/commands/HelloTest.cfc
component extends="wheels.Testbox" {
    
    function run() {
        describe("Hello Command", function() {
            
            it("greets with default name", function() {
                var result = execute("wheels hello");
                expect(result).toInclude("Hello, World!");
            });
            
            it("greets with custom name", function() {
                var result = execute("wheels hello John");
                expect(result).toInclude("Hello, John!");
            });
            
        });
    }
    
    private function execute(required string command) {
        // Capture output
        savecontent variable="local.output" {
            shell.run(arguments.command);
        }
        return local.output;
    }
    
}
it("generates files correctly", function() {
    // Run command
    execute("wheels generate custom test");
    
    // Verify files created
    expect(fileExists("/custom/test.cfc")).toBeTrue();
    
    // Verify content
    var content = fileRead("/custom/test.cfc");
    expect(content).toInclude("component");
    
    // Cleanup
    fileDelete("/custom/test.cfc");
});
function run(required string name, string type = "default") {
    // Validate required
    if (!len(trim(arguments.name))) {
        error("Name cannot be empty");
    }
    
    // Validate options
    var validTypes = ["default", "custom", "advanced"];
    if (!arrayFind(validTypes, arguments.type)) {
        error("Invalid type. Must be one of: #arrayToList(validTypes)#");
    }
}
function run() {
    print.line("Starting process...").toConsole();
    
    // Show what's happening
    print.indentedLine("→ Loading configuration");
    var config = loadConfig();
    
    print.indentedLine("→ Processing files");
    var count = processFiles();
    
    print.indentedLine("→ Saving results");
    saveResults();
    
    print.greenBoldLine("✓ Complete! Processed #count# files.");
}
function run(required string name) {
    var filePath = resolvePath("#arguments.name#.txt");
    
    // Check if already exists
    if (fileExists(filePath)) {
        print.yellowLine("File already exists, skipping");
        return;
    }
    
    // Create file
    fileWrite(filePath, "content");
    print.greenLine("✓ Created file");
}
{
    "name": "my-wheels-commands",
    "version": "1.0.0",
    "type": "commandbox-modules",
    "dependencies": {
        "wheels-cli": "^3.0.0"
    }
}
my-wheels-commands/
├── ModuleConfig.cfc
├── commands/
│   └── wheels/
│       └── mycommand.cfc
└── models/
    └── MyService.cfc
box forgebox publish
// commands/wheels/db/backup.cfc
component extends="wheels-cli.models.BaseCommand" {
    
    property name="datasource" inject="coldbox:datasource";
    
    function run(string file = "backup-#dateFormat(now(), 'yyyy-mm-dd')#.sql") {
        print.line("Creating database backup...").toConsole();
        
        var spinner = progressSpinner.create();
        spinner.start("Backing up database");
        
        try {
            // Get database info
            var dbInfo = getDatabaseInfo();
            
            // Create backup
            var backupPath = resolvePath(arguments.file);
            createBackup(dbInfo, backupPath);
            
            spinner.stop();
            print.greenBoldLine("✓ Backup created: #backupPath#");
            
        } catch (any e) {
            spinner.stop();
            print.redLine("✗ Backup failed: #e.message#");
            return 1;
        }
    }
    
}
// commands/wheels/quality.cfc
component extends="wheels-cli.models.BaseCommand" {
    
    property name="analysisService" inject="AnalysisService@wheels-cli";
    
    function run(string path = ".", boolean fix = false) {
        var issues = analysisService.analyze(arguments.path);
        
        if (arrayLen(issues)) {
            print.redLine("Found #arrayLen(issues)# issues:");
            
            for (var issue in issues) {
                print.line("#issue.file#:#issue.line# - #issue.message#");
            }
            
            if (arguments.fix) {
                print.line().line("Attempting fixes...");
                var fixed = analysisService.fix(issues);
                print.greenLine("Fixed #fixed# issues");
            }
        } else {
            print.greenLine("✓ No issues found!");
        }
    }
    
}
Service Architecture
Testing Guide
CommandBox Documentation
Contributing to Wheels CLI

Migrations Guide

Learn how to manage database schema changes effectively using Wheels CLI migrations.

Overview

Database migrations provide version control for your database schema. They allow you to:

  • Track schema changes over time

  • Share database changes with your team

  • Deploy schema updates safely

  • Roll back changes if needed

  • Keep database and code in sync

Migration Basics

What is a Migration?

A migration is a CFC file that describes a database change. Each migration has:

  • A timestamp-based version number

  • An up() method to apply changes

  • An optional down() method to reverse changes

Migration Files

Migrations are stored in /app/migrator/migrations/ with this naming convention:

[YYYYMMDDHHmmss]_[description].cfc

Example:

20240125143022_create_users_table.cfc
20240125143523_add_email_to_users.cfc

Creating Migrations

Generate Migration Commands

# Create blank migration
wheels dbmigrate create blank add_status_to_orders

# Create table migration
wheels dbmigrate create table products

# Add column migration
wheels dbmigrate create column users email

Migration Structure

Basic migration template:

component extends="wheels.migrator.Migration" {
    
    function up() {
        transaction {
            // Apply changes
        }
    }
    
    function down() {
        transaction {
            // Reverse changes
        }
    }
    
}

Table Operations

Creating Tables

function up() {
    transaction {
        t = createTable("products");
        
        // Primary key (auto-created as 'id' by default)
        t.primaryKey("productId"); // Custom primary key
        
        // Column types
        t.string(columnNames="name", limit=100);
        t.text(columnNames="description");
        t.text(columnNames="content", size="mediumtext"); // MySQL only: mediumtext (16MB)
        t.text(columnNames="longDescription", size="longtext"); // MySQL only: longtext (4GB)
        t.integer(columnNames="quantity");
        t.bigInteger(columnNames="views");
        t.float(columnNames="weight");
        t.decimal(columnNames="price", precision=10, scale=2);
        t.boolean(columnNames="active", default=true);
        t.date(columnNames="releaseDate");
        t.datetime(columnNames="publishedAt");
        t.timestamp(columnNames="lastModified");
        t.time(columnNames="openingTime");
        t.binary(columnNames="data");
        t.uniqueidentifier(columnNames="uniqueId");
        
        // Special columns
        t.timestamps(); // Creates createdAt and updatedAt
        t.references(referenceNames="user"); // Creates userId foreign key
        
        // Create the table
        t.create();
    }
}

Table Options

function up() {
    transaction {
        t = createTable("products", 
            id=false, // Don't create auto-increment id
            force=true, // Drop if exists
            options="ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
        );
        
        // Composite primary key
        t.primaryKey(["orderId", "productId"]);
        
        t.create();
    }
}

Dropping Tables

function down() {
    transaction {
        dropTable("products");
    }
}

Column Operations

Adding Columns

function up() {
    transaction {
        addColumn(
            table="users",
            column="phoneNumber",
            type="string",
            limit=20,
            allowNull=true
        );
        
        // Multiple columns
        t = changeTable("users");
        t.string(columnNames="address");
        t.string(columnNames="city");
        t.string(columnNames="postalCode", limit=10);
        t.update();
    }
}

Modifying Columns

function up() {
    transaction {
        changeColumn(
            table="products",
            column="price",
            type="decimal",
            precision=12,
            scale=2,
            allowNull=false,
            default=0
        );
    }
}

Renaming Columns

function up() {
    transaction {
        renameColumn(
            table="users",
            column="email_address",
            newName="email"
        );
    }
}

Removing Columns

function up() {
    transaction {
        removeColumn(table="users", column="deprecated_field");
        
        // Multiple columns
        t = changeTable("products");
        t.removeColumn("oldPrice");
        t.removeColumn("legacyCode");
        t.update();
    }
}

Index Operations

Creating Indexes

function up() {
    transaction {
        // Simple index
        addIndex(table="users", columnNames="email");
        
        // Unique index
        addIndex(
            table="users",
            columnNames="username",
            unique=true
        );
        
        // Composite index
        addIndex(
            table="products",
            columnNames="category,status",
            name="idx_category_status"
        );
        
        // In table creation
        t = createTable("orders");
        t.string(columnNames="orderNumber");
        t.index("orderNumber", unique=true);
        t.create();
    }
}

Removing Indexes

function down() {
    transaction {
        removeIndex(table="users", name="idx_users_email");
        
        // Or by column
        removeIndex(table="products", column="sku");
    }
}

Foreign Keys

Adding Foreign Keys

function up() {
    transaction {
        // Simple foreign key
        addForeignKey(
            table="orders",
            column="userId",
            referenceTable="users",
            referenceColumn="id"
        );
        
        // With options
        addForeignKey(
            table="orderItems",
            column="orderId",
            referenceTable="orders",
            referenceColumn="id",
            onDelete="CASCADE",
            onUpdate="CASCADE"
        );
        
        // In table creation
        t = createTable("posts");
        t.references(referenceNames="user", onDelete="SET NULL");
        t.references(referenceNames="category", foreignKey=true);
        t.create();
    }
}

Removing Foreign Keys

function down() {
    transaction {
        removeForeignKey(
            table="orders",
            name="fk_orders_users"
        );
    }
}

Data Migrations

Inserting Data

function up() {
    transaction {
        // Single record
        sql("
            INSERT INTO roles (name, description, createdAt) 
            VALUES ('admin', 'Administrator', NOW())
        ");
        
        // Multiple records
        addRecord(table="permissions", name="users.create");
        addRecord(table="permissions", name="users.read");
        addRecord(table="permissions", name="users.update");
        addRecord(table="permissions", name="users.delete");
    }
}

Updating Data

function up() {
    transaction {
        updateRecord(
            table="products",
            where="status IS NULL",
            values={status: "active"}
        );
        
        // Complex updates
        sql("
            UPDATE users 
            SET fullName = CONCAT(firstName, ' ', lastName)
            WHERE fullName IS NULL
        ");
    }
}

Removing Data

function down() {
    transaction {
        removeRecord(
            table="roles",
            where="name = 'temp_role'"
        );
    }
}

Advanced Migrations

Conditional Migrations

function up() {
    transaction {
        // Check if column exists
        if (!hasColumn("users", "avatar")) {
            addColumn(table="users", column="avatar", type="string");
        }
        
        // Check if table exists
        if (!hasTable("analytics")) {
            t = createTable("analytics");
            t.integer("views");
            t.timestamps();
            t.create();
        }
        
        // Database-specific
        if (getDatabaseType() == "mysql") {
            sql("ALTER TABLE users ENGINE=InnoDB");
        }
    }
}

Using Raw SQL

function up() {
    transaction {
        // Complex operations
        sql("
            CREATE VIEW active_products AS
            SELECT * FROM products
            WHERE active = 1 AND deletedAt IS NULL
        ");
        
        // Stored procedures
        sql("
            CREATE PROCEDURE CleanupOldData()
            BEGIN
                DELETE FROM logs WHERE createdAt < DATE_SUB(NOW(), INTERVAL 90 DAY);
            END
        ");
    }
}

Environment-Specific

function up() {
    transaction {
        // Always run
        addColumn(table="users", column="lastLoginAt", type="datetime");
        
        // Development only
        if (getEnvironment() == "development") {
            // Add test data
            for (var i = 1; i <= 100; i++) {
                addRecord(
                    table="users",
                    email="test#i#@example.com",
                    password="hashed_password"
                );
            }
        }
    }
}

Running Migrations

Basic Commands

# Check migration status
wheels dbmigrate info

# Run all pending migrations
wheels dbmigrate latest

# Run next migration only
wheels dbmigrate up

# Rollback last migration
wheels dbmigrate down

# Run specific version
wheels dbmigrate exec 20240125143022

# Reset all migrations
wheels dbmigrate reset

Migration Workflow

  1. Create migration

    wheels dbmigrate create table orders
  2. Edit migration file

    // Edit /app/migrator/migrations/[timestamp]_create_orders_table.cfc
  3. Test migration

    # Run migration
    wheels dbmigrate latest
    
    # Verify
    wheels dbmigrate info
    
    # Test rollback
    wheels dbmigrate down
  4. Commit and share

    git add db/migrate/
    git commit -m "Add orders table migration"

Best Practices

1. Always Use Transactions

function up() {
    transaction {
        // All operations in transaction
        // Rollback on any error
    }
}

2. Make Migrations Reversible

function up() {
    transaction {
        addColumn(table="users", column="nickname", type="string");
    }
}

function down() {
    transaction {
        removeColumn(table="users", column="nickname");
    }
}

3. One Change Per Migration

# Good: Separate migrations
wheels dbmigrate create blank add_status_to_orders
wheels dbmigrate create blank add_priority_to_orders

# Bad: Multiple unrelated changes
wheels dbmigrate create blank update_orders_and_users

4. Test Migrations Thoroughly

# Test up
wheels dbmigrate latest

# Test down
wheels dbmigrate down

# Test up again
wheels dbmigrate up

5. Never Modify Completed Migrations

# Bad: Editing existing migration
# Good: Create new migration to fix issues
wheels dbmigrate create blank fix_orders_status_column

Common Patterns

Adding Non-Nullable Column

function up() {
    transaction {
        // Add nullable first
        addColumn(table="users", column="role", type="string", allowNull=true);
        
        // Set default values
        updateRecord(table="users", where="1=1", values={role: "member"});
        
        // Make non-nullable
        changeColumn(table="users", column="role", allowNull=false);
    }
}

Renaming Table with Foreign Keys

function up() {
    transaction {
        // Drop foreign keys first
        removeForeignKey(table="posts", name="fk_posts_users");
        
        // Rename table
        renameTable(oldName="posts", newName="articles");
        
        // Recreate foreign keys
        addForeignKey(
            table="articles",
            column="userId",
            referenceTable="users",
            referenceColumn="id"
        );
    }
}

Safe Column Removal

function up() {
    transaction {
        // First migration: deprecate column
        if (getEnvironment() != "production") {
            announce("Column 'users.oldField' is deprecated and will be removed");
        }
    }
}

// Later migration (after code deployment)
function up() {
    transaction {
        removeColumn(table="users", column="oldField");
    }
}

Troubleshooting

Migration Failed

# Check error
wheels dbmigrate info

# Fix migration file
# Retry
wheels dbmigrate latest

Stuck Migration

-- Manually fix schema_migrations table
DELETE FROM schema_migrations WHERE version = '20240125143022';

Performance Issues

function up() {
    // Increase timeout for large tables
    setting requestTimeout="300";
    
    transaction {
        // Add index without locking (MySQL)
        sql("ALTER TABLE large_table ADD INDEX idx_column (column)");
    }
}

Integration with CI/CD

Pre-deployment Check

#!/bin/bash
# Check for pending migrations
if wheels dbmigrate info | grep -q "pending"; then
    echo "Pending migrations detected!"
    wheels dbmigrate info
    exit 1
fi

Automated Deployment

# .github/workflows/deploy.yml
- name: Run migrations
  run: |
    wheels dbmigrate latest
    wheels dbmigrate info

See Also

  • wheels dbmigrate commands - Migration command reference

  • Database Schema - Schema import/export

  • Model Generation - Generate models with migrations

  • Testing Guide - Testing migrations

wheels generate test

Generate test files for models, controllers, views, and other components using TestBox BDD syntax.

Synopsis

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Positional parameters: wheels generate test model User (most common)

  • Named parameters: type=value target=value (e.g., type=model target=User)

  • Flag parameters: --flag equals flag=true (e.g., --crud equals crud=true)

Parameter Mixing Rules:

✅ ALLOWED:

  • All positional: wheels generate test model User

  • All positional + flags: wheels generate test model User --crud --factory

  • All named: type=model target=User crud=true

❌ NOT ALLOWED:

  • Positional + named: wheels generate test model target=User (causes error)

Recommendation: Use positional for type/target, flags for options: wheels generate test model User --crud --factory

Description

The wheels generate test command creates test files for various components of your Wheels application using TestBox 5 BDD syntax. All generated tests use standard CFML cfhttp() for HTTP testing and proper Wheels model() syntax, ensuring compatibility and reliability.

Arguments

Argument
Description
Default

Options

Option
Description
Default

Test Types

The command generates different test structures based on the type:

Type
Location
Purpose
Testing Method

Examples

Basic Model Test

Generate a basic model test with validation and association tests:

Output: tests/specs/models/UserSpec.cfc

Generated Code:

Model Test with CRUD Operations

Generate a model test with create, read, update, delete operations:

Output: tests/specs/models/ProductSpec.cfc

Contains:

  • Basic validation tests

  • it("should create a new product") - Tests model().new() and save()

  • it("should find an existing product") - Tests findByKey()

  • it("should update an existing product") - Tests property updates and save()

  • it("should delete a product") - Tests delete() method

Sample CRUD Test:

Model Test with Factory Pattern

Generate tests using model().create() for test data:

Output: tests/specs/models/OrderSpec.cfc

Generated Code:

Basic Controller Test

Generate HTTP-based controller tests:

Output: tests/specs/controllers/UsersControllerSpec.cfc

Generated Code:

Controller Test with CRUD Actions

Generate full CRUD controller tests via HTTP:

Output: tests/specs/controllers/ProductsControllerSpec.cfc

Contains:

  • it("should list all products (index action)") - Tests GET /products

  • it("should display a specific product (show action)") - Tests GET /products/:id

  • it("should create a new product (create action)") - Tests POST /products

  • it("should update an existing product (update action)") - Tests PUT /products/:id

  • it("should delete a product") - Tests DELETE /products/:id

Sample Controller Test:

View Test

Generate view rendering tests:

Output: tests/specs/views/users/editViewSpec.cfc

Generated Code:

Unit Test

Generate unit tests for custom services/libraries:

Output: tests/specs/unit/OrderProcessorSpec.cfc

Generated Code:

Unit Test with Mock Examples

Generate unit tests with MockBox mocking examples:

Output: tests/specs/unit/PaymentServiceSpec.cfc

Additional Mock Test:

Integration Test

Generate end-to-end workflow tests:

Output: tests/specs/integration/CheckoutFlowIntegrationSpec.cfc

Generated Code:

API Test

Generate API endpoint tests with JSON handling:

Output: tests/specs/integration/api/UsersAPISpec.cfc

Generated Code:

Force Overwrite

Overwrite existing test files without confirmation:

Effect: Overwrites tests/specs/models/UserSpec.cfc without prompting.

Generate and Open

Create test and open in default editor:

Effect: Creates test file and opens it in your system's default .cfc file editor.

Generated Test Features

All generated tests include:

  • TestBox 5 BDD Syntax: Modern describe() and it() syntax

  • Proper Lifecycle Methods: beforeAll(), beforeEach(), afterEach() hooks

  • HTTP-Based Testing: Uses cfhttp() for controller, view, integration, and API tests

  • Model Testing: Uses model().new(), model().create(), and model().findByKey()

  • CRUD Operations: Complete create, read, update, delete test cases (with --crud)

  • Factory Pattern: Test data creation using model().create() (with --factory)

  • Mock Examples: MockBox stub examples (with --mock)

  • JSON Handling: serializeJSON() and deserializeJSON() for API tests

  • Placeholder Comments: Helpful comments guiding test implementation

Common Test Patterns

Model Validation Testing

Model CRUD Testing

HTTP Controller Testing

API JSON Testing

Running Tests

Run the generated tests using the Wheels test command:

Best Practices

  1. Fill in Test Attributes: Generated tests include // Add test attributes comments - replace with actual model attributes

  2. Customize Assertions: Add specific assertions for your application's business logic

  3. Use Factory Pattern: Use --factory flag for tests requiring multiple similar objects

  4. Test Edge Cases: Add tests for empty values, null inputs, boundary conditions

  5. Clean Up Test Data: Use afterEach() or transactions to clean up test data

  6. Use Descriptive Test Names: Keep it() descriptions clear and specific

Troubleshooting

Tests Fail with "Model Not Found"

Ensure your model exists in /app/models/ before generating tests.

HTTP Tests Return 404

Verify your routes are configured correctly in /config/routes.cfm.

Factory Tests Create Invalid Records

Add required attributes in the model().create() calls with valid test data.

See Also

  • - Run tests

  • - Testing documentation

  • - TestBox framework docs

wheels advanced testing

The Wheels CLI provides advanced testing capabilities through integration with TestBox. These commands offer specialized test runners for different testing scenarios.

Available Commands

test:all - Run All Tests

Runs all tests in your application using TestBox CLI.

Options

Option
Type
Default
Description

Examples

test:unit - Run Unit Tests

Runs only unit tests located in the tests/specs/unit directory.

Options

Option
Type
Default
Description

Examples

test:integration - Run Integration Tests

Runs only integration tests located in the tests/specs/integration directory.

Options

Same as test:unit but defaults to tests/specs/integration directory.

Examples

test:watch - Watch Mode

Watches for file changes and automatically reruns tests.

Options

Option
Type
Default
Description

Examples

test:coverage - Code Coverage

Runs tests with code coverage analysis (requires FusionReactor).

Options

Option
Type
Default
Description

Examples

Test Organization

Directory Structure

The standard test directory structure for Wheels applications:

Test Types

The --type parameter determines which test suite to run:

  • app (default): Runs tests in /wheels/app/tests route, uses tests/specs directory

  • core: Runs tests in /wheels/core/tests route, for framework tests

Sample Tests

When you run test:unit or test:integration for the first time and the directories don't exist, sample test files are created automatically in the correct locations:

  • Unit tests: tests/specs/unit/SampleUnitTest.cfc

  • Integration tests: tests/specs/integration/SampleIntegrationTest.cfc

Output Formats

All test commands support multiple output formats via the --format parameter:

  • txt (default): Human-readable text output

  • json: JSON format for parsing and automation

  • junit: JUnit XML format for CI/CD integration

  • html: HTML format for browser viewing

Best Practices

  1. Organize Tests by Type

    • Keep unit tests in tests/specs/unit/

    • Keep integration tests in tests/specs/integration/

    • Use subdirectories for better organization

  2. Use Labels for Test Organization

  3. Set Coverage Thresholds

    • Aim for at least 80% code coverage

    • Use --threshold to enforce minimum coverage

  4. Watch Mode for TDD

    • Use test:watch during development

    • Keep tests running in a separate terminal

  5. CI/CD Integration

    • Use --format=junit for CI systems

    • Generate coverage reports with --coverageReporter=xml

Coverage Requirements

Code coverage requires FusionReactor 8.0+ to be installed and configured:

  1. Install FusionReactor

  2. Enable Code Coverage in FusionReactor settings

  3. Restart your ColdFusion/Lucee server

  4. Run wheels test:coverage

Troubleshooting

TestBox CLI Not Found

If you get an error about TestBox CLI not being installed:

No Tests Found

Make sure your test files:

  • Are in the correct directory (tests/specs/ or subdirectories)

  • Have the .cfc extension

  • Extend wheels.Testbox

Coverage Not Working

If coverage shows as disabled:

  • Verify FusionReactor is installed

  • Check that Code Coverage is enabled in FusionReactor settings

  • Ensure you've restarted the server after enabling coverage

Test Routes Not Working

The test commands use these routes:

  • App tests: http://localhost:port/wheels/app/tests

  • Core tests: http://localhost:port/wheels/core/tests

Ensure these routes are accessible and properly configured.

Related Commands

  • wheels test run - Modern test runner (not a TestBox wrapper)

  • box testbox run - Direct TestBox CLI usage

  • wheels g test - Generate test files

wheels test:all

--type

string

app

Type of tests to run: (app, core)

--format

string

txt

Output format (txt, json, junit, html)

--coverage

boolean

false

Generate coverage report

--coverageReporter

string

-

Coverage reporter format (html, json, xml)

--coverageOutputDir

string

-

Directory for coverage output

--verbose

boolean

true

Display extra details including passing and skipped tests

--directory

string

tests/specs

The directory to use to discover test bundles and specs to test. Mutually exclusive with bundles. Example: directory=tests.specs

--recurse

boolean

true

Recurse the directory mapping or not

--bundles

string

-

The path or list of paths of the spec bundle CFCs to run and test ONLY

--labels

string

-

The list of labels that a suite or spec must have in order to execute

--excludes

string

-

The list of labels that a suite or spec must not have in order to execute

--filter

string

-

Test filter pattern

# Run all app tests
wheels test:all

# Run with JSON format
wheels test:all --format=json

# Run with coverage
wheels test:all --coverage --coverageReporter=html

# Filter tests by name
wheels test:all --filter=UserTest --verbose

# Run core tests
wheels test:all --type=core

# Run specific bundles
wheels test:all --bundles=tests.specs.unit.models,tests.specs.unit.controllers
wheels test:unit

--type

string

app

Type of tests to run: (app, core)

--format

string

txt

Output format (txt, json, junit, html)

--verbose

boolean

true

Display extra details including passing and skipped tests

--bundles

string

-

The path or list of paths of the spec bundle CFCs to run and test ONLY

--labels

string

-

The list of labels that a suite or spec must have in order to execute

--excludes

string

-

The list of labels that a suite or spec must not have in order to execute

--filter

string

-

Test filter pattern

--directory

string

tests/specs/unit

The directory to use to discover test bundles and specs to test. Mutually exclusive with bundles. Example: directory=tests.specs

# Run unit tests
wheels test:unit

# Run with JSON format
wheels test:unit --format=json

# Filter specific tests
wheels test:unit --filter=UserModelTest

# Run core unit tests
wheels test:unit --type=core
wheels test:integration
# Run integration tests
wheels test:integration

# Run specific workflow tests
wheels test:integration --filter=UserWorkflowTest

# With verbose output and JUnit format
wheels test:integration --verbose --format=junit

# Run integration tests
wheels test:integration --type=app
wheels test:watch

--type

string

app

Type of tests to run: (app, core)

--directory

string

tests/specs

The directory to use to discover test bundles and specs to test. Mutually exclusive with bundles. Example: directory=tests.specs

--format

string

txt

Output format (txt, json, junit, html)

--verbose

boolean

true

Display extra details including passing and skipped tests

--delay

integer

1000

Delay in milliseconds before rerunning tests

--bundles

string

-

The path or list of paths of the spec bundle CFCs to run and test ONLY

--labels

string

-

The list of labels that a suite or spec must have in order to execute

--excludes

string

-

The list of labels that a suite or spec must not have in order to execute

--filter

string

-

Test filter pattern

# Watch all tests
wheels test:watch

# Watch unit tests only
wheels test:watch --directory=tests/specs/unit

# Watch with custom delay and JSON format
wheels test:watch --delay=500 --format=json
wheels test:coverage

--type

string

app

Type of tests to run: (app, core)

--directory

string

tests/specs

The directory to use to discover test bundles and specs to test. Mutually exclusive with bundles. Example: directory=tests.specs

--format

string

txt

Output format (txt, json, junit, html)

--outputDir

string

tests/results/coverage

Directory to output the coverage report

--threshold

integer

-

Coverage percentage threshold (0-100)

--pathsToCapture

string

/app

Paths to capture for coverage

--whitelist

string

*.cfc

Whitelist paths for coverage

--blacklist

string

*Test.cfc,*Spec.cfc

Blacklist paths from coverage

--bundles

string

-

The path or list of paths of the spec bundle CFCs to run and test ONLY

--labels

string

-

The list of labels that a suite or spec must have in order to execute

--excludes

string

-

The list of labels that a suite or spec must not have in order to execute

--filter

string

-

Test filter pattern

--verbose

boolean

true

Display extra details including passing and skipped tests

# Basic coverage
wheels test:coverage

# With threshold and specific directory
wheels test:coverage --threshold=80 --directory=tests/specs/unit

# Coverage for specific paths
wheels test:coverage --pathsToCapture=/models,/controllers

# With JUnit output
wheels test:coverage --format=json --outputDir=coverage-reports
tests/
├── specs/             # Main test directory (default for type=app)
│   ├── unit/          # Unit tests
│   │   ├── models/    # Model unit tests
│   │   ├── controllers/ # Controller unit tests
│   │   └── helpers/   # Helper unit tests
│   ├── integration/   # Integration tests
│   │   ├── workflows/ # User workflow tests
│   │   └── api/       # API integration tests
│   └── functions/     # Function-specific tests
└── results/           # Test results and reports
    └── coverage/      # Coverage reports
it("should process payments", function() {
    // test code
}).labels("critical", "payments");
box install testbox-cli
box reload
wheels generate test [type] [target] [options]
wheels g test [type] [target] [options]

type

Type of test: model, controller, view, unit, integration, api

Required

target

Name of the component/object to test

Required

--name

Name of the view (required for view tests)

""

--crud

Generate CRUD test methods (create, read, update, delete)

false

--mock

Generate mock/stub examples (for unit tests)

false

--factory

Generate factory examples using model().create() pattern

false

--force

Overwrite existing files without prompting

false

--open

Open the created file in default editor

false

model

/tests/specs/models/

Model validations, associations, callbacks, custom methods

Direct model instantiation

controller

/tests/specs/controllers/

Controller actions via HTTP requests

cfhttp() requests

view

/tests/specs/views/

View rendering via HTTP requests

cfhttp() requests

unit

/tests/specs/unit/

Service/library components with custom logic

Direct component instantiation

integration

/tests/specs/integration/

End-to-end workflow tests

cfhttp() requests

api

/tests/specs/integration/api/

API endpoints with JSON request/response

cfhttp() with JSON

# Positional (recommended)
wheels generate test model User

# OR all named
wheels g test type=model target=User
component extends="wheels.Testbox" {

    function run() {

        describe("User Model", function() {

            beforeEach(function() {
                variables.user = model("User").new();
            });

            it("should validate required fields", function() {
                expect(user.valid()).toBe(false);
                // Add specific field validations here
            });

            it("should have expected associations", function() {
                // Test your model associations here
                // Example: expect(isObject(user)).toBe(true);
            });

            it("should test custom model methods", function() {
                // Test custom model methods here
            });
        });
    }
}
# Positional + flag (recommended)
wheels generate test model Product --crud

# OR all named
wheels g test type=model target=Product crud=true
it("should create a new product", function() {
    product.name = "Test Product";
    expect(product.save()).toBe(true);
    var newProduct = product;
    expect(newProduct.id).toBeGT(0);
});
wheels generate test model Order --crud --factory
beforeEach(function() {
    // Factory pattern: create reusable test data with sensible defaults
    variables.order = model("Order").new({
        // Add default test attributes here
    });
});

it("should create a new order", function() {
    var newOrder = model("Order").create({
        // Add test attributes
    });
    expect(newOrder.id).toBeGT(0);
});
wheels generate test controller Users
component extends="wheels.Testbox" {

    function beforeAll() {
        variables.baseUrl = "http://localhost:8080";
    }

    function run() {

        describe("Users Controller", function() {

            it("should respond to index request", function() {
                cfhttp(url = "#variables.baseUrl#/users", method = "GET", result = "response");
                expect(response.status_code).toBe(200);
                // Add more specific assertions for your controller actions
            });
        });
    }
}
wheels generate test controller Products --crud
it("should list all products (index action)", function() {
    cfhttp(url = "#variables.baseUrl#/products", method = "GET", result = "response");
    expect(response.status_code).toBe(200);
    expect(response.filecontent).toInclude("Products");
});

it("should create a new product (create action)", function() {
    cfhttp(url = "#variables.baseUrl#/products", method = "POST", result = "response") {
        cfhttpparam(type = "formfield", name = "product[name]", value = "Test Product");
        // Add more form fields as needed
    }
    expect(response.status_code).toBe(302); // Redirect on success
});
wheels generate test view users edit
component extends="wheels.Testbox" {

    function beforeAll() {
        variables.baseUrl = "http://localhost:8080";
    }

    function run() {

        describe("Users edit View", function() {

            it("should render edit view without errors", function() {
                // Test view rendering via HTTP request
                cfhttp(url = "#variables.baseUrl#/users/edit", method = "GET", result = "response");
                expect(response.status_code).toBe(200);
                expect(response.filecontent).toInclude("Users");
            });

            it("should display required HTML elements", function() {
                cfhttp(url = "#variables.baseUrl#/users/edit", method = "GET", result = "response");
                // Add specific HTML element assertions
                // expect(response.filecontent).toInclude("<form");
                // expect(response.filecontent).toInclude("<input");
            });
        });
    }
}
wheels generate test unit OrderProcessor
component extends="wheels.Testbox" {

    function run() {

        describe("OrderProcessor Unit Tests", function() {

            it("should test orderprocessor functionality", function() {
                // Create your service/component to test
                // var service = new app.lib.OrderProcessorService();
                // Test your service methods here
                // expect(service.someMethod()).toBe(expectedValue);
            });

            it("should handle edge cases", function() {
                // Test edge cases like empty strings, null values, etc.
                // expect(someFunction("")).toBe(expectedValue);
            });

            it("should handle errors gracefully", function() {
                // Test error handling
                // expect(function() {
                //     someFunction(invalidInput);
                // }).toThrow();
            });
        });
    }
}
wheels generate test unit PaymentService --mock
it("should work with mocked dependencies", function() {
    // Example of using MockBox for mocking
    // var mockDependency = createMock("app.lib.DependencyService");
    // mockDependency.$("someMethod").$results("mocked value");
    // Test with mocked dependency
});
wheels generate test integration CheckoutFlow --crud
component extends="wheels.Testbox" {

    function beforeAll() {
        variables.baseUrl = "http://localhost:8080";
    }

    function run() {

        describe("CheckoutFlow Integration Test", function() {

            it("should complete the full checkoutflow workflow", function() {
                // Test complete user journey using HTTP requests

                // 1. Visit listing page
                cfhttp(url = "#variables.baseUrl#/checkoutflows", method = "GET", result = "listResponse");
                expect(listResponse.status_code).toBe(200);

                // 2. Create new record
                cfhttp(url = "#variables.baseUrl#/checkoutflows", method = "POST", result = "createResponse") {
                    cfhttpparam(type = "formfield", name = "checkoutflow[name]", value = "Integration Test");
                }
                expect(createResponse.status_code).toBe(302); // Redirect on success

                // 3. Verify listing shows new record
                cfhttp(url = "#variables.baseUrl#/checkoutflows", method = "GET", result = "verifyResponse");
                expect(verifyResponse.filecontent).toInclude("Integration Test");

                // 4. Add more workflow steps (update, delete, etc.)
            });

            it("should complete operations within acceptable time", function() {
                var startTime = getTickCount();
                cfhttp(url = "#variables.baseUrl#/checkoutflows", method = "GET", result = "response");
                var endTime = getTickCount();
                var executionTime = endTime - startTime;
                expect(executionTime).toBeLT(5000, "Request should complete in under 5 seconds");
            });
        });
    }
}
wheels generate test api Users --crud
component extends="wheels.Testbox" {

    function beforeAll() {
        variables.apiUrl = "http://localhost:8080/api";
    }

    function run() {

        describe("Users API", function() {

            it("should return paginated users via GET", function() {
                cfhttp(url = "#variables.apiUrl#/users", method = "GET", result = "response") {
                    cfhttpparam(type = "header", name = "Accept", value = "application/json");
                    // Add authentication header if needed
                    // cfhttpparam(type = "header", name = "Authorization", value = "Bearer TOKEN");
                }
                expect(response.status_code).toBe(200);
                var jsonData = deserializeJSON(response.filecontent);
                expect(jsonData).toHaveKey("data");
                expect(isArray(jsonData.data)).toBe(true);
            });

            it("should create a new user via POST", function() {
                var postData = {
                    name = "API Test User"
                };
                cfhttp(url = "#variables.apiUrl#/users", method = "POST", result = "response") {
                    cfhttpparam(type = "header", name = "Content-Type", value = "application/json");
                    cfhttpparam(type = "body", value = serializeJSON(postData));
                }
                expect(response.status_code).toBe(201);
                var jsonData = deserializeJSON(response.filecontent);
                expect(jsonData.data).toHaveKey("id");
            });

            it("should return 401 for unauthorized requests", function() {
                // Test without authentication header
                cfhttp(url = "#variables.apiUrl#/users", method = "GET", result = "response");
                // expect(response.status_code).toBe(401);
                // Add your authentication tests here
            });
        });
    }
}
wheels generate test model User --force
wheels generate test controller Products --crud --open
it("should validate required fields", function() {
    expect(user.valid()).toBe(false);
    expect(user.errors).toHaveKey("email");
});
it("should create a new user", function() {
    var user = model("User").create({
        email = "[email protected]",
        firstName = "Test"
    });
    expect(user.id).toBeGT(0);
});
it("should return 200 status", function() {
    cfhttp(url = "#baseUrl#/users", method = "GET", result = "response");
    expect(response.status_code).toBe(200);
});
it("should return valid JSON", function() {
    cfhttp(url = "#apiUrl#/users", method = "GET", result = "response") {
        cfhttpparam(type = "header", name = "Accept", value = "application/json");
    }
    var data = deserializeJSON(response.filecontent);
    expect(data).toHaveKey("data");
});
# Run all tests
wheels test run

# Run specific test bundle
wheels test run --testBundles=ProductSpec

# Run with coverage
wheels test run --coverage
wheels test run
Testing Guide
TestBox Documentation

wheels generate api-resource

Generate a complete RESTful API controller with advanced features like pagination, filtering, sorting, and authentication.

Synopsis

wheels generate api-resource [name] [options]
wheels g api-resource [name] [options]

Description

The wheels generate api-resource command creates a production-ready RESTful API controller optimized for JSON APIs. It generates API-specific controllers with no view rendering logic, including optional features like pagination, filtering, sorting, authentication, and API versioning.

The generated controllers use provides("json") and renderWith() to return JSON responses with proper HTTP status codes for REST operations.

Arguments

Argument
Description
Default

name

Resource name (singular or plural)

Required

Options

Option
Description
Default

--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

--skipModel

Skip model generation

false

--skipMigration

Skip migration generation

false

--skipTests

Skip test generation

false

--namespace

API namespace

api

--docs

Generate API documentation template

false

--force

Overwrite existing files

false

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Named parameters: name=value (e.g., name=products, version="v2")

  • Flag parameters: --flag equals flag=true (e.g., --auth equals auth=true)

  • Flag with value: --flag=value equals flag=value (e.g., --version=v2)

Parameter Mixing Rules:

✅ ALLOWED:

  • All positional: wheels generate api-resource products

  • All named: name=products version="v2" auth=true

  • Positional + flags: wheels generate api-resource products --auth --skipModel

❌ NOT ALLOWED:

  • Positional + named: wheels generate api-resource products version="v2" (causes error)

Recommendation: Use positional for name + flags for options: wheels generate api-resource products --auth --version=v2


Examples

Basic API Controller

Generate a simple API controller:

# Positional name only
wheels generate api-resource products

Creates:

  • /models/Product.cfc - Model file

  • /controllers/api/v1/Products.cfc - Versioned API controller with pagination, filtering, sorting

Without Advanced Features

Generate a minimal API controller without optional features:

# Using named parameters (all named)
wheels g api-resource name=products pagination=false filtering=false sorting=false

# OR using flags (positional + flags) - RECOMMENDED
wheels g api-resource products --pagination=false --filtering=false --sorting=false

Creates a simple controller with only basic CRUD operations.

With Authentication

Generate API controller with authentication:

# Using flag
wheels generate api-resource products --auth

Includes Bearer token authentication that requires Authorization header for create, update, delete actions.

Custom Version and Namespace

Generate API controller with custom versioning:

# Using flags with values
wheels generate api-resource products --version=v2 --namespace=public

Creates /controllers/public/v2/Products.cfc

Skip Model Generation

Generate only the controller (model already exists):

# Using flag
wheels generate api-resource products --skipModel

Complete Setup with Documentation

Generate everything with API documentation:

# Multiple flags
wheels generate api-resource products --auth --docs

Creates:

  • /models/Product.cfc

  • /controllers/api/v1/Products.cfc with authentication

  • /app/docs/api/products.md - API documentation

Generated Controller Features

With All Features Enabled

wheels generate api-resource products --auth --pagination --filtering --sorting

Generates:

component extends="wheels.Controller" {

    function config() {
        provides("json");
        filters(through="setJsonResponse");
        filters(through="authenticate", except="index,show");
    }

    /**
     * GET /products
     * Supports: ?page=1&perPage=25&sort=name,-price&filter[name]=widget
     */
    function index() {
        local.page = params.page ?: 1;
        local.perPage = params.perPage ?: 25;
        local.options = {};
        local.options.page = local.page;
        local.options.perPage = local.perPage;

        if (structKeyExists(params, "sort")) {
            local.options.order = parseSort(params.sort);
        }

        if (structKeyExists(params, "filter")) {
            local.options.where = parseFilter(params.filter);
        }

        local.products = model("Product").findAll(argumentCollection=local.options);

        local.response = {
            data = local.products,
            meta = {
                pagination = {
                    page = local.products.currentPage ?: local.page,
                    perPage = local.perPage,
                    total = local.products.totalRecords ?: 0,
                    pages = local.products.totalPages ?: 1
                }
            }
        };

        renderWith(data=local.response);
    }

    function show() { /* ... */ }
    function create() { /* ... */ }
    function update() { /* ... */ }
    function delete() { /* ... */ }

    // Helper methods for pagination, filtering, sorting, auth
    private function authenticate() { /* ... */ }
    private function parseSort(required string sort) { /* ... */ }
    private function parseFilter(required struct filter) { /* ... */ }
}

Adding Routes

After generating your API resource, add routes to /config/routes.cfm:

Default Namespaced Routes

// Add inside mapper() block
namespace(name="api", function() {
    namespace(name="v1", function() {
        resources(name="products", except="new,edit");
    });
});

Creates routes:

  • GET /api/v1/products

  • GET /api/v1/products/:key

  • POST /api/v1/products

  • PUT /api/v1/products/:key

  • DELETE /api/v1/products/:key

Custom Version

namespace(name="api", function() {
    namespace(name="v2", function() {
        resources(name="products", except="new,edit");
    });
});

No Namespace

If you used --namespace="":

resources(name="products", except="new,edit");

Feature Details

Pagination

When --pagination is enabled (default):

Request:

curl "http://localhost:8080/api/v1/products?page=2&perPage=10"

Response:

{
  "data": [ /* products */ ],
  "meta": {
    "pagination": {
      "page": 2,
      "perPage": 10,
      "total": 100,
      "pages": 10
    }
  }
}

Filtering

When --filtering is enabled (default):

Request:

curl "http://localhost:8080/api/v1/products?filter[name]=widget&filter[minPrice]=10"

The generated controller includes a parseFilter() method with TODO comments for you to implement your filtering logic.

Sorting

When --sorting is enabled (default):

Request:

# Sort by name ascending, then price descending
curl "http://localhost:8080/api/v1/products?sort=name,-price"

The - prefix indicates descending order.

Authentication

When --auth is enabled:

Request:

curl -X POST http://localhost:8080/api/v1/products \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{"product":{"name":"Widget"}}'

The generated controller includes authentication methods that you need to implement with your actual token validation logic.

HTTP Status Codes

The generated controller uses proper REST HTTP status codes:

Action
Success Status
Error Status

index

200 OK

-

show

200 OK

404 Not Found

create

201 Created

422 Unprocessable Entity

update

200 OK

404 Not Found, 422 Unprocessable Entity

delete

204 No Content

404 Not Found

auth failure

-

401 Unauthorized

Testing Your API

Basic Requests

# List products with pagination
curl "http://localhost:8080/api/v1/products?page=1&perPage=25"

# Get specific product
curl http://localhost:8080/api/v1/products/1

# Create product
curl -X POST http://localhost:8080/api/v1/products \
  -H "Content-Type: application/json" \
  -d '{"product":{"name":"Widget","price":29.99}}'

# Update product
curl -X PUT http://localhost:8080/api/v1/products/1 \
  -H "Content-Type: application/json" \
  -d '{"product":{"price":39.99}}'

# Delete product
curl -X DELETE http://localhost:8080/api/v1/products/1

With Filtering and Sorting

# Filter and sort
curl "http://localhost:8080/api/v1/products?filter[name]=widget&sort=-createdAt"

# Pagination with filters
curl "http://localhost:8080/api/v1/products?page=1&perPage=10&filter[minPrice]=20&sort=name"

With Authentication

# With Bearer token
curl -X POST http://localhost:8080/api/v1/products \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"product":{"name":"Widget"}}'

Example Responses

Success with Pagination (200 OK):

{
  "data": [
    {
      "id": 1,
      "name": "Widget",
      "price": 29.99,
      "createdAt": "2023-01-01T12:00:00Z",
      "updatedAt": "2023-01-01T12:00:00Z"
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "perPage": 25,
      "total": 1,
      "pages": 1
    }
  }
}

Validation Error (422):

{
  "error": "Validation failed",
  "errors": [
    {
      "property": "name",
      "message": "This field is required"
    }
  ]
}

Not Found (404):

{
  "error": "Record not found"
}

Unauthorized (401):

{
  "error": "Unauthorized"
}

Customization

Implementing Filter Logic

Edit the generated parseFilter() method:

private function parseFilter(required struct filter) {
    local.where = [];
    local.params = {};

    if (structKeyExists(arguments.filter, "name")) {
        arrayAppend(local.where, "name LIKE :name");
        local.params.name = "%#arguments.filter.name#%";
    }

    if (structKeyExists(arguments.filter, "minPrice")) {
        arrayAppend(local.where, "price >= :minPrice");
        local.params.minPrice = arguments.filter.minPrice;
    }

    if (structKeyExists(arguments.filter, "category")) {
        arrayAppend(local.where, "category = :category");
        local.params.category = arguments.filter.category;
    }

    return arrayLen(local.where) ? arrayToList(local.where, " AND ") : "";
}

Implementing Authentication

Edit the generated isValidToken() method:

private function isValidToken(required string token) {
    // Example: Check against database
    local.apiKey = model("ApiKey").findOne(where="token = :token", token=arguments.token);

    if (isObject(local.apiKey) && local.apiKey.active) {
        // Store user context in session/request
        request.user = local.apiKey.user();
        return true;
    }

    return false;
}

Adding More Sortable Fields

Edit the parseSort() method:

private function parseSort(required string sort) {
    local.allowedFields = ["id", "name", "price", "category", "createdAt", "updatedAt"];
    // ... rest of method
}

Best Practices

  1. Use Versioning: Always version your APIs (--version=v1)

  2. Enable Pagination: Prevent performance issues with large datasets

  3. Add Authentication: Secure your API endpoints with --auth

  4. Document Your API: Use --docs flag and keep documentation updated

  5. Implement Filtering: Customize parseFilter() for your model fields

  6. Whitelist Sort Fields: Only allow sorting on indexed fields

  7. Use Proper Status Codes: 201 for creation, 204 for deletion

  8. Return Error Details: Always include error messages for 4xx/5xx

  9. Rate Limiting: Consider adding rate limiting for public APIs

  10. CORS Headers: Add CORS support for browser-based clients

Comparison with Other Commands

Feature

api-resource

controller --api

scaffold

Generates model

Optional

No

Yes

Generates views

No

No

Yes

Actions

REST only

REST only

Full CRUD

Format

Configurable

JSON only

HTML + JSON

Versioning

Yes

No

No

Pagination

Optional

No

No

Filtering

Optional

No

No

Sorting

Optional

No

No

Authentication

Optional

No

No

Best for

Production APIs

Simple APIs

Full-stack apps

See Also

  • wheels generate controller - Generate standard controllers

  • wheels generate model - Generate models

  • wheels generate scaffold - Generate full CRUD resources

  • Wheels REST Documentation - REST API best practices

wheels generate model

Generate a model with properties, validations, and associations.

Synopsis

Parameter Syntax

CommandBox supports multiple parameter formats:

  • Named parameters: name=value (e.g., name=User, properties=name:string,email:string)

  • Flag parameters: --flag equals flag=true (e.g., --migration equals migration=true)

  • Flag with value: --flag=value equals flag=value (e.g., --primaryKey=uuid)

Note: Flag syntax (--flag) avoids positional/named parameter conflicts and is recommended for boolean options.

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
Valid Values
Default

Parameter Validation

Required Parameters

  • name: Cannot be empty, must be valid CFML component name (alphanumeric, starts with letter)

Property Types Validation

Valid property types for the properties parameter:

Type
Database Type
Validation

Model Name Validation

  • Must be singular (User, not Users)

  • Must be PascalCase (User, BlogPost)

  • Cannot contain spaces or special characters

  • Must be valid CFML component name

Relationship Validation

  • Relationship model names must follow model naming conventions

  • Models referenced in relationships should exist or be created

  • Comma-separated values cannot contain spaces around commas

Examples

Basic model

Creates:

  • /models/User.cfc

  • Migration file (if enabled)

Model with properties

Model with associations

Model without migration

Complex model

Validation Examples

✅ Valid Examples

❌ Invalid Examples and Errors

Invalid Model Names

Invalid Property Types

Invalid Relationships

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:

Common Validation Errors

Model Name Errors

  • Empty name: name="" → Provide a valid model name

  • Plural names: name=Users → Use singular form: name=User

  • Invalid characters: name="Blog Post" → Use PascalCase: name=BlogPost

  • Lowercase: name=user → Use PascalCase: name=User

Property Format Errors

  • Missing colon: properties="name,email:string" → properties="name:string,email:string"

  • Invalid types: properties="name:varchar" → properties="name:string"

  • Extra spaces: properties="name: string" → properties="name:string"

  • Missing type: properties="name:" → properties="name:string"

Boolean Parameter Errors

  • Invalid boolean: --migration=yes → --migration=true or --migration=false

  • Mixed syntax: migration=true --force → --migration=true --force

Relationship Format Errors

  • Lowercase models: belongsTo="user" → belongsTo="User"

  • Extra spaces: belongsTo="User, Category" → belongsTo="User,Category"

  • Invalid separators: belongsTo="User;Category" → belongsTo="User,Category"

Best Practices

  1. Naming: Use singular names (User, not Users)

  2. Properties: Define all database columns with correct types

  3. Validations: Add comprehensive validations in model code

  4. Associations: Define all relationships using PascalCase

  5. Callbacks: Use for automatic behaviors

  6. Indexes: Add to migration for performance

  7. Validation: Always validate parameters before running command

Common Patterns

Soft Deletes

Calculated Properties

Scopes

Default Values

Testing

Generate model tests:

Troubleshooting

Command Fails with "Invalid model name"

  1. Check that name is not empty: name=User

  2. Ensure PascalCase format: name=User (not name=user)

  3. Use singular form: name=User (not name=Users)

  4. Remove special characters: name=BlogPost (not name="Blog Post")

Properties Not Generated in Migration

  1. Check property format: properties="name:string,email:string"

  2. Ensure valid property types (see Property Types table above)

  3. Remove extra spaces: name:string (not name: string)

  4. Use comma separators: name:string,email:string

Relationships Not Working

  1. Use PascalCase model names: belongsTo="User" (not belongsTo="user")

  2. Remove spaces after commas: belongsTo="User,Category"

  3. Ensure referenced models exist or will be created

Migration Not Generated

  1. Check --migration=false wasn't set

  2. Ensure you have write permissions in the directory

  3. Verify migration directory exists: /app/migrator/migrations/

Boolean Parameters Not Working

  1. Use --flag for flag=true: --force equals force=true

  2. Use --flag=false for false values: --migration=false

  3. Don't mix syntaxes: Use all flags or all named parameters

See Also

  • - Create migrations

  • - Add properties to existing models

  • - Generate controllers

  • - Generate complete CRUD

wheels generate model name=<modelName> [options]

#Can also be used as:
wheels g model name=<modelName> [options]

name

Model name (singular)

Required

properties

Model properties (format: name:type,name2:type2)

Property format: name:type[,name2:type2] where type is valid column type

""

belongsTo

Parent model relationships (comma-separated)

Valid model names (PascalCase), comma-separated

""

hasMany

Child model relationships (comma-separated)

Valid model names (PascalCase), comma-separated

""

hasOne

One-to-one relationships (comma-separated)

Valid model names (PascalCase), comma-separated

""

primaryKey

Primary key column name(s)

Valid column name (alphanumeric, underscore)

id

tableName

Custom database table name

Valid table name (alphanumeric, underscore)

""

description

Model description

Any descriptive text

""

migration

Generate database migration

true, false

true

force

Overwrite existing files

true, false

false

string

VARCHAR(255)

Default string type

text

TEXT

For longer text content

integer

INTEGER

Whole numbers

biginteger

BIGINT

Large whole numbers

float

FLOAT

Decimal numbers

decimal

DECIMAL(10,2)

Precise decimal numbers

boolean

BOOLEAN

true/false values

date

DATE

Date values

datetime

DATETIME

Date and time values

timestamp

TIMESTAMP

Timestamp values

binary

BLOB

Binary data

uuid

VARCHAR(35)

UUID strings

wheels generate model name=User
wheels generate model name=User --properties="firstName:string,lastName:string,email:string,age:integer"
wheels generate model name=Post --belongsTo="User" --hasMany="Comments"
wheels generate model name=Setting --migration=false
wheels generate model name=Product --properties="name:string,price:decimal,stock:integer,active:boolean" --belongsTo="Category,Brand" --hasMany="Reviews,OrderItems"
# Valid model name and properties
wheels generate model name=User --properties="firstName:string,lastName:string,email:string,age:integer"

# Valid relationships
wheels generate model name=Post --belongsTo="User,Category" --hasMany="Comments,Tags"

# Valid property types
wheels generate model name=Product --properties="name:string,description:text,price:decimal,inStock:boolean,createdAt:datetime"
# Error: Model name cannot be empty
wheels generate model name=""
# Result: Invalid model name error

# Error: Model name should be singular
wheels generate model name=Users
# Result: Warning about plural name

# Error: Invalid characters
wheels generate model name="Blog Post"
# Result: Invalid model name error
# Error: Invalid property type
wheels generate model name=User --properties="name:varchar,age:int"
# Result: Use 'string' instead of 'varchar', 'integer' instead of 'int'

# Error: Missing property type
wheels generate model name=User --properties="name,email:string"
# Result: Property format must be 'name:type'
# Error: Invalid model name format
wheels generate model name=Post --belongsTo="user,blog_category"
# Result: Use PascalCase: 'User,BlogCategory'

# Error: Spaces in comma-separated values
wheels generate model name=Post --belongsTo="User, Category"
# Result: Remove spaces: 'User,Category'

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", columnNames="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 name=User --properties="email:string,name:string"
wheels generate test model name=User
wheels dbmigrate create table
wheels generate property
wheels generate controller
wheels scaffold

wheels generate route

Generate route definitions for your Wheels application's /config/routes.cfm file.

Synopsis

wheels generate route [objectname]
#can also be used as:
wheels g route [objectname]

# HTTP method routes
wheels generate route --get="pattern,controller##action"
wheels generate route --post="pattern,controller##action"
wheels generate route --put="pattern,controller##action"
wheels generate route --patch="pattern,controller##action"
wheels generate route --delete="pattern,controller##action"

# Root route
wheels generate route --root="controller##action"

# Resources route (explicit)
wheels generate route --resources=true [objectname]

Description

The wheels generate route command helps you create route definitions in your Wheels application's /config/routes.cfm file. It supports individual HTTP method routes, RESTful resource routes, and root routes.

IMPORTANT: All HTTP method parameters must use the equals syntax: --get="pattern,handler" not --get pattern,handler

Parameter Syntax

CommandBox supports multiple parameter formats:

  • Named parameters: name=value (e.g., objectname=products, get="pattern,handler")

  • Flag parameters: --flag equals flag=true (e.g., --resources equals resources=true)

  • Flag with value: --flag=value equals flag=value (e.g., --get="pattern,handler")

Note: Flag syntax (--flag) avoids positional/named parameter conflicts and is recommended for boolean options.

Arguments

Argument
Description
Default

objectname

The name of the resource for resources route

Optional (required for resources routes)

Options

Option
Description
Example
Default

--get

Create a GET route with pattern,handler format

--get="products/sale,products##sale"

--post

Create a POST route with pattern,handler format

--post="contact,contact##send"

--put

Create a PUT route with pattern,handler format

--put="users/[key],users##update"

--patch

Create a PATCH route with pattern,handler format

--patch="profiles,profiles##update"

--delete

Create a DELETE route with pattern,handler format

--delete="sessions,sessions##destroy"

--resources

Create a resources route (use with objectname)

--resources=true

false

--root

Create a root route with handler

--root="pages##home"

Examples

Resources Route (default)

wheels generate route products

Generates in /config/routes.cfm:

.resources("products")

This creates all standard 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)

GET Route

wheels generate route --get="products/sale,products##sale"

Generates:

.get(pattern="products/sale", to="products##sale")

POST Route

wheels generate route --post="api/users,api.users##create"

Generates:

.post(pattern="api/users", to="api.users##create")

PUT Route

wheels generate route --put="users/[key]/activate,users##activate"

Generates:

.put(pattern="users/[key]/activate", to="users##activate")

DELETE Route

wheels generate route --delete="sessions,sessions##destroy"

Generates:

.delete(pattern="sessions", to="sessions##destroy")

Root Route

wheels generate route --root="pages##home"

Generates:

.root(to="pages##home", method="get")

Explicit Resources Route

wheels generate route --resources=true users

Generates:

.resources("users")

Route Patterns

Dynamic Segments

Routes support dynamic segments using [key] notation:

wheels generate route --get="users/[key]/profile,users##profile"

Generates:

.get(pattern="users/[key]/profile", to="users##profile")

The [key] parameter will be available as params.key in your controller.

Custom Parameters

You can use any parameter name:

wheels generate route --get="posts/[year]/[month],posts##archive"

Generates:

.get(pattern="posts/[year]/[month]", to="posts##archive")

Parameters will be available as params.year and params.month in your controller.

Pattern Only Routes

If you omit the handler, the route will use standard controller/action mapping:

wheels generate route --get="products/search"

Generates:

.get(pattern="products/search")

This will map to the search action in the products controller.

Route File Integration

Generated Routes Location

All routes are added to /config/routes.cfm at the CLI marker position:

<cfscript>
mapper()
    .resources("products")        // Existing routes
    .get(pattern="about", to="pages#about")    // Existing routes

    // CLI-Appends-Here            // CLI adds new routes here

    .wildcard()                   // Wildcard should stay last
    .root(to="home#index")        // Root route
.end();
</cfscript>

Route Order

The command automatically places new routes at the correct position before the wildcard route. Routes are processed in order, so specific routes must come before general ones.

Using Generated Routes

Route Helpers

Resource routes automatically create URL helpers:

<!--- For resources("products") --->
#linkTo(route="products", text="All Products")#       <!-- /products -->
#linkTo(route="product", key=123, text="View")#       <!-- /products/123 -->
#linkTo(route="newProduct", text="Add Product")#     <!-- /products/new -->
#linkTo(route="editProduct", key=123, text="Edit")#  <!-- /products/123/edit -->

#urlFor(route="products")#           <!-- /products -->
#urlFor(route="product", key=123)#   <!-- /products/123 -->

Custom Route Helpers

For custom routes, you'll need to manually create route names in your routes.cfm:

<!--- Add name parameter manually in routes.cfm --->
.get(name="productSale", pattern="products/sale", to="products##sale")

<!--- Then use in views --->
#linkTo(route="productSale", text="Special Sale")#

Detailed Parameter Usage

Command Line Parameter Formats

Building on CommandBox's parameter syntax, Wheels route generation supports:

1. Named Parameters (Recommended)

wheels generate route --get="products/sale,products##sale"
wheels generate route --post="contact/send,contact##send"
wheels generate route --resources=true --objectname=users
wheels generate route --root="pages##home"

2. Positional Parameters

wheels generate route products              # objectname (resources route)
wheels g route users                        # Short alias with objectname

3. Mixed Parameters

wheels generate route users --resources=true
wheels generate route --objectname=products --resources=true

Parameter Validation Rules

HTTP Method Parameters

  • Format: --method="pattern,handler" or --method="pattern"

  • Methods: get, post, put, patch, delete

  • Separator: Comma (,) between pattern and handler

  • Quotes: Always use quotes around the value

  • Handler Format: controller##action (double hash required in CFML)

Resources Parameters

  • Format: --resources=true objectname or objectname --resources=true

  • Boolean: Must be explicit true or false

  • Objectname: Required when using resources flag

Root Parameters

  • Format: --root="controller##action"

  • Handler: Required controller and action

  • Quotes: Always use quotes

Parameter Examples by Type

String Parameters with Handlers

# GET route with handler
wheels generate route --get="api/users,api##index"

# POST route with handler
wheels generate route --post="users/login,sessions##create"

# PUT route with handler
wheels generate route --put="profiles/[key],profiles##update"

String Parameters without Handlers

# Pattern-only routes (uses convention)
wheels generate route --get="products/search"
wheels generate route --post="newsletter/signup"

Boolean Parameters

# Resources flag (explicit true/false)
wheels generate route --resources=true products
wheels generate route --resources=false    # Invalid - needs objectname

Mixed Parameter Combinations

# Objectname with resources flag
wheels generate route products --resources=true
wheels generate route --resources=true users

# Multiple routes in sequence
wheels generate route --get="login,sessions##new"
wheels generate route --post="login,sessions##create"
wheels generate route --delete="logout,sessions##destroy"

Common Parameter Mistakes

❌ Missing equals sign:

wheels generate route --get products/sale,products##sale

❌ Missing quotes:

wheels generate route --get=products/sale,products##sale

❌ Single hash instead of double:

wheels generate route --get="products/sale,products#sale"

❌ Missing objectname with resources:

wheels generate route --resources=true        # No objectname

✅ Correct formats:

wheels generate route --get="products/sale,products##sale"
wheels generate route products --resources
wheels generate route --root="pages##home"
wheels generate route users    # Positional objectname

Advanced Parameter Usage

Dynamic URL Segments

# Single parameter
wheels generate route --get="users/[key],users##show"

# Multiple parameters
wheels generate route --get="posts/[year]/[month],posts##archive"

# Optional parameters (configure manually in routes.cfm)
wheels generate route --get="blog/[category]" # Add [category?] manually

API-Style Routes

# RESTful API endpoints
wheels generate route --get="api/v1/users,api.v1.users##index"
wheels generate route --post="api/v1/users,api.v1.users##create"
wheels generate route --put="api/v1/users/[key],api.v1.users##update"
wheels generate route --delete="api/v1/users/[key],api.v1.users##destroy"

Namespace-Style Controllers

# Admin controllers
wheels generate route --get="admin/dashboard,admin.dashboard##index"
wheels generate route --get="admin/users,admin.users##index"

# Module-based controllers
wheels generate route --get="shop/products,shop.products##index"
wheels generate route --post="shop/checkout,shop.checkout##process"

Parameter Processing Details

Command Line Processing

  1. Quoted Parameters: Preserve spaces and special characters

  2. Equals Processing: Splits parameter name from value

  3. Boolean Conversion: Converts "true"/"false" strings to boolean values

  4. Array Processing: CommandBox processes space-separated values as arrays

Internal Parameter Handling

  1. reconstructArgs(): Processes CommandBox parameter format

  2. Validation: Checks required parameters are present

  3. Route Generation: Formats parameters for Wheels router syntax

  4. File Injection: Places routes at correct position in routes.cfm

Integration with Routes.cfm

CLI Marker

The command looks for // CLI-Appends-Here comment to place new routes. If not found, it tries different indentation levels:

  1. // CLI-Appends-Here (3 tabs)

  2. // CLI-Appends-Here (2 tabs)

  3. // CLI-Appends-Here (1 tab)

  4. // CLI-Appends-Here (no tabs)

Manual Route Organization

After using the CLI, you may want to reorganize routes manually:

<cfscript>
mapper()
    // Public pages first
    .get(pattern="about", to="pages##about")
    .get(pattern="contact", to="contact##index")

    // Resources grouped together
    .resources("products")
    .resources("users")

    // Authentication routes
    .get(pattern="login", to="sessions##new")
    .post(pattern="login", to="sessions##create")

    // CLI generated routes will appear here
    // CLI-Appends-Here

    .wildcard()  // Always keep wildcard last
    .root(to="home##index", method="get")
.end();
</cfscript>

Best Practices

  1. Use equals syntax: Always use --get="pattern,handler" format

  2. Resources for CRUD: Use resources route for full CRUD operations

  3. Custom routes for special actions: Use HTTP method routes for non-CRUD actions

  4. Check route order: Specific routes before general ones

  5. Test after generation: Visit URLs to ensure routes work

  6. Reload application: Use ?reload=true after route changes

Troubleshooting

Common Issues and Solutions

1. Parameter Syntax Errors

Issue: "Missing argument" errors when using HTTP method parameters

❌ Incorrect:

wheels generate route --get products/sale,products##sale    # Missing =
wheels generate route --post contact send                   # Missing quotes and =

✅ Correct:

wheels generate route --get="products/sale,products##sale"  # With = and quotes
wheels generate route --post="contact,sessions##create"     # Proper format

Solution: Always use equals syntax with quotes for HTTP method parameters.

2. CFML Syntax Errors

Issue: Template compilation errors with single hash (#) in handlers

❌ Incorrect:

wheels generate route --get="users,users#show"   # Single # causes CFML errors

✅ Correct:

wheels generate route --get="users,users##show"  # Double ## for CFML escaping

Solution: Always use double hash (##) in controller##action handlers.

3. Routes Not Working

Issue: Generated routes don't respond or show 404 errors

Possible Causes:

  • Application not reloaded after route changes

  • Route order conflicts (specific routes after wildcard)

  • Controller or action doesn't exist

Solutions:

# 1. Always reload after route changes
http://localhost:8080/?reload=true

# 2. Check route order in routes.cfm
# Ensure wildcard() comes AFTER specific routes

# 3. Verify controller exists
# For route: --get="products,products##sale"
# Need: /app/controllers/Products.cfc with sale() function

4. Parameter Parsing Issues

Issue: Complex patterns not parsed correctly

❌ Problematic:

# Spaces in patterns without quotes
wheels generate route --get=api/v1/users,api##index

# Special characters not escaped
wheels generate route --get="api-users,api#index"

✅ Solutions:

# Always quote complex patterns
wheels generate route --get="api/v1/users,api##index"

# Use proper CFML escaping
wheels generate route --get="api-users,api##index"

5. Resources Route Issues

Issue: Resources flag not working or missing objectname

❌ Common Mistakes:

wheels generate route --resources=true              # Missing objectname
wheels generate route --resources products          # Missing =true
wheels generate route products --resource           # Wrong flag name

✅ Correct Usage:

wheels generate route --resources=true products     # Explicit flag with objectname
wheels generate route products --resources=true     # Alternative order
wheels generate route products                      # Default resources (implicit)

6. Route Placement Issues

Issue: Routes added in wrong location or break existing routes

Common Problems:

  • CLI marker // CLI-Appends-Here not found

  • Routes added after wildcard route

  • Malformed routes.cfm syntax

Solutions:

<!-- Ensure routes.cfm has proper structure -->
<cfscript>
mapper()
    // Existing routes
    .resources("products")

    // CLI marker for new routes
    // CLI-Appends-Here

    // Wildcard MUST be last
    .wildcard()

    // Root route
    .root(to="home##index", method="get")
.end();  // Don't forget .end()!
</cfscript>

Validation and Testing

Pre-Generation Checklist

Before generating routes, verify:

# 1. Check current directory is Wheels app root
ls config/routes.cfm    # Should exist

# 2. Verify routes.cfm has CLI marker
grep "CLI-Appends-Here" config/routes.cfm    # Should find marker

# 3. Check routes.cfm syntax is valid
# Look for proper mapper() and .end() structure

Post-Generation Validation

After generating routes, always:

# 1. Reload application
http://localhost:8080/?reload=true

# 2. Test route in browser
http://localhost:8080/your-new-route

# 3. Check debug footer for route information
# Look for your new route in the Routes section

Testing Generated Routes

# Test different HTTP methods
curl -X GET http://localhost:8080/api/users
curl -X POST http://localhost:8080/api/users -d "name=test"
curl -X PUT http://localhost:8080/api/users/1 -d "name=updated"
curl -X DELETE http://localhost:8080/api/users/1

Error Reference

Common Error Messages

"Please provide either an objectname for a resources route or specify a route type"

  • Cause: No parameters provided to command

  • Solution: Provide either objectname or HTTP method parameter

"key [TYPE] doesn't exist"

  • Cause: Internal processing error (rare)

  • Solution: Try simpler route first, then add complexity

"Template compilation error"

  • Cause: Single hash (#) in generated route

  • Solution: Check for double hash (##) in all handlers

"Route not found" (404 errors)

  • Cause: Route not added or application not reloaded

  • Solution: Check routes.cfm and reload application

Best Practices for Avoiding Issues

1. Parameter Formatting

# Always use consistent formatting
wheels generate route --get="pattern,handler"    # ✅ Consistent
wheels generate route --get pattern,handler      # ❌ Inconsistent

2. Route Planning

# Plan route structure before generating
# 1. Resources routes first
wheels generate route products
wheels generate route users

# 2. Custom routes second
wheels generate route --get="search,search##index"
wheels generate route --post="contact,contact##send"

# 3. API routes with namespace pattern
wheels generate route --get="api/products,api.products##index"

3. Testing Strategy

# Generate one route at a time
wheels generate route --get="test,test##index"
# Test it works
curl http://localhost:8080/test
# Then generate next route

4. Documentation

# Document custom routes in routes.cfm
.get(name="productSearch", pattern="products/search", to="products##search")
// Custom search endpoint for products - returns JSON

Getting Help

If you encounter issues not covered here:

  1. Check the debug footer: Shows all registered routes

  2. Verify controller exists: Match route handler to actual controller/action

  3. Test with simple routes first: Basic patterns before complex ones

  4. Check Wheels routing guide: For advanced routing features

  5. Reload frequently: Always reload after route changes

See Also

  • wheels generate scaffold - Generate complete CRUD with routes

  • wheels generate controller - Generate controllers

  • wheels generate model - Generate models

  • Wheels Routing Guide - Complete routing documentation

wheels generate controller

Generate a controller with actions and optional views.

Synopsis

wheels generate controller name=<controllerName> [options]

#Can also be used as:
wheels g controller name=<controllerName> [options]

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Positional parameters: wheels generate controller Products (controller name)

  • Named parameters: name=value (e.g., name=Products, actions=index,show)

  • Flag parameters: --flag equals flag=true (e.g., --crud equals crud=true)

  • Flag with value: --flag=value equals flag=value (e.g., --actions=index,show)

Parameter Mixing Rules:

ALLOWED:

  • Positional: wheels generate controller Products

  • Positional + flags: wheels generate controller Products --crud --api

  • All named: name=Products actions=index,show

NOT ALLOWED:

  • Positional + named: wheels generate controller Products actions=index (causes error)

Recommendation: Use positional for controller name, flags for options: wheels generate controller Products --crud

Note: Flag syntax (--flag) avoids positional/named parameter conflicts and is recommended for boolean options.

Important: Use --actions (plural) not --action (singular) for custom action lists.

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

name

Name of the controller to create (usually plural)

Required

Options

Option
Description
Default

actions

Actions to generate (comma-delimited) - HIGHEST PRIORITY, overrides --crud

index

crud

Generate CRUD controller with actions (index, show, new, create, edit, update, delete) and scaffold-style views (index, show, new, edit, _form)

false

api

Generate API controller (no views generated, only JSON/XML endpoints)

false

noViews

Skip view generation (only generate controller)

false

description

Controller description comment

""

force

Overwrite existing files

false

Parameter Priority and Behavior

Understanding how parameters interact is important for getting the expected results.

Default Behavior

The controller generator creates views for ALL actions by default (unless you use --api or --noViews).

Parameter Priority

The command evaluates parameters in the following order:

PRIORITY 1 (HIGHEST): --actions
   └─> If specified, generates ONLY the specified actions
   └─> Overrides --crud and --api
   └─> Example: --actions=dashboard,reports

PRIORITY 2: --crud
   └─> If specified (and --actions not used), generates 7 CRUD actions:
       - index, show, new, create, edit, update, delete
   └─> Generates 5 scaffold-style views:
       - index.cfm, show.cfm, new.cfm, edit.cfm, _form.cfm

PRIORITY 3: --api
   └─> If specified (and --actions not used), generates 5 API actions:
       - index, show, create, update, delete
   └─> Implies --crud but excludes form actions (new, edit)
   └─> Implies --noViews (no views generated)

PRIORITY 4 (DEFAULT): No flags
   └─> Generates only 1 action: index
   └─> Generates 1 view: index.cfm

View Generation Rules

Views are generated for actions EXCEPT when:

  • --api flag is used (API controllers return JSON/XML, not HTML)

  • --noViews flag is used (explicitly skip view generation)

Views ARE generated by default for:

  • Controllers with --crud flag (5 scaffold-style views)

  • Controllers with custom --actions (1 view per action)

  • Controllers with no flags (1 view for index action)

Parameter Combinations

Basic controller (no flags)

wheels generate controller Products

# Result:
# Actions: index
# Views: index.cfm

Custom actions

wheels generate controller Products --actions=dashboard,reports,export

# Result:
# Actions: dashboard, reports, export
# Views: dashboard.cfm, reports.cfm, export.cfm

CRUD controller with scaffold-style views

wheels generate controller Products --crud

# Result:
# Actions: index, show, new, create, edit, update, delete (7 actions)
# Views: index.cfm, show.cfm, new.cfm, edit.cfm, _form.cfm (5 views)
# Note: Same views as "wheels generate scaffold"

Custom actions without views

wheels generate controller Products --actions=dashboard,export --noViews

# Result:
# Actions: dashboard, export
# Views: None (explicitly skipped)

API controller (no views)

wheels generate controller Orders --api

# Result:
# Actions: index, show, create, update, delete (5 actions)
# Views: None (API returns JSON/XML)

CRUD controller without views

wheels generate controller Products --crud --noViews

# Result:
# Actions: index, show, new, create, edit, update, delete (7 actions)
# Views: None (explicitly skipped)

Custom actions override CRUD

wheels generate controller Products --actions=dashboard,reports --crud

# Result:
# Actions: dashboard, reports (--actions has priority)
# Views: dashboard.cfm, reports.cfm (views still created)
# Note: --crud is ignored when --actions is specified

Important Rules

  1. Views generated by default - Unless you use --api or --noViews, views are created for ALL actions

  2. --actions overrides --crud - Custom actions take priority, but views are STILL generated

  3. --api implies --crud and --noViews - API controllers get 5 actions, no views

  4. --noViews skips all views - Use when you only need the controller file

  5. --crud generates scaffold-style views - Same 5 views as wheels generate scaffold command

Decision Tree

ACTION GENERATION:
├─ Has --actions? → Use those actions ONLY (highest priority)
├─ Has --api? → Generate 5 actions (index, show, create, update, delete)
├─ Has --crud? → Generate 7 actions (index, show, new, create, edit, update, delete)
└─ Default → Generate 1 action (index)

VIEW GENERATION:
├─ Has --api? → NO VIEWS (JSON/XML responses)
├─ Has --noViews? → NO VIEWS (explicitly skipped)
├─ Has --crud? → 5 VIEWS (index, show, new, edit, _form)
└─ Default → CREATE 1 VIEW PER ACTION

Common Use Cases

What You Want
Command
Actions
Views

Traditional web app (scaffold-style)

--crud

7

5 (index, show, new, edit, _form)

REST API (JSON/XML)

--api

5

None

Single page controller

(no flags)

1 (index)

1 (index)

Custom actions with views

--actions=dashboard,export

2

2 (dashboard, export)

Controller only (no views)

--crud --noViews

7

None

Custom without views

--actions=api,process --noViews

2

None

When to Use What

  • Use --crud - Traditional web app with CRUD operations, forms, and .resources() routing (same as scaffold)

  • Use --api - REST API that returns JSON/XML (no views)

  • Use --actions - Custom actions with automatic view generation (highest priority)

  • Use --noViews - When you only need controller file (manual view management)

  • Use no flags - Simple controller with index action and view

Key Difference: --crud vs --api

Both generate RESTful controllers with different purposes:

Aspect
--crud
--api

Purpose

Traditional web application

API endpoints

Actions

7 (includes new, edit forms)

5 (no form actions)

Views

5 scaffold-style views

None

View Files

index, show, new, edit, _form

N/A

Routing

Use .resources() in routes.cfm

Use .resources() in routes.cfm

Response

HTML pages with forms

JSON/XML data

Use Case

User-facing web apps

Mobile apps, SPAs, integrations

Important: --crud generates the same views as wheels generate scaffold command, including the _form.cfm partial for form code reuse.

Examples:

# CRUD web app with scaffold-style views
wheels generate controller Products --crud
# Result: 7 actions + 5 views
# Use for: Traditional web applications with HTML forms

# REST API without views
wheels generate controller Products --api
# Result: 5 actions + 0 views
# Use for: Mobile apps, single-page apps, third-party integrations

Examples

Parameter Priority Examples

Understanding how --actions overrides --crud:

# Example 1: CRUD flag generates 7 actions and 5 views
wheels generate controller Products --crud

# Result:
# Actions: index, show, new, create, edit, update, delete (7 actions)
# Views: index.cfm, show.cfm, new.cfm, edit.cfm, _form.cfm (5 views)

# Example 2: Actions flag OVERRIDES crud (HIGHEST PRIORITY)
wheels generate controller Products --crud --actions=index

# Result:
# Actions: index (only this action)
# Views: index.cfm (only this view)
# Note: --actions has HIGHEST PRIORITY, --crud is ignored

# Example 3: Multiple custom actions override crud
wheels generate controller Products --crud --actions=dashboard,reports

# Result:
# Actions: dashboard, reports (only these actions)
# Views: dashboard.cfm, reports.cfm (only these views)
# Note: --actions overrides --crud completely

Common Mistake: Using --action (singular) instead of --actions (plural)

# WRONG - will not work
wheels generate controller Products --action=index

# CORRECT - use plural --actions
wheels generate controller Products --actions=index

Basic controller

# Positional (recommended)
wheels generate controller Products

# OR named
wheels g controller name=Products

Creates:

  • app/controllers/Products.cfc with index action

  • app/views/products/index.cfm view

Controller with custom actions

# Positional + flag (recommended)
wheels generate controller Products --actions=dashboard,reports,export

# OR all named
wheels g controller name=Products actions=dashboard,reports,export

Creates:

  • app/controllers/Products.cfc with 3 custom actions

  • app/views/products/dashboard.cfm

  • app/views/products/reports.cfm

  • app/views/products/export.cfm

CRUD controller (scaffold-style views)

# Positional + flag (recommended)
wheels generate controller Products --crud

# OR all named
wheels g controller name=Products crud=true

Creates:

  • app/controllers/Products.cfc with 7 CRUD actions

  • 5 scaffold-style views: index.cfm, show.cfm, new.cfm, edit.cfm, _form.cfm

Note: The _form.cfm partial is shared between new and edit views, just like in wheels generate scaffold.

API controller (no views)

# Positional + flag (recommended)
wheels generate controller Orders --api

# OR all named
wheels g controller name=Orders api=true

Creates:

  • app/controllers/Orders.cfc with 5 API actions

  • No views (API controllers don't need views)

Controller without views (noViews flag)

# Positional + flag (recommended)
wheels generate controller Products --crud --noViews

# OR all named
wheels g controller name=Products rest=true noViews=true

Creates:

  • app/controllers/Products.cfc with 7 RESTful actions

  • No views (explicitly skipped with --noViews)

Custom actions without views

# Positional + flags (recommended)
wheels generate controller Reports --actions=dashboard,export --noViews

# OR all named
wheels g controller name=Reports actions=dashboard,export noViews=true

Creates:

  • app/controllers/Reports.cfc with 2 custom actions

  • No views (explicitly skipped)

Generated Code

Basic Controller

component extends="Controller" {

  /**
	* Controller config settings
	**/
	function config() {

	}

    /**
     * index action
     */
    function index() {
        // TODO: Implement index action
    }
}

Controller with Description

/**
 * Handles user management operations
 */
component extends="Controller" {

  /**
	* Controller config settings
	**/
	function config() {

	}

    /**
     * index action
     */
    function index() {
        // TODO: Implement index action
    }
}

RESTful Controller

component extends="Controller" {

	function config() {
		verifies(except="index,new,create", params="key", paramsTypes="integer", handler="objectNotFound");
	}

	/**
	* View all Products
	**/
	function index() {
		products=model("product").findAll();
	}

	/**
	* View Product
	**/
	function show() {
		product=model("product").findByKey(params.key);
	}

	/**
	* Add New Product
	**/
	function new() {
		product=model("product").new();
	}

	/**
	* Create Product
	**/
	function create() {
		product=model("product").create(params.product);
		if(product.hasErrors()){
			renderView(action="new");
		} else {
			redirectTo(action="index", success="Product successfully created");
		}
	}

	/**
	* Edit Product
	**/
	function edit() {
		product=model("product").findByKey(params.key);
	}

	/**
	* Update Product
	**/
	function update() {
		product=model("product").findByKey(params.key);
		if(product.update(params.product)){
			redirectTo(action="index", success="Product successfully updated");
		} else {
			renderView(action="edit");
		}
	}

	/**
	* Delete Product
	**/
	function delete() {
		product=model("product").deleteByKey(params.key);
		redirectTo(action="index", success="Product successfully deleted");
	}

	/**
	* Redirect away if verifies fails, or if an object can't be found
	**/
	function objectNotFound() {
		redirectTo(action="index", error="That Product wasn't found");
	}

}

API Controller

/**
 * API endpoint for order processing
 */
component extends="wheels.Controller" {

    function init() {
        provides("json");
		filters(through="setJsonResponse");
    }

    /**
     * GET /orders
     * Returns a list of all orders
     */
    function index() {
        local.orders = model("order").findAll();
        renderWith(data={ orders=local.orders });
    }

    /**
     * GET /orders/:key
     * Returns a specific order by ID
     */
    function show() {
        local.order = model("order").findByKey(params.key);

        if (IsObject(local.order)) {
            renderWith(data={ order=local.order });
        } else {
            renderWith(data={ error="Record not found" }, status=404);
        }
    }

    /**
     * POST /orders
     * Creates a new order
     */
    function create() {
        local.order = model("order").new(params.order);

        if (local.order.save()) {
            renderWith(data={ order=local.order }, status=201);
        } else {
            renderWith(data={ error="Validation failed", errors=local.order.allErrors() }, status=422);
        }
    }

    /**
     * PUT /orders/:key
     * Updates an existing order
     */
    function update() {
        local.order = model("order").findByKey(params.key);

        if (IsObject(local.order)) {
            local.order.update(params.order);

            if (local.order.hasErrors()) {
                renderWith(data={ error="Validation failed", errors=local.order.allErrors() }, status=422);
            } else {
                renderWith(data={ order=local.order });
            }
        } else {
            renderWith(data={ error="Record not found" }, status=404);
        }
    }

    /**
     * DELETE /orders/:key
     * Deletes a order
     */
    function delete() {
        local.order = model("order").findByKey(params.key);

        if (IsObject(local.order)) {
            local.order.delete();
            renderWith(data={}, status=204);
        } else {
            renderWith(data={ error="Record not found" }, status=404);
        }
    }

	/**
	* Set Response to JSON
	*/
	private function setJsonResponse() {
		params.format = "json";
	}

}

View Generation

Views are automatically generated for non-API controllers:

index.cfm

<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>

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

<cfscript>
mapper()
    .get(name="products", to="products##index")
    .get(name="product", to="products##show")
    .post(name="products", to="products##create")
    .wildcard()
.end();
</cfscript>

RESTful Resources

<cfscript>
mapper()
    .resources("products")
    .wildcard()
.end();
</cfscript>

Testing

Generate tests alongside controllers:

wheels generate controller name=products --crud
wheels generate test controller name=products

Best Practices

  1. Use plural names for resource controllers

  2. Keep controllers focused on single resources

  3. Use --crud for standard web app CRUD operations (with views and forms)

  4. Use --api for API endpoints (JSON/XML, no views)

  5. Use --actions when you need custom actions (HIGHEST PRIORITY - overrides --crud)

  6. Implement proper error handling

  7. Add authentication in config() method

  8. Use filters for common functionality

Common Patterns

Authentication Filter

function config() {
    filters(through="authenticate", except="index,show");
}

private function authenticate() {
    if (!session.isLoggedIn) {
        redirectTo(controller="sessions", action="new");
    }
}

Pagination

function index() {
    products = model("Product").findAll(
        page=params.page ?: 1,
        perPage=25,
        order="createdAt DESC"
    );
}

Search

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();
    }
}

See Also

  • wheels generate model - Generate models

  • wheels generate view - Generate views

  • wheels scaffold - Generate complete CRUD

  • wheels generate test - Generate controller tests

Testing Guide

Comprehensive guide to testing in Wheels applications using the CLI.

Overview

Wheels CLI provides robust testing capabilities through TestBox integration, offering:

  • Unit and integration testing

  • BDD-style test writing

  • Watch mode for continuous testing

  • Code coverage reporting

  • Parallel test execution

  • Docker-based testing across multiple engines and databases

Test Structure

Directory Layout

/tests/
├── Application.cfc          # Test suite configuration
├── specs/                   # Test specifications (alternative to folders below)
├── unit/                    # Unit tests
│   ├── models/             # Model tests
│   ├── controllers/        # Controller tests
│   └── services/           # Service tests
├── integration/            # Integration tests
├── fixtures/               # Test data files
└── helpers/                # Test utilities

Test File Naming

Follow these conventions:

  • Model tests: UserTest.cfc or UserSpec.cfc

  • Controller tests: UsersControllerTest.cfc

  • Integration tests: UserFlowTest.cfc

Writing Tests

Basic Test Structure

// tests/unit/models/UserTest.cfc
component extends="wheels.Testbox" {

    function run() {
        describe("User Model", function() {

            beforeEach(function() {
                // Setup before each test
                variables.user = model("User").new();
            });

            afterEach(function() {
                // Cleanup after each test
            });

            it("validates email presence", function() {
                variables.user.email = "";
                expect(variables.user.valid()).toBeFalse();
                expect(variables.user.errors).toHaveKey("email");
            });

            it("validates email format", function() {
                variables.user.email = "invalid-email";
                expect(variables.user.valid()).toBeFalse();
                expect(variables.user.errors.email).toInclude("valid email");
            });

        });
    }

}

Model Testing

component extends="wheels.Testbox" {

    function run() {
        describe("Product Model", function() {

            describe("Validations", function() {
                it("requires a name", function() {
                    var product = model("Product").new();
                    expect(product.valid()).toBeFalse();
                    expect(product.errors).toHaveKey("name");
                });

                it("requires price to be positive", function() {
                    var product = model("Product").new(
                        name = "Test Product",
                        price = -10
                    );
                    expect(product.valid()).toBeFalse();
                    expect(product.errors.price).toInclude("greater than 0");
                });
            });

            describe("Associations", function() {
                it("has many reviews", function() {
                    var product = model("Product").findOne();
                    expect(product).toHaveKey("reviews");
                    expect(product.reviews()).toBeQuery();
                });
            });

            describe("Scopes", function() {
                it("filters active products", function() {
                    // Create test data
                    model("Product").create(name="Active", active=true);
                    model("Product").create(name="Inactive", active=false);

                    var activeProducts = model("Product").active().findAll();
                    expect(activeProducts.recordCount).toBe(1);
                    expect(activeProducts.name).toBe("Active");
                });
            });

        });
    }

}

Controller Testing

component extends="wheels.Testbox" {

    function beforeAll() {
        // Setup test request context
        variables.mockController = prepareMock(createObject("component", "controllers.Products"));
    }

    function run() {
        describe("Products Controller", function() {

            describe("index action", function() {
                it("returns all products", function() {
                    // Setup
                    var products = queryNew("id,name", "integer,varchar", [
                        [1, "Product 1"],
                        [2, "Product 2"]
                    ]);

                    mockController.$("model").$args("Product").$returns(
                        mockModel.$("findAll").$returns(products)
                    );

                    // Execute
                    mockController.index();

                    // Assert
                    expect(mockController.products).toBe(products);
                    expect(mockController.products.recordCount).toBe(2);
                });
            });

            describe("create action", function() {
                it("creates product with valid data", function() {
                    // Setup params
                    mockController.params = {
                        product: {
                            name: "New Product",
                            price: 99.99
                        }
                    };

                    // Mock successful save
                    var mockProduct = createEmptyMock("models.Product");
                    mockProduct.$("save").$returns(true);
                    mockProduct.$("id", 123);

                    mockController.$("model").$args("Product").$returns(
                        createMock("models.Product").$("new").$returns(mockProduct)
                    );

                    // Execute
                    mockController.create();

                    // Assert
                    expect(mockController.flashMessages.success).toInclude("created successfully");
                    expect(mockController.redirectTo.action).toBe("show");
                    expect(mockController.redirectTo.key).toBe(123);
                });
            });

        });
    }

}

Integration Testing

component extends="wheels.Testbox" {

    function run() {
        describe("User Registration Flow", function() {

            it("allows new user to register", function() {
                // Visit registration page
                var event = execute(event="users.new", renderResults=true);
                expect(event.getRenderedContent()).toInclude("Register");

                // Submit registration form
                var event = execute(
                    event = "users.create",
                    eventArguments = {
                        user: {
                            email: "[email protected]",
                            password: "SecurePass123!",
                            passwordConfirmation: "SecurePass123!"
                        }
                    }
                );

                // Verify user created
                var user = model("User").findOne(where="email='[email protected]'");
                expect(user).toBeObject();

                // Verify logged in
                expect(session.userId).toBe(user.id);

                // Verify redirect
                expect(event.getValue("relocate_URI")).toBe("/dashboard");
            });

        });
    }

}

Test Helpers

Creating Test Factories

// tests/helpers/Factories.cfc
component {

    function createUser(struct overrides = {}) {
        var defaults = {
            email: "user#createUUID()#@test.com",
            password: "password123",
            firstName: "Test",
            lastName: "User"
        };

        defaults.append(arguments.overrides);
        return model("User").create(defaults);
    }

    function createProduct(struct overrides = {}) {
        var defaults = {
            name: "Product #createUUID()#",
            price: randRange(10, 100),
            stock: randRange(0, 50)
        };

        defaults.append(arguments.overrides);
        return model("Product").create(defaults);
    }

}

Test Data Management

// tests/helpers/TestDatabase.cfc
component {

    function setUp() {
        // Start transaction
        transaction action="begin";
    }

    function tearDown() {
        // Rollback transaction
        transaction action="rollback";
    }

    function clean() {
        // Clean specific tables
        queryExecute("DELETE FROM users WHERE email LIKE '%@test.com'");
        queryExecute("DELETE FROM products WHERE name LIKE 'Test%'");
    }

    function loadFixtures(required string name) {
        var fixtures = deserializeJSON(
            fileRead("/tests/fixtures/#arguments.name#.json")
        );

        for (var record in fixtures) {
            queryExecute(
                "INSERT INTO #arguments.name# (#structKeyList(record)#)
                 VALUES (#structKeyList(record, ':')#)",
                record
            );
        }
    }

}

Running Tests

Basic Commands

# Run all tests
wheels test run

# Run specific test file
wheels test run tests/unit/models/UserTest.cfc

# Run tests in directory
wheels test run tests/unit/models/

# Run with specific reporter
wheels test run --reporter=json
wheels test run --reporter=junit --outputFile=results.xml

Watch Mode

# Watch for changes and rerun tests
wheels test run --watch

# Watch specific directory
wheels test run tests/models --watch

# Watch with custom debounce
wheels test run --watch --watchDelay=1000

Filtering Tests

# Run by test bundles
wheels test run --bundles=models,controllers

# Run by labels
wheels test run --labels=critical

# Run by test name pattern
wheels test run --filter="user"

# Exclude patterns
wheels test run --excludes="slow,integration"

Code Coverage

Generate Coverage Report

# Generate HTML coverage report
wheels test coverage

# With custom output directory
wheels test coverage --outputDir=coverage-reports

# Include only specific paths
wheels test coverage --includes="models/,controllers/"

Coverage Configuration

In tests/Application.cfc:

this.coverage = {
    enabled: true,
    includes: ["models", "controllers"],
    excludes: ["tests", "wheels"],
    outputDir: expandPath("/tests/coverage/"),
    reportFormats: ["html", "json"]
};

Test Configuration

Test Suite Configuration

// tests/Application.cfc
component {

    this.name = "WheelsTestSuite" & Hash(GetCurrentTemplatePath());

    // Test datasource
    this.datasources["test"] = {
        url: "jdbc:h2:mem:test;MODE=MySQL",
        driver: "org.h2.Driver"
    };
    this.datasource = "test";

    // TestBox settings
    this.testbox = {
        bundles: ["tests"],
        recurse: true,
        reporter: "simple",
        reportpath: "/tests/results",
        runner: ["tests/runner.cfm"],
        labels: [],
        options: {}
    };

}

Environment Variables

# Set test environment
export WHEELS_ENV=testing

# Set test datasource
export WHEELS_TEST_DATASOURCE=myapp_test

# Enable verbose output
export TESTBOX_VERBOSE=true

Testing Best Practices

1. Test Organization

tests/
├── unit/              # Fast, isolated tests
│   ├── models/       # One file per model
│   └── services/     # Service layer tests
├── integration/      # Tests with dependencies
└── e2e/             # End-to-end tests

2. Test Isolation

describe("User Model", function() {

    beforeEach(function() {
        // Fresh instance for each test
        variables.user = model("User").new();

        // Clear caches
        application.wheels.cache.queries = {};
    });

    afterEach(function() {
        // Clean up test data
        if (isDefined("variables.user.id")) {
            variables.user.delete();
        }
    });

});

3. Descriptive Tests

// Good: Descriptive test names
it("validates email format with standard RFC 5322 regex", function() {
    // test implementation
});

it("prevents duplicate email addresses case-insensitively", function() {
    // test implementation
});

// Bad: Vague test names
it("works", function() {
    // test implementation
});

4. AAA Pattern

it("calculates order total with tax", function() {
    // Arrange
    var order = createOrder();
    var item1 = createOrderItem(price: 100, quantity: 2);
    var item2 = createOrderItem(price: 50, quantity: 1);
    order.addItem(item1);
    order.addItem(item2);

    // Act
    var total = order.calculateTotal(taxRate: 0.08);

    // Assert
    expect(total).toBe(270); // (200 + 50) * 1.08
});

Continuous Integration

GitHub Actions

name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - name: Setup CommandBox
      uses: Ortus-Solutions/[email protected]

    - name: Install dependencies
      run: box install

    - name: Run tests
      run: |
        box server start
        wheels test run --reporter=junit --outputFile=test-results.xml

    - name: Upload test results
      uses: actions/upload-artifact@v4
      with:
        name: test-results
        path: test-results.xml

    - name: Generate coverage
      run: wheels test coverage

    - name: Upload coverage
      uses: codecov/codecov-action@v1

Pre-commit Hooks

#!/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

echo "Running linter..."
wheels analyze code

if [ $? -ne 0 ]; then
    echo "Code quality check failed!"
    exit 1
fi

Debugging Tests

Using Debug Output

it("processes data correctly", function() {
    var result = processData(testData);

    // Debug output
    debug(result);
    writeDump(var=result, abort=false);

    // Conditional debugging
    if (request.debug ?: false) {
        writeOutput("Result: #serializeJSON(result)#");
    }

    expect(result.status).toBe("success");
});

Interactive Debugging

# Run specific test with debugging
wheels test debug tests/unit/models/UserTest.cfc

# Enable verbose mode
wheels test run --verbose

# Show SQL queries
wheels test run --showSQL

Performance Testing

Load Testing

describe("Performance", function() {

    it("handles 1000 concurrent users", function() {
        var threads = [];

        for (var i = 1; i <= 1000; i++) {
            arrayAppend(threads, function() {
                var result = model("Product").findAll();
                return result.recordCount;
            });
        }

        var start = getTickCount();
        var results = parallel(threads);
        var duration = getTickCount() - start;

        expect(duration).toBeLT(5000); // Less than 5 seconds
        expect(arrayLen(results)).toBe(1000);
    });

});

Common Testing Patterns

Testing Private Methods

it("tests private method", function() {
    var user = model("User").new();

    // Use makePublic() for testing
    makePublic(user, "privateMethod");

    var result = user.privateMethod();
    expect(result).toBe("expected");
});

Mocking External Services

it("sends email on user creation", function() {
    // Mock email service
    var mockMailer = createEmptyMock("services.Mailer");
    mockMailer.$("send").$returns(true);

    // Inject mock
    var user = model("User").new();
    user.$property("mailer", mockMailer);

    // Test
    user.save();

    // Verify
    expect(mockMailer.$times("send")).toBe(1);
    expect(mockMailer.$callLog().send[1].to).toBe(user.email);
});

Docker-Based Testing

Wheels provides a comprehensive Docker environment for testing across multiple CFML engines and databases.

Quick Start with Docker

# Start the TestUI and all test containers
docker compose --profile all up -d

# Access the TestUI
open http://localhost:3000

TestUI Features

The modern TestUI provides:

  • Visual Test Runner: Run and monitor tests in real-time

  • Container Management: Start/stop containers directly from the UI

  • Multi-Engine Support: Test on Lucee 5/6 and Adobe ColdFusion 2018/2021/2023

  • Multi-Database Support: MySQL, PostgreSQL, SQL Server, H2, and Oracle

  • Pre-flight Checks: Ensures all services are running before tests

  • Test History: Track test results over time

Container Management

The TestUI includes an API server that allows you to:

  1. Click on any stopped engine or database to start it

  2. Monitor container health and status

  3. View real-time logs

  4. No terminal required for basic operations

Docker Profiles

Use profiles to start specific combinations:

# Just the UI
docker compose --profile ui up -d

# Quick test setup (Lucee 5 + MySQL)
docker compose --profile quick-test up -d

# All Lucee engines
docker compose --profile lucee up -d

# All Adobe engines
docker compose --profile adobe up -d

# All databases
docker compose --profile db up -d

Running Tests via Docker

# Using the CLI inside a container
docker exec -it wheels-lucee5-1 wheels test run

# Direct URL access
curl http://localhost:60005/wheels/core/tests?format=json&db=mysql

Database Testing

Test against different databases by using the db parameter:

# MySQL
wheels test run --db=mysql

# PostgreSQL
wheels test run --db=postgres

# SQL Server
wheels test run --db=sqlserver

# H2 (Lucee only)
wheels test run --db=h2

# Oracle
wheels test run --db=oracle

See Also

  • wheels test run - Test execution command

  • wheels test coverage - Coverage generation

  • wheels generate test - Generate test files

  • TestBox Documentation - Complete TestBox guide

  • Docker Testing Guide - Detailed Docker testing documentation

wheels env setup

Setup a new environment configuration for your Wheels application with comprehensive database, template, and configuration options.

Synopsis

Description

The wheels env setup command creates and configures new environments for your Wheels application. It generates:

  • Environment-specific .env.[environment] files with database and server settings using generic DB_* variable names

  • Configuration files at config/[environment]/settings.cfm with Wheels settings

  • Template-specific files (Docker, Vagrant) if requested

  • Server.json updates for environment-specific configurations

  • Updates config/environment.cfm with the current environment setting

The command supports copying configurations from existing environments and allows full customization of database types, templates, and framework settings.

Interactive Database Credentials

When setting up environments with non-H2 databases (MySQL, PostgreSQL, MSSQL, Oracle), if database credentials are not provided as command arguments, the command will interactively prompt you to enter:

  • Database host (default: localhost)

  • Database port (database-specific defaults)

  • Database username (default: varies by database type)

  • Database password (masked input)

  • Oracle SID (Oracle only)

This ensures you never use incorrect default credentials that could cause authentication failures.

Arguments

Argument
Description
Required

Note: Always use named parameter syntax: environment=name to avoid parameter conflicts.

Options

Option
Description
Default
Valid Values

Examples

Basic Environment Setup

Interactive Credential Example

Using Base Environment

Advanced Configuration

Template-Based Setups

What It Creates

1. Environment Variables File (.env.[environment])

Note: All database types now use generic DB_* variable names for portability and consistency.

For H2 database:

For MySQL database:

For Microsoft SQL Server:

2. Configuration File (config/[environment]/settings.cfm)

3. Template-Specific Files

Docker Template (--template=docker)

Creates:

  • docker-compose.[environment].yml

  • Dockerfile (if not exists)

Vagrant Template (--template=vagrant)

Creates:

  • Vagrantfile.[environment]

  • vagrant/provision-[environment].sh

Database Types

H2 (Embedded)

  • Use Case: Development, testing, quick prototyping

  • Connection: No network port required (embedded)

  • Database Path: ./db/[database_name]

  • Default Credentials: username=sa, password=(empty)

MySQL

  • Use Case: Production, staging environments

  • Default Port: 3306

  • Default Credentials: username=wheels, password=wheels_password

PostgreSQL

  • Use Case: Production, complex applications

  • Default Port: 5432

  • Default Credentials: username=wheels, password=wheels_password

Microsoft SQL Server

  • Use Case: Enterprise environments

  • Default Port: 1433

  • Default Credentials: username=sa, password=Wheels_Pass123!

Base Environment Copying

When using --base, the command copies configuration from an existing environment:

What Gets Copied:

  • Database host, username, and password

  • Server configuration (port, CF engine)

  • Custom environment variables

What Gets Modified:

  • Environment name

  • Database name (becomes wheels_[new_environment])

  • Database type, driver, and port (based on --dbtype)

  • Reload password (becomes wheels[new_environment])

Environment Naming Conventions

Recommended Names:

  • development or dev - Local development

  • testing or test - Automated testing

  • staging - Pre-production testing

  • production or prod - Live environment

  • qa - Quality assurance

  • demo - Client demonstrations

Custom Names:

Template Options

Local Template (default)

Best for traditional server deployments:

Docker Template

Creates containerized environment:

Generated docker-compose.docker-dev.yml:

Vagrant Template

Creates VM-based environment:

Next Steps After Setup

The command provides environment-specific next steps:

Local Template:

  1. Switch to environment: wheels env switch [environment]

  2. Start server: box server start

  3. Access application at: http://localhost:8080

Docker Template:

  1. Start Docker environment: docker-compose -f docker-compose.[environment].yml up

  2. Access application at: http://localhost:8080

  3. Stop environment: docker-compose -f docker-compose.[environment].yml down

Vagrant Template:

  1. Start Vagrant VM: vagrant up

  2. Access application at: http://localhost:8080 or http://192.168.56.10:8080

  3. SSH into VM: vagrant ssh

  4. Stop VM: vagrant halt

Configuration Management

Environment Detection

Update config/environment.cfm to automatically detect environments:

Environment Variables Integration

Load .env.[environment] files in Application.cfc:

Validation and Testing

After creating an environment, validate the setup:

Error Handling

Common Issues and Solutions:

Environment already exists:

Base environment not found:

Database connection issues:

  • Verify database credentials in .env.[environment]

  • Check database server is running

  • Validate port configuration

Permission issues:

  • Ensure write permissions for config directory

  • Check file system permissions

Best Practices

1. Environment Naming

  • Use consistent, descriptive names

  • Avoid spaces and special characters

  • Follow team conventions

2. Database Management

  • Use separate databases per environment

  • Document database naming conventions

  • Implement proper backup strategies

3. Security

  • Use strong, unique reload passwords

  • Never commit sensitive credentials

  • Use environment variables for secrets

4. Configuration

  • Start with a solid base environment

  • Document environment purposes

  • Test configurations thoroughly

5. Template Selection

  • local: Traditional server deployments

  • docker: Containerized applications

  • vagrant: Isolated development VMs

Integration Examples

CI/CD Pipeline

Feature Development

Troubleshooting

Configuration File Issues

  1. Check syntax in generated settings.cfm

  2. Verify file permissions in config directory

  3. Review environment variable formats

Database Connection Problems

  1. Verify database server is running

  2. Check credentials in .env.[environment]

  3. Test connection manually

  4. Review port configurations (remember H2 has no port)

Environment Detection

  1. Check config/environment.cfm logic

  2. Verify server variables

  3. Test detection rules manually

Performance Considerations

Development Environments

  • Enable debugging for detailed information

  • Disable caching for hot reload

  • Use H2 for fast setup and teardown

Production Environments

  • Disable debugging for performance

  • Enable all caching options

  • Use optimized database configurations

See Also

  • - List all environments

  • - Switch between environments

  • - Show current environment

wheels env setup environment=<name> [options]

environment

Environment name (e.g., development, staging, production, testing)

Yes

--template

Deployment template type

local

local, docker, vagrant

--dbtype

Database type

h2

h2, mysql, postgres, mssql, oracle

--database

Custom database name

wheels_[environment]

Any valid database name

--datasource

ColdFusion datasource name

wheels_[environment]

Any valid datasource name

--host

Database host

localhost (or prompted)

Any valid hostname/IP

--port

Database port

Database-specific (or prompted)

Valid port number

--username

Database username

Database-specific (or prompted)

Any valid username

--password

Database password

(prompted if not provided)

Any string

--sid

Oracle SID (Oracle only)

ORCL (or prompted)

Any valid SID

--base

Base environment to copy from

(none)

Any existing environment name

--force

Overwrite existing environment

false

true, false

--debug

Enable debug settings

false

true, false

--cache

Enable cache settings

false

true, false

--reloadPassword

Custom reload password

wheels[environment]

Any string

--skipDatabase

Skip database creation

false

true, false

--help

Show detailed help information

false

-

# Create development environment with H2 database (default)
wheels env setup environment=development

# Create staging environment with MySQL (will prompt for credentials)
wheels env setup environment=staging --dbtype=mysql

# Create production environment with PostgreSQL and caching enabled
wheels env setup environment=production --dbtype=postgres --cache=true --debug=false

# Create environment with explicit credentials (no prompting)
wheels env setup environment=test --dbtype=mssql --host=localhost --port=1433 --username=sa --password=MyPassword123!
# Running without credentials prompts interactively
wheels env setup environment=production --dbtype=mssql

# Output:
# Setting up production environment...
#
# Database credentials not provided for mssql database
# Would you like to enter database credentials now? [y/n] y
#
# Please provide database connection details:
#
# Database Host [localhost]: localhost
# Database Port [1433]: 1433
# Database Username [sa]: sa
# Database Password: ************
#
# Database credentials captured successfully!
# Copy settings from development environment but use PostgreSQL
wheels env setup environment=testing --base=development --dbtype=postgres

# Create staging environment based on production settings
wheels env setup environment=staging --base=production --database=staging_db

# Create QA environment with custom settings
wheels env setup environment=qa --base=development --dbtype=mysql --debug=true --cache=false
# Setup with custom database and datasource names
wheels env setup environment=integration --dbtype=mysql --database=wheels_integration_db --datasource=integration_ds

# Setup with specific reload password and debugging
wheels env setup environment=dev --debug=true --reloadPassword=mypassword123 --force

# Docker environment setup
wheels env setup environment=docker-dev --template=docker --dbtype=postgres --database=wheels_docker
# Local development (default)
wheels env setup environment=dev --template=local --dbtype=h2

# Docker containerized environment
wheels env setup environment=docker-staging --template=docker --dbtype=mysql

# Vagrant VM environment
wheels env setup environment=vm-test --template=vagrant --dbtype=postgres
## Wheels Environment: development
## Generated on: 2025-01-18 12:30:00

## Application Settings
WHEELS_ENV=development
WHEELS_RELOAD_PASSWORD=wheelsdevelopment

## Database Settings
DB_TYPE=h2
DB_HOST=
DB_PORT=
DB_DATABASE=./db/wheels_development
DB_USER=sa
DB_PASSWORD=
DB_DATASOURCE=wheels_development

## Server Settings
SERVER_PORT=8080
SERVER_CFENGINE=lucee5
## Wheels Environment: production
## Generated on: 2025-01-18 12:30:00

## Application Settings
WHEELS_ENV=production
WHEELS_RELOAD_PASSWORD=wheelsproduction

## Database Settings
DB_TYPE=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=wheels_production
DB_USER=wheels
DB_PASSWORD=wheels_password
DB_DATASOURCE=wheels_production

## Server Settings
SERVER_PORT=8080
SERVER_CFENGINE=lucee5
## Wheels Environment: staging
## Generated on: 2025-01-18 12:30:00

## Application Settings
WHEELS_ENV=staging
WHEELS_RELOAD_PASSWORD=wheelsstaging

## Database Settings
DB_TYPE=mssql
DB_HOST=localhost
DB_PORT=1433
DB_DATABASE=wheels_staging
DB_USER=sa
DB_PASSWORD=MySecurePassword123!
DB_DATASOURCE=wheels_staging

## Server Settings
SERVER_PORT=8080
SERVER_CFENGINE=lucee5
<cfscript>
    // Environment: production
    // Generated: 2025-01-18 12:30:00
    // Debug Mode: Disabled
    // Cache Mode: Enabled

    // Database settings
    set(dataSourceName="wheels_production");

    // Environment settings
    set(environment="production");

    // Debug settings - controlled by debug argument
    set(showDebugInformation=false);
    set(showErrorInformation=false);

    // Caching settings - controlled by cache argument
    set(cacheFileChecking=true);
    set(cacheImages=true);
    set(cacheModelInitialization=true);
    set(cacheControllerInitialization=true);
    set(cacheRoutes=true);
    set(cacheActions=true);
    set(cachePages=true);
    set(cachePartials=true);
    set(cacheQueries=true);

    // Security
    set(reloadPassword="wheelsproduction");

    // URLs
    set(urlRewriting="partial");

    // Environment-specific settings
    set(sendEmailOnError=true);
    set(errorEmailAddress="[email protected]");
</cfscript>
wheels env setup environment=dev --dbtype=h2 --database=my_dev_db
wheels env setup environment=prod --dbtype=mysql --database=wheels_production
wheels env setup environment=staging --dbtype=postgres
wheels env setup environment=enterprise --dbtype=mssql
# Copy from production but use H2 for testing
wheels env setup environment=test --base=production --dbtype=h2

# Copy from development but use different database name
wheels env setup environment=feature-branch --base=development --database=feature_test_db
wheels env setup environment=feature-auth --base=development
wheels env setup environment=performance-test --base=production --cache=false
wheels env setup environment=client-demo --base=staging
wheels env setup environment=prod --template=local --dbtype=mysql
wheels env setup environment=docker-dev --template=docker --dbtype=postgres
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - WHEELS_ENV=docker-dev
      - DB_TYPE=postgres
      - DB_HOST=db
      - DB_PORT=5432
      - DB_NAME=wheels
      - DB_USER=wheels
      - DB_PASSWORD=wheels_password
    volumes:
      - .:/app
    depends_on:
      - db

  db:
    image: postgres:14
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB=wheels
      POSTGRES_USER=wheels
      POSTGRES_PASSWORD=wheels_password
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:
wheels env setup environment=vm-test --template=vagrant --dbtype=mysql
<cfscript>
// Auto-detect environment based on server name
if (cgi.server_name contains "staging") {
    this.env = "staging";
} else if (cgi.server_name contains "test") {
    this.env = "testing";
} else if (cgi.server_name contains "demo") {
    this.env = "demo";
} else if (cgi.server_name contains "localhost") {
    this.env = "development";
} else {
    this.env = "production";
}
</cfscript>
<cfscript>
component extends="wheels.Controller" {

    function config() {
        // Load environment-specific variables
        var envFile = expandPath(".env." & get("environment"));
        if (fileExists(envFile)) {
            loadEnvironmentFile(envFile);
        }
    }

    private function loadEnvironmentFile(filePath) {
        var lines = fileRead(arguments.filePath).listToArray(chr(10));
        for (var line in lines) {
            if (len(trim(line)) && !line.startsWith("##")) {
                var parts = line.listToArray("=");
                if (arrayLen(parts) >= 2) {
                    var key = trim(parts[1]);
                    var value = trim(parts[2]);
                    // Set as system property or use in configuration
                    set(lCase(key), value);
                }
            }
        }
    }
}
</cfscript>
# List all environments to verify creation
wheels env list

# Switch to the new environment
wheels env switch [environment]

# Test database connection
wheels test run --type=core

# Check configuration
wheels env current
wheels env setup environment=staging --force
# Check available environments
wheels env list
# Use correct base environment name
wheels env setup environment=test --base=development
# Create testing environment for CI
wheels env setup environment=ci-test --base=production --dbtype=h2 --debug=false

# Create staging environment for deployment testing
wheels env setup environment=staging --base=production --dbtype=mysql --cache=true
# Create feature-specific environment
wheels env setup environment=feature-login --base=development --database=login_feature_db

# Create A/B testing environments
wheels env setup environment=variant-a --base=production --database=variant_a_db
wheels env setup environment=variant-b --base=production --database=variant_b_db
wheels env list
wheels env switch
wheels env current
Environment Configuration Guide

wheels generate snippets

This command works correctly without options (parameters). Option support is under development and will be available soon.

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

--path

Output file path (required when output=file)

--customize

Interactive customization

false

--create

Create custom snippet template

--force

Overwrite existing files

false

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(columnNames="model_name", allowNull=false);
            t.integer(columnNames="record_id", allowNull=false);
            t.string(columnNames="action", allowNull=false);
            t.text(columnNames="changes");
            t.integer(columnNames="user_id");
            t.string(columnNames="ip_address");
            t.string(columnNames="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

Configuration and Defaults

An overview of Wheels configuration and how it is used in your applications. Learn how to override a Wheels convention to make it your own.

We all love the "Convention over Configuration" motto of Wheels, but what about those two cases that pop into everyone's head? What if I want to develop in my own way? Or, What about an existing application that I need to port into Wheels? Gladly, that's what configuration and defaults are there for. Let's take a look at exactly how this is performed.

Where Configurations Happen

You will find configuration files in the config folder at the root of your Wheels application. In general, most of your settings will go in /config/settings.cfm.

You can also set values based on what environment you have set. For example, you can have different values for your settings depending on whether you're in development mode or production mode. See the chapter on Switching Environments for more details.

How to Set Configurations

To change a Wheels application default, you generally use the set() function. With it, you can perform all sorts of tweaks to the framework's default behaviors.

How to Access Configuration Values

Use the get() function to access the value of a Wheels application setting. Just pass it the name of the setting.

CFScript
if (get("environment") == "production") {
    // Do something for production environment
}

Setting CFML Application Configurations

In CFML's standard Application.cfc, you can normally set values for your application's properties in the thisscope. Wheels still provides these options to you in the file at config/app.cfm.

Here is an example of what can go in config/app.cfm:

config/app.cfm
this.name = "TheNextSiteToBeatTwitter";
this.sessionManagement = false;

this.customTagPaths = ListAppend(
  this.customTagPaths,
  ExpandPath("../customtags")
);

Using Environment Variables in config/app.cfm

Important: When your application starts, Wheels automatically loads .env files and makes their values available in this.env before config/app.cfm is executed. This means you can access environment variables directly in config/app.cfm using this.env.

Accessing Environment Variables

Use this.env to access values from your .env file:

config/app.cfm
// Access environment variables using this.env
this.name = this.env["APP_NAME"] ?: "MyWheelsApp";

// Database configuration using environment variables
this.datasources["myapp"] = {
    class: this.env["DB_CLASS"] ?: "org.h2.Driver",
    connectionString: this.env["DB_CONNECTION_STRING"],
    username: this.env["DB_USER"],
    password: this.env["DB_PASSWORD"]
};

// Multiple datasources from environment variables
this.datasources["primary"] = {
    class: "com.mysql.cj.jdbc.Driver",
    connectionString: "jdbc:mysql://" & this.env["DB_HOST"] & ":" & this.env["DB_PORT"] & "/" & this.env["DB_NAME"],
    username: this.env["DB_USER"],
    password: this.env["DB_PASSWORD"]
};

Example .env File

.env
# Application Settings
APP_NAME=MyWheelsApp
WHEELS_ENV=development

# Database Configuration
DB_HOST=localhost
DB_PORT=3306
DB_NAME=myapp_dev
DB_USER=dbuser
DB_PASSWORD=secret123
DB_CLASS=com.mysql.cj.jdbc.Driver
DB_CONNECTION_STRING=jdbc:mysql://localhost:3306/myapp_dev?useSSL=false

Environment-Specific Configuration

You can create environment-specific .env files:

  • .env - Default values for all environments

  • .env.development - Development-specific values

  • .env.production - Production-specific values

  • .env.testing - Testing-specific values

Wheels will automatically load the appropriate file (.env.[environment]) based the variable WHEELS_ENV defined in your current .env file.

Best Practices for Environment Variables

  1. Never commit sensitive credentials - Add .env to your .gitignore file

  2. Use .env.example as a template - Commit a template with placeholder values

  3. Use the null coalescing operator - Provide defaults: this.env["KEY"] ?: "default"

  4. Document required variables - List all required environment variables in your README

.env.example
# Copy this file to .env and fill in your values
# Never commit .env to version control!

APP_NAME=YourAppName
DB_HOST=localhost
DB_PORT=3306
DB_NAME=your_database
DB_USER=your_username
DB_PASSWORD=your_password

Types of Configurations Available

There are several types of configurations that you can perform in Wheels to override all those default behaviors. In Wheels, you can find all these configuration options:

  • Environment settings

  • URL rewriting settings

  • Data source settings

  • Function settings

  • Debugging and error settings

  • Caching settings

  • ORM settings

  • Plugin settings

  • Media settings

  • Routing settings

  • View helper settings

  • CSRF protection settings

  • Miscellaneous Settings

  • Migrator settings

Let's take a closer look at each of these options.

Environment Settings

Not only are the environments useful for separating your production settings from your "under development" settings, but they are also opportunities for you to override settings that will only take effect in a specified environment.

The setting for the current environment can be found in config/environment.cfm and should look something like this:

config/environment.cfm
set(environment="development");

Full Listing of Environment Settings

Name
Type
Default
Description

environment

string

development

Environment to load. Set this value in config/environment.cfm. Valid values are development, testing, maintenance, and production.

reloadPassword

string

[empty string]

Password to require when reloading the Wheels application from the URL. Leave empty to require no password.

redirectAfterReload

boolean

Enabled in maintenance and production

Whether or not to redirect away from the current URL when it includes a reload request. This hinders accidentally exposing your application's reload URL and password in web analytics software, screenshots of the browser, etc.

ipExceptions

string

[empty string]

IP addresses that Wheels will ignore when the environment is set to maintenance. That way administrators can test the site while in maintenance mode, while the rest of users will see the message loaded in /app/events/onmaintenance.cfm.

allowEnvironmentSwitchViaUrl

boolean

true

Set to false to disable switching of environment configurations via URL. You can still reload the application, but switching environments themselves will be disabled.

URL Rewriting Settings

Sometimes it is useful for our applications to "force" URL rewriting. By default, Wheels will try to determine what type of URL rewriting to perform and set it up for you. But you can force in or out this setting by using the example below:

CFScript
set(urlRewriting="Off");

The code above will tell Wheels to skip its automatic detection of the URL Rewriting capabilities and just set it as "Off".

You can also set it to "Partial" if you believe that your web server is capable of rewriting the URL as folders after index.cfm.

For more information, read the chapter about URL Rewriting.

Data Source Settings

Probably the most important configuration of them all. What is an application without a database to store all of its precious data?

The data source configuration is what tells Wheels which database to use for all of its models. (This can be overridden on a per-model basis, but that will be covered later.)

Basic Data Source Configuration

config/settings.cfm
set(dataSourceName="yourDataSourceName");
set(dataSourceUserName="yourDataSourceUsername");
set(dataSourcePassword="yourDataSourcePassword");

Using Environment Variables for Data Sources

Recommended: Use environment variables for database credentials to keep sensitive information secure. You can access environment variables using this scope:

In config/app.cfm (recommended for datasource configuration):

config/app.cfm
// Use this.env to access .env file variables
this.datasources["myapp"] = {
    class: this.env["DB_CLASS"],
    connectionString: this.env["DB_CONNECTION_STRING"],
    username: this.env["DB_USER"],
    password: this.env["DB_PASSWORD"]
};

See Using Environment Variables in config/app.cfm for more details on working with environment variables.

Function Settings

OK, here it's where the fun begins! Wheels includes a lot of functions to make your life as a CFML developer easier. A lot of those functions have sensible default argument values to minimize the amount of code that you need to write. And yes, you guessed it, Wheels lets you override those default argument values application-wide.

Let's look at a little of example:

CFScript
set(functionName="findAll", perPage=20);

That little line of code will make all calls to the findAll() method in Wheels return a maximum number of 20 record per page (if pagination is enabled for that findAll() call). How great is that? You don't need to set the perPage value for every single call to findAll() if you have a different requirement than the Wheels default of 10 records.

Debugging and Error Settings

You'll generally want to configure how Wheels handles errors and debugging information based on your environment. For example, you probably won't want to expose CFML errors in your production environment, whereas you would want to see those errors in your development environment.

For example, let's say that we want to enable debugging information in our "development" environment temporarily:

CFScript
// config/development/settings.cfm
set(showDebugInformation=false);

Full Listing of Debugging and Error Settings

Name
Type
Default
Description

errorEmailServer

string

[empty string]

Server to use to send out error emails. When left blank, this defaults to settings in the ColdFusion Administrator (if set).

errorEmailAddress

string

[empty string]

Comma-delimited list of email address to send error notifications to. Only applies if sendEmailOnError is set to true.

errorEmailSubject

string

Error

Subject of email that gets sent to administrators on errors. Only applies if sendEmailOnError is set to true.

excludeFromErrorEmail

string

[empty string]

List of variables available in the scopes to exclude from the scope dumps included in error emails. Use this to keep sensitive information from being sent in plain text over email.

sendEmailOnError

boolean

Enabled in production environments that have a TLD like .com, .org, etc.

When set to true, Wheels will send an email to administrators whenever Wheels throws an error.

showDebugInformation

boolean

Enabled in development mode.

When set to true, Wheels will show debugging information in the footers of your pages.

showErrorInformation

boolean

Enabled in development, maintenance, and testing mode.

When set to false, Wheels will run and display code stored at /app/events/onerror.cfm instead of revealing CFML errors.

For more information, refer to the chapter about Switching Environments.

Caching Settings

Wheels does a pretty good job at caching the framework and its output to speed up your application. But if personalization is key in your application, finer control over caching settings will become more important.

Let's say your application generates dynamic routes and you need it to check the routes on each request. This task will be as simple as this line of code:

CFScript
set(cacheRoutes=false);

Full Listing of Caching Settings

Name
Type
Default
Description

cacheActions

boolean

Enabled in maintenance, testing, and production

When set to true, Wheels will cache output generated by actions when specified (in a caches() call, for example).

cacheControllerConfig

boolean

Enabled in development, maintenance, testing, and production

When set to false, any changes you make to the config() function in the controller file will be picked up immediately.

cacheCullInterval

numeric

5

Number of minutes between each culling action. The reason the cache is not culled during each request is to keep performance as high as possible.

cacheCullPercentage

numeric

10

If you set this value to 10, then at most, 10% of expired items will be deleted from the cache.

cacheDatabaseSchema

boolean

Enabled in development, maintenance, testing, and production

When set to false, you can add a field to the database, and Wheels will pick that up right away.

cacheFileChecking

boolean

Enabled in development, maintenance, testing, and production

When set to true, Wheels will cache whether or not controller, helper, and layout files exist

cacheImages

boolean

Enabled in development, maintenance, testing, and production

When set to true, Wheels will cache general image information used in imageTag() like width and height.

cacheModelConfig

boolean

Enabled in development, maintenance, testing, and production

When set to false, any changes you make to the config() function in the model file will be picked up immediately.

cachePages

boolean

Enabled in maintenance, testing, and production

When set to true, Wheels will cache output generated by a view page when specified (in a renderView() call, for example).

cachePartials

boolean

Enabled in maintenance, testing, and production

When set to true, Wheels will cache output generated by partials when specified (in a includePartial() call for example).

cacheQueries

boolean

Enabled in maintenance, testing, and production

When set to true, Wheels will cache SQL queries when specified (in a findAll() call, for example).

clearQueryCacheOnReload

boolean

true

Set to true to clear any queries that Wheels has cached on application reload.

cacheRoutes

boolean

Enabled in development, maintenance, testing, and production

When set to true, Wheels will cache routes across all page views.

defaultCacheTime

numeric

60

Number of minutes an item should be cached when it has not been specifically set through one of the functions that perform the caching in Wheels (i.e., caches(), findAll(), includePartial(), etc.)

maximumItemsToCache

numeric

5000

Maximum number of items to store in Wheels's cache at one time. When the cache is full, items will be deleted automatically at a regular interval based on the other cache settings.

For more information, refer to the chapter on Caching.

ORM Settings

The Wheels ORM provides many sensible conventions and defaults, but sometimes your data structure requires different column naming or behavior than what Wheels expects out of the box. Use these settings to change those naming conventions or behaviors across your entire application.

For example, if we wanted to prefix all of the database table names in our application with blog_ but didn't want to include that at the beginning of model names, we would do this:

CFScript
set(tableNamePrefix="blog_");

Now your post model will map to the blog_posts table, comment model will map to the blog_comments table, etc.

Full Listing of ORM Settings

Name
Type
Default
Description

afterFindCallbackLegacySupport

boolean

true

When this is set to false and you're implementing an afterFind() callback, you need to write the same logic for both the this scope (for objects) and arguments scope (for queries). Setting this to false makes both ways use the arguments scope so you don't need to duplicate logic. Note that the default is true for backwards compatibility.

automaticValidations

boolean

true

Set to false to stop Wheels from automatically running object validations based on column settings in your database.

setUpdatedAtOnCreate

boolean

true

Set to false to stop Wheels from populating the updatedAt timestamp with the createdAt timestamp's value.

softDeleteProperty

string

deletedAt

Name of database column that Wheels will look for in order to enforce soft deletes.

tableNamePrefix

string

[empty string]

String to prefix all database tables with so you don't need to define your model objects including it. Useful in environments that have table naming conventions like starting all table names with tbl

timeStampOnCreateProperty

string

createdAt

Name of database column that Wheels will look for in order to automatically store a "created at" time stamp when records are created.

timeStampOnUpdateProperty

string

updatedAt

Name of the database column that Wheels will look for in order to automatically store an "updated at" time stamp when records are updated.

transactionMode

string

commit

Use commit, rollback, or none to set default transaction handling for creates, updates and deletes.

useExpandedColumnAliases

boolean

false

When set to true, Wheels will always prepend children objects' names to columns included via findAll()'s include argument, even if there are no naming conflicts. For example, model("post").findAll(include="comment") in a fictitious blog application would yield these column names: id, title, authorId, body, createdAt, commentID, commentName, commentBody, commentCreatedAt, commentDeletedAt. When this setting is set to false, the returned column list would look like this: id, title, authorId, body, createdAt, commentID, name, commentBody, commentCreatedAt, deletedAt.

modelRequireConfig

boolean

false

Set to true to have Wheels throw an error when it can't find a config() method for a model. If you prefer to always use config() methods, this setting could save you some confusion when it appears that your configuration code isn't running due to misspelling "config" for example.

Plugin Settings

There are several settings that make plugin development more convenient. We recommend only changing these settings in development mode so there aren't any deployment issues in production, testing, and maintenancemodes. (At that point, your plugin should be properly packaged in a zip file.)

If you want to keep what's stored in a plugin's zip file from overwriting changes that you made in its expanded folder, set this in config/development/settings.cfm:

CFScript
set(overwritePlugins=false);

See the chapter on Installing and Using Plugins for more information.

Name
Type
Default
Description

deletePluginDirectories

boolean

true

When set to true, Wheels will remove subdirectories within the plugins folder that do not contain corresponding plugin zip files. Set to false to add convenience to the process for developing your own plugins.

loadIncompatiblePlugins

boolean

true

Set to false to stop Wheels from loading plugins whose supported versions do not match your current version of Wheels.

overwritePlugins

boolean

true

When set to true, Wheels will overwrite plugin files based on their source zip files on application reload. Setting this to false makes plugin development easier because you don't need to keep rezipping your plugin files every time you make a change.

showIncompatiblePlugins

boolean

false

When set to true, an incompatibility warning will be displayed for plugins that do not specify the current Wheels version.

Media Settings

Configure how Wheels handles linking to assets through view helpers like imageTag(), styleSheetLinkTag(), and javaScriptIncludeTag().

See the chapter about Date, Media, and Text Helpers for more information.

Full Listing of Asset Settings

Name
Type
Default
Description

assetQueryString

boolean

false in development mode, true in the other modes

Set to true to append a unique query string based on a time stamp to JavaScript, CSS, and image files included with the media view helpers. This helps force local browser caches to refresh when there is an update to your assets. This query string is updated when reloading your Wheels application. You can also hard code it by passing in a string.

assetPaths

struct

false

Pass false or a struct with up to 2 different keys to autopopulate the domains of your assets: http (required) and https. For example: {http="asset0.domain1.com,asset2.domain1.com,asset3.domain1.com", https="secure.domain1.com"}

Routing Settings

Wheels includes a powerful routing system. Parts of it are configurable with the following settings.

See the chapters about Using Routes and Obfuscating URLs for more information about how this all works together.

Full Listing of Routing Settings

Name
Type
Default
Description

loadDefaultRoutes

boolean

true

Set to false to disable Wheels's default route patterns for controller/action/key, etc.

obfuscateUrls

boolean

false

Set to true to obfuscate primary keys in URLs.

View Helper Settings

Wheels has a multitude of view helpers for building links, forms, form elements, and more. Use these settings to configure global defaults for their behavior.

Name
Type
Default
Description

booleanAttributes

any

allowfullscreen, async, autofocus, autoplay, checked, compact, controls, declare, default, defaultchecked, defaultmuted, defaultselected, defer, disabled, draggable, enabled, formnovalidate, hidden, indeterminate, inert, ismap, itemscope, loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open, pauseonexit, readonly, required, reversed, scoped, seamless, selected, sortable, spellcheck, translate, truespeed, typemustmatch, visible

A list of HTML attributes that should be allowed to be set as boolean values when added to HTML tags (e.g. disabled instead of disabled="disabled"). You can also pass in true(all attributes will be boolean) or false (no boolean attributes allowed, like in XHTML).

CSRF Protection Settings

Wheels includes built-in Cross-Site Request Forgery (CSRF) protection for form posts. Part of the CSRF protection involves storing an authenticity token in the session (default) or within an encrypted cookie. Most of the settings below are for when you've chosen to store the authenticity token within a cookie instead of the server's session store.

Name
Type
Default
Description

csrfStore

string

session

Which storage strategy to use for storing the CSRF authenticity token. Valid values are session or cookie. Choosing session requires no additional configuration. Choosing cookie for this requires additional configuration listed below.

csrfCookieEncryptionAlgorithm

string

AES

Encryption algorithm to use for encrypting the authenticity token cookie contents. This setting is ignored if you're using session storage. See your CF engine's documentation for the Encrypt() function for more information.

csrfCookieEncryptionSecretKey

string

Secret key used to encrypt the authenticity token cookie contents. This value must be configured to a string compatible with the csrfCookieEncryptionAlgorithmsetting if you're using cookie storage. This value is ignored if you're using session storage. See your CF engine's documentation for the Encrypt() function for more information.

csrfCookieEncryptionEncoding

string

Base64

Encoding to use to write the encrypted value to the cookie. This value is ignored if you're using session storage. See your CF engine's documentation for the Encrypt() function for more information.

csrfCookieName

string

_wheels_authenticity

The name of the cookie to be set to store CSRF token data. This value is ignored if you're using session storage.

csrfCookieDomain

string

Domain to set the cookie on. See your CF engine's documentation for cfcookie for more information.

csrfCookieEncodeValue

boolean

Whether or not to have CF encode the cookie. See your CF engine's documentation for cfcookie for more information.

csrfCookieHttpOnly

boolean

true

Whether or not they have CF set the cookie as HTTPOnly. See your CF engine's documentation for cfcookie for more information.

csrfCookiePath

string

/

Path to set the cookie on. See your CF engine's documentation for cfcookie for more information.

csrfCookiePreserveCase

boolean

Whether or not to preserve the case of the cookie's name. See your CF engine's documentation for cfcookie for more information.

csrfCookieSecure

boolean

Whether or not to only allow the cookie to be delivered over the HTTPS protocol. See your CF engine's documentation for cfcookie for more information.

CORS Protection Settings

Wheels includes built-in Cross-Origin Resource Sharing (CORS) which allows you to configure which cross-origin requests and methods are allowed. By default, this feature is turned off which will deny cross-origin requests at the browser level.

In this first version, the user can enable this feature, which will allow requests from all origins and all methods.

Name
Type
Default

allowCorsRequests

boolean

false

Miscellaneous Settings

Name
Type
Default
Description

disableEngineCheck

boolean

false

Set to true if you don't want Wheels to block you from using older CFML engines (such as ColdFusion 9, Railo etc).

enableMigratorComponent

boolean

true

Set to false to completely disable the migrator component which will prevent any Database migrations

enablePluginsComponent

boolean

true

Set to false to completely disable the plugins component which will prevent any plugin loading, and not load the entire plugins system

enablePublicComponent

boolean

true

Set to false to completely disable the public component which will disable the GUI even in development mode

Migrator Configuration Settings

Setting
Type
Default
Description

autoMigrateDatabase

Boolean

false

Automatically runs available migration on applicationstart.

migratorTableName

String

c_o_r_e_migrator_versions

The name of the table that stores the versions migrated.

createMigratorTable

Boolean

true

Create the c_o_r_e_migrator_versions database table.

writeMigratorSQLFiles

Boolean

false

Writes the executed SQL to a .sql file in the /app/migrator/sql directory.

migratorObjectCase

String

lower

Specifies the case of created database object. Options are 'lower', 'upper' and 'none' (which uses the given value unmodified)

allowMigrationDown

Boolean

false (true in development mode)

Prevents 'down' migrations (rollbacks)

wheels generate scaffold

Generate complete CRUD scaffolding for a resource including model, controller, views, tests, and migration.

Synopsis

wheels generate scaffold name
#can also be used as:
wheels g scaffold name

# With properties and options
wheels generate scaffold Product --properties="name:string,price:decimal"
wheels generate scaffold Comment --belongsTo=Product --api=true

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.

CommandBox Parameter Syntax

This command supports multiple parameter formats:

  • Positional parameters: wheels generate scaffold Product (resource name)

  • Named parameters: name=value (e.g., name=Product, properties="name:string,price:decimal")

  • Flag parameters: --flag equals flag=true (e.g., --api equals api=true)

  • Flag with value: --flag=value equals flag=value (e.g., --properties="name:string")

Parameter Mixing Rules:

✅ ALLOWED:

  • All positional: wheels generate scaffold Product

  • All named: name=Product properties="name:string"

  • Positional + flags: wheels generate scaffold Product --api --migrate

❌ NOT ALLOWED:

  • Positional + named: wheels generate scaffold Product properties="name:string" (causes error)

Recommendation: Use positional for resource name, flags for options: wheels generate scaffold Product --properties="name:string" --api --migrate

Note: Flag syntax (--flag) avoids positional/named parameter conflicts and is recommended for boolean options.

Arguments

Argument
Description
Default

name

Resource name (singular)

Required

Options

Option
Description
Example
Default

--properties

Model properties (format: name:type,name2:type2)

--properties="name:string,price:decimal"

--belongsTo

Parent model relationships (comma-separated)

--belongsTo=User,Category

--hasMany

Child model relationships (comma-separated)

--hasMany=orders,comments

--api

Generate API-only scaffold (no views)

--api=true or --api

false

--tests

Generate test files

--tests=false

true

--migrate

Run migrations after scaffolding

--migrate=true or --migrate

false

--force

Overwrite existing files

--force=true or --force

false

Examples

Basic scaffold

wheels generate scaffold Product

Scaffold with properties

wheels generate scaffold Product --properties="name:string,price:decimal,stock:integer"

Scaffold with associations

wheels scaffold Order --properties="total:decimal,status:string" \
  --belongsTo=User --hasMany=orderItems

API scaffold

wheels generate scaffold Product --api=true --properties="name:string,price:decimal"

Scaffold with auto-migration

wheels generate scaffold Category --properties="name:string" --migrate=true

Detailed Parameter Usage

Command Line Parameter Formats

Building on CommandBox's parameter syntax, Wheels scaffold generation supports:

1. Positional Parameters (Basic)

wheels generate scaffold Product          # Resource name (required)
wheels g scaffold User                    # Short alias with resource name

2. Named Parameters with Flags (Recommended)

wheels generate scaffold Product --properties="name:string,price:decimal"
wheels generate scaffold Comment --belongsTo=Product --properties="content:text"
wheels generate scaffold User --hasMany=posts,comments --api=true

3. Positional + Flags (Valid)

wheels generate scaffold Order --properties="total:decimal" --belongsTo=User --migrate
wheels g scaffold Product --properties="name:string" --api --tests=false --force

Parameter Validation Rules

Resource Name (Required)

  • Format: Singular noun (e.g., Product, User, Comment)

  • Conventions: PascalCase recommended

  • Examples: Product, OrderItem, UserProfile

Properties Parameter

  • Format: --properties="name:type,name2:type2,name3:type3"

  • Types: string, text, integer, decimal, boolean, date, datetime, time

  • Separator: Comma (,) between properties

  • Quotes: Always use quotes around the entire properties string

Association Parameters

  • belongsTo: --belongsTo=Model1,Model2 (comma-separated parent models)

  • hasMany: --hasMany=model1,model2 (comma-separated child models - lowercase plural)

  • Format: Model names in PascalCase for belongsTo, camelCase plural for hasMany

Boolean Parameters

  • Short flags: --api, --migrate, --force (equals true)

  • Explicit: --api=true, --tests=false, --migrate=true

  • Default values: api=false, tests=true, migrate=false, force=false

Parameter Examples by Type

String Parameters (Properties)

# Basic properties
wheels generate scaffold Product --properties="name:string,description:text"

# Complex properties with various types
wheels generate scaffold Order --properties="total:decimal,status:string,orderDate:datetime,shipped:boolean"

# Properties with foreign keys (use belongsTo instead)
wheels generate scaffold Comment --properties="content:text,rating:integer" --belongsTo=Product,User

Association Parameters

# Single associations
wheels generate scaffold Post --belongsTo=User
wheels generate scaffold User --hasMany=posts

# Multiple associations
wheels generate scaffold Order --belongsTo=User,ShippingAddress --hasMany=orderItems,payments
wheels generate scaffold Product --hasMany=reviews,orderItems,images

Boolean Flag Parameters

# API-only scaffold (no views)
wheels generate scaffold Product --api --properties="name:string,price:decimal"

# Skip tests generation
wheels generate scaffold Comment --tests=false --belongsTo=Post

# Force overwrite existing files
wheels generate scaffold User --force --properties="name:string,email:string"

# Auto-run migrations
wheels generate scaffold Category --migrate --properties="name:string,slug:string"

Combined Parameter Examples

# Complete e-commerce product
wheels generate scaffold Product \
  --properties="name:string,description:text,price:decimal,inStock:boolean,sku:string" \
  --belongsTo=Category \
  --hasMany=orderItems,reviews \
  --migrate

# Blog post with API
wheels generate scaffold Post \
  --properties="title:string,content:text,published:boolean,publishedAt:datetime" \
  --belongsTo=User \
  --hasMany=comments \
  --api=true \
  --migrate

# User profile with relationships
wheels generate scaffold User \
  --properties="firstName:string,lastName:string,email:string,active:boolean" \
  --hasMany=posts,comments,orders \
  --tests=true \
  --migrate

Common Parameter Mistakes

❌ Wrong property format:

wheels generate scaffold Product properties=name:string,price:decimal  # Missing --
wheels generate scaffold Product --properties=name string price decimal  # Wrong separator

❌ Wrong association format:

wheels generate scaffold Comment belongs-to=Product  # Wrong parameter name
wheels generate scaffold Order --belongsTo=user      # Should be User (PascalCase)
wheels generate scaffold User --hasMany=Posts        # Should be posts (camelCase plural)

❌ Wrong boolean format:

wheels generate scaffold Product api=true            # Missing --
wheels generate scaffold Product --api true          # Should be --api=true or --api

❌ Mixing positional and named parameters:

wheels generate scaffold Product properties="name:string"     # Positional + named (ERROR)
wheels generate scaffold name=Product --api=true             # Named + flag (inconsistent)

✅ Correct formats:

wheels generate scaffold Product --properties="name:string,price:decimal"  # Positional + flags
wheels generate scaffold Comment --belongsTo=Product                       # Positional + flags
wheels generate scaffold User --hasMany=posts,comments                     # Positional + flags
wheels generate scaffold Product --api=true    # or just --api             # Positional + flags

# OR all named parameters:
wheels generate scaffold name=Product properties="name:string"             # All named

Advanced Parameter Usage

Complex Data Types

# Different column types
wheels generate scaffold Event --properties="name:string,description:text,eventDate:date,startTime:time,duration:integer,price:decimal,active:boolean"

# Text fields for large content
wheels generate scaffold Article --properties="title:string,summary:text,content:text,publishedAt:datetime"

# Decimal fields with precision
wheels generate scaffold Product --properties="price:decimal,weight:decimal,dimensions:string"

Multi-level Associations

# Blog system
wheels generate scaffold User --hasMany=posts,comments
wheels generate scaffold Post --belongsTo=User --hasMany=comments
wheels generate scaffold Comment --belongsTo=User,Post

# E-commerce system
wheels generate scaffold Category --hasMany=products
wheels generate scaffold Product --belongsTo=Category --hasMany=orderItems,reviews
wheels generate scaffold Order --belongsTo=User --hasMany=orderItems
wheels generate scaffold OrderItem --belongsTo=Order,Product

API-First Development

# API-only resources
wheels generate scaffold ApiUser --api --properties="name:string,email:string,apiKey:string"
wheels generate scaffold ApiToken --api --belongsTo=ApiUser --properties="token:string,expiresAt:datetime"

# Mobile app backend
wheels generate scaffold MobileUser --api --properties="deviceId:string,pushToken:string"
wheels generate scaffold Notification --api --belongsTo=MobileUser --properties="message:text,sent:boolean"

Parameter Processing Details

Command Line Processing

  1. Resource Name: First positional argument, converted to proper case variants

  2. Properties: Parsed into individual property definitions with types

  3. Associations: Split by comma and processed into relationship configurations

  4. Boolean Flags: Converted to boolean values for scaffold options

  5. Validation: Checked for required parameters and valid formats

Internal Parameter Handling

  1. reconstructArgs(): Processes CommandBox parameter format

  2. validateScaffold(): Validates resource name and checks for conflicts

  3. generateScaffold(): Coordinates generation of all components

  4. Template Processing: Applies parameters to code generation templates

  5. File Creation: Creates model, controller, views, tests, and migration

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 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(columnNames="price", precision=10, scale=2);
            t.integer("stock");
            t.timestamps();
            t.create();
            
            addIndex(table="products", columnNames="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

Troubleshooting

Common Issues and Solutions

1. Parameter Syntax Errors

Issue: "Missing argument", "positional and named parameters", or parameter parsing errors

❌ Incorrect:

wheels generate scaffold Product properties=name:string,price:decimal  # Missing --
wheels generate scaffold Comment belongs-to=Product                    # Wrong parameter name
wheels generate scaffold User api=true                                 # Missing --
wheels generate scaffold Product properties="name:string"              # Positional + named (ERROR)

✅ Correct:

wheels generate scaffold Product --properties="name:string,price:decimal"  # Positional + flags
wheels generate scaffold Comment --belongsTo=Product                      # Positional + flags
wheels generate scaffold User --api=true                                  # Positional + flags
wheels generate scaffold name=Product properties="name:string"            # All named

Solution:

  • Use --flag=value format with proper parameter names and quotes for complex values

  • Never mix positional and named parameters - use either all named (name=value) or positional with flags (Product --flag)

2. Association Parameter Issues

Issue: Relationships not generated correctly

❌ Common Mistakes:

wheels generate scaffold Comment --belongsTo=product    # Lowercase, should be Product
wheels generate scaffold User --hasMany=Posts           # Should be posts (lowercase plural)
wheels generate scaffold Order --belongsTo=user,User    # Inconsistent case

✅ Correct Usage:

wheels generate scaffold Comment --belongsTo=Product    # PascalCase for belongsTo
wheels generate scaffold User --hasMany=posts           # camelCase plural for hasMany
wheels generate scaffold Order --belongsTo=User         # Consistent PascalCase

3. Properties Format Issues

Issue: Properties not parsed or generated incorrectly

❌ Problematic:

# Missing quotes around properties
wheels generate scaffold Product --properties=name:string,price:decimal

# Wrong separator
wheels generate scaffold Product --properties="name string, price decimal"

# Invalid property types
wheels generate scaffold Product --properties="name:varchar,price:money"

✅ Solutions:

# Always quote properties
wheels generate scaffold Product --properties="name:string,price:decimal"

# Use colon separator and comma between properties
wheels generate scaffold Product --properties="name:string,price:decimal,stock:integer"

# Use valid Wheels property types
wheels generate scaffold Product --properties="name:string,price:decimal,description:text,active:boolean"

4. File Generation Issues

Issue: Files not created or partially generated

Possible Causes:

  • Insufficient permissions in target directories

  • Existing files blocking generation (use --force)

  • Invalid resource names

  • Template processing errors

Solutions:

# Check directory permissions
ls -la app/models app/controllers app/views

# Force overwrite existing files
wheels generate scaffold Product --force --properties="name:string"

# Use valid resource names (singular, PascalCase)
wheels generate scaffold ProductCategory  # ✅ Good
wheels generate scaffold product-item     # ❌ Invalid characters

5. Migration Issues

Issue: Migrations not created or contain errors

Common Problems:

  • Properties with invalid SQL types

  • Association foreign keys missing

  • Migration syntax errors

Solutions:

# Check generated migration file
ls app/migrator/migrations/*create*

# Use valid property types that map to SQL
wheels generate scaffold Product --properties="name:string,price:decimal,inStock:boolean"

# For associations, foreign keys are auto-generated
wheels generate scaffold Comment --belongsTo=Product  # Creates productId foreign key

6. Route Integration Issues

Issue: Generated routes not working

Problem: Routes not added to routes.cfm or placed incorrectly

Solution:

<!-- Check routes.cfm for resources route -->
<cfscript>
mapper()
    .resources("products")    // Added by scaffold
    // CLI-Appends-Here
    .wildcard()
    .root(to="home##index")
.end();
</cfscript>

Validation and Testing

Pre-Generation Checklist

Before generating scaffolds:

# 1. Verify Wheels app directory
ls config/routes.cfm app/models app/controllers app/views

# 2. Check for naming conflicts
ls app/models/Product.cfc      # Should not exist (or use --force)
ls app/controllers/Products.cfc

# 3. Plan your associations
# Know which models depend on others

Post-Generation Validation

After scaffolding:

# 1. Check all files were created
ls app/models/Product.cfc
ls app/controllers/Products.cfc
ls app/views/products/
ls app/migrator/migrations/*products*

# 2. Run migrations
wheels dbmigrate up

# 3. Test the scaffold
# Start server and visit /products
server start

Testing Generated Code

# 1. Run generated tests
wheels test

# 2. Manual testing
# Visit each CRUD action:
# GET    /products        (index)
# GET    /products/new    (new)
# POST   /products        (create)
# GET    /products/1      (show)
# GET    /products/1/edit (edit)
# PUT    /products/1      (update)
# DELETE /products/1      (delete)

Error Reference

Common Error Messages

"Cannot scaffold '[name]':"

  • Cause: Resource name validation failed or conflicts exist

  • Solution: Use singular, valid identifier name and check for existing files

"Scaffolding failed!"

  • Cause: Template processing or file creation error

  • Solution: Check file permissions and template syntax

"Missing argument name"

  • Cause: Parameter syntax error in command

  • Solution: Use proper --flag=value format

Best Practices for Avoiding Issues

1. Parameter Planning

# Plan your scaffold before running
# 1. Resource name (singular, PascalCase)
# 2. Properties (all needed fields)
# 3. Associations (belongsTo and hasMany)
# 4. Options (api, migrate, tests)

# Example planning:
# Product: name:string, price:decimal, description:text, active:boolean
# Belongs to Category, has many OrderItems and Reviews
wheels generate scaffold Product \
  --properties="name:string,price:decimal,description:text,active:boolean" \
  --belongsTo=Category \
  --hasMany=orderItems,reviews \
  --migrate

2. Incremental Development

# Start simple, add complexity
# 1. Basic scaffold first
wheels generate scaffold Product --properties="name:string"

# 2. Add associations later if needed
# (modify generated files manually or re-scaffold with --force)

3. Template Customization

# Customize templates before scaffolding
# 1. Copy templates to app/snippets/
mkdir -p app/snippets/crud
cp /path/to/cli/templates/crud/* app/snippets/crud/

# 2. Modify templates to match project style
# 3. Run scaffold - uses custom templates

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

  8. Planning: Design your data model before scaffolding

  9. Incremental: Start simple, add complexity gradually

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 docker init

Initialize Docker configuration for your Wheels application with support for multiple databases, CF engines, production mode, and Nginx reverse proxy.

Synopsis

wheels docker init [options]

Description

The wheels docker init command creates Docker configuration files for containerizing your Wheels application. It generates a Dockerfile, docker-compose.yml, .dockerignore, configures datasources in CFConfig.json, and optionally creates Nginx configuration for reverse proxy support.

Options

Option
Description
Default
Valid Values

--db

Database system to use

mysql

h2, mysql, postgres, mssql, oracle

--dbVersion

Database version to use

varies by db

Any valid version for the selected database

--cfengine

CFML engine to use

lucee

lucee, adobe

--cfVersion

CFML engine version

6

Any valid version for the selected engine

--port

Custom application port (overrides server.json)

from server.json or 8080

Any valid port number

--force

Overwrite existing Docker files without confirmation

false

true, false

--production

Generate production-ready configuration

false

true, false

--nginx

Include Nginx reverse proxy

false

true, false

Default Database Versions:

  • MySQL: 8.0

  • PostgreSQL: 15

  • MSSQL: 2019-latest

  • Oracle: latest (Oracle Database 23c Free)

  • H2: embedded (no version needed)

Examples

Basic initialization (MySQL with Lucee 6)

wheels docker init

Initialize with PostgreSQL

wheels docker init --db=postgres

Initialize with specific database version

wheels docker init --db=postgres --dbVersion=13

Initialize with Adobe ColdFusion

wheels docker init --cfengine=adobe --cfVersion=2023

Initialize with H2 embedded database

wheels docker init --db=h2

Initialize with Oracle

wheels docker init --db=oracle

Initialize with specific Oracle version

wheels docker init --db=oracle --dbVersion=23-slim

Custom port

wheels docker init --port=3000

Force overwrite existing files

wheels docker init --force

Production configuration

wheels docker init --production

Production with Nginx reverse proxy

wheels docker init --production --nginx

Development with Nginx on custom port

wheels docker init --nginx --port=8080

Full custom configuration

wheels docker init --db=postgres --dbVersion=15 --cfengine=lucee --cfVersion=6 --port=8888 --production --nginx

What It Does

  1. Checks for existing files (unless --force is used):

    • Detects existing Dockerfile, docker-compose.yml, and .dockerignore

    • Prompts before overwriting existing Docker configuration

    • Lists all files that will be replaced

    • Allows cancellation to prevent accidental overwrites

  2. Creates Dockerfile optimized for CFML applications:

    • Development mode (default):

      • Hot-reload enabled

      • Development tools installed (curl, nano)

      • Source code mounted for live updates

      • BOX_INSTALL=TRUE for automatic dependency installation

    • Production mode (--production):

      • Security hardened with non-root user (UID 1001)

      • box install --production for optimized dependencies

      • Health checks configured (30s interval, 10s timeout, 3 retries)

      • Production environment variables set

      • No source volume mounts

      • Optimized image size

    • Based on ortussolutions/commandbox:latest

    • Installs H2 extension for Lucee if H2 database selected

    • Exposes application port (custom, from server.json, or defaults to 8080)

  3. Generates docker-compose.yml with:

    • Application service with port mapping or internal exposure

    • Database service (mysql, postgres, or mssql) if selected

    • Nginx reverse proxy service (if --nginx)

    • Environment variables for database connection

    • Volume mappings for data persistence

    • Development mode: Source code mounted for hot-reload, development command

    • Production mode: No source mounts, restart: always policies

  4. Creates Nginx configuration (if --nginx):

    • Reverse proxy to application backend

    • Load balancing ready upstream configuration

    • WebSocket support with upgrade headers

    • Static asset caching with 1-day expiration

    • Custom upload size limits (100MB)

    • Health check endpoint at /health

    • Production mode only:

      • Security headers (X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy)

      • Gzip compression for text content types

    • Port configuration:

      • Development: Nginx on port 8080

      • Production: Nginx on port 80

  5. Creates .dockerignore excluding:

    • .git and .gitignore

    • node_modules

    • .CommandBox

    • server.json

    • logs and *.log

    • tests

    • .env

    • Production mode only: README files, IDE configs (.vscode, .idea), .DS_Store

  6. Updates CFConfig.json:

    • Configures wheels-dev datasource for selected database

    • Sets up proper JDBC drivers and connection strings:

      • MySQL: com.mysql.cj.jdbc.Driver

      • PostgreSQL: org.postgresql.Driver

      • MSSQL: com.microsoft.sqlserver.jdbc.SQLServerDriver

      • Oracle: oracle.jdbc.OracleDriver

    • Uses Docker service name db for host resolution

    • Configures appropriate ports and credentials

  7. Updates server.json for Docker compatibility:

    • Sets web.host to 0.0.0.0 (required for Docker containers to accept external connections)

    • Sets openBrowser to false (Docker containers have no GUI)

    • Configures port from --port argument, existing server.json, or defaults to 8080

    • Sets CFEngine version (e.g., lucee@6 or adobe@2023)

    • Adds CFConfigFile reference if missing

Generated Files

Development Dockerfile

FROM ortussolutions/commandbox:latest

## Install curl and nano
RUN apt-get update && apt-get install -y curl nano

## Clean up the image
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

## Copy application files
COPY . /app
WORKDIR /app

## Install Dependencies
ENV BOX_INSTALL             TRUE

## Expose port
EXPOSE 8080

## Set Healthcheck URI
ENV HEALTHCHECK_URI         "http://127.0.0.1:8080/"

## Start the application
CMD ["box", "server", "start", "--console", "--force"]

Production Dockerfile

FROM ortussolutions/commandbox:latest

## Install required packages
RUN apt-get update && apt-get install -y curl nano && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

## Copy application files
COPY . /app
WORKDIR /app

## Install Dependencies
RUN box install --production

## Production optimizations
ENV ENVIRONMENT             production
ENV BOX_SERVER_PROFILE      production

## Security: Run as non-root user
RUN useradd -m -u 1001 appuser && \
    chown -R appuser:appuser /app
USER appuser

## Expose port
EXPOSE 8080

## Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
  CMD curl -f http://127.0.0.1:8080/ || exit 1

## Start the application
CMD ["box", "server", "start", "--console"]

docker-compose.yml (Development with MySQL)

version: "3.8"

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      ENVIRONMENT: development
      DB_HOST: db
      DB_PORT: 3306
      DB_NAME: wheels
      DB_USER: wheels
      DB_PASSWORD: wheels
    volumes:
      - .:/app
      - ../../../core/src/wheels:/app/vendor/wheels
      - ../../../docs:/app/vendor/wheels/docs
      - ../../../tests:/app/tests
    command: sh -c "box install && box server start --console --force"
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: wheels
      MYSQL_DATABASE: wheels
      MYSQL_USER: wheels
      MYSQL_PASSWORD: wheels
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

docker-compose.yml (Production with Nginx)

version: "3.8"

services:
  app:
    build: .
    expose:
      - 8080
    environment:
      ENVIRONMENT: production
      DB_HOST: db
      DB_PORT: 3306
      DB_NAME: wheels
      DB_USER: wheels
      DB_PASSWORD: wheels
    restart: always
    depends_on:
      - db

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    restart: always
    depends_on:
      - app

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: wheels
      MYSQL_DATABASE: wheels
      MYSQL_USER: wheels
      MYSQL_PASSWORD: wheels
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

nginx.conf (Generated with --nginx)

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # Performance
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;

    upstream app_backend {
        server app:8080;
    }

    server {
        listen 80;
        server_name _;

        # Max upload size
        client_max_body_size 100M;

        # Security headers (production only)
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Referrer-Policy "no-referrer-when-downgrade" always;

        # Gzip compression (production only)
        gzip on;
        gzip_vary on;
        gzip_min_length 1024;
        gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json;

        location / {
            proxy_pass http://app_backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            # WebSocket support
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";

            # Timeouts
            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
        }

        # Static assets caching
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
            proxy_pass http://app_backend;
            proxy_cache_valid 200 1d;
            expires 1d;
            add_header Cache-Control "public, immutable";
        }

        # Health check endpoint
        location /health {
            access_log off;
            proxy_pass http://app_backend;
        }
    }
}

Database Configurations

MySQL

  • Image: mysql:8.0 (default)

  • Port: 3306

  • Credentials: wheels/wheels

  • Database: wheels

  • Root Password: wheels

PostgreSQL

  • Image: postgres:15 (default)

  • Port: 5432

  • Credentials: wheels/wheels

  • Database: wheels

MSSQL

  • Image: mcr.microsoft.com/mssql/server:2019-latest (default)

  • Port: 1433

  • Credentials: sa/Wheels123!

  • Database: wheels

  • Note: Requires EULA acceptance

Oracle

  • Image: gvenzl/oracle-free:latest (default - Oracle Database 23c Free)

  • Port: 1521

  • Credentials: wheels/wheels

  • SID: FREE

  • Note: Uses lightweight Oracle Free container image

  • Available tags: latest, 23, 23-slim, 23-faststart, 23-slim-faststart

H2

  • Embedded: No separate container needed

  • Extension: Automatically added to Lucee deployments via Dockerfile

  • Connection: Configured in CFConfig.json

  • Storage: Within application container filesystem

Production vs Development Mode

Development Mode (default)

  • Source code mounted as volumes for hot-reload

  • Full development tools installed (curl, nano)

  • Debugging and verbose logging enabled

  • No restart policies

  • Direct port exposure (app accessible on configured port)

  • Development-friendly error messages

  • box install runs on container start with --force flag

  • Volume mounts for Wheels core, docs, and tests (framework development)

Production Mode (--production)

  • No source code volume mounts - code baked into image

  • Security hardened Dockerfile with non-root user (UID 1001)

  • Automatic restart policies (restart: always)

  • Health checks configured (30s interval, 10s timeout, 60s start period, 3 retries)

  • Optimized image size with box install --production

  • Production environment variable set

  • Additional .dockerignore exclusions (docs, IDE configs)

  • BOX_SERVER_PROFILE=production environment

  • No command override in docker-compose (uses CMD from Dockerfile)

Comparison Table

Feature
Development
Production

Source Volume Mount

✅ Yes

❌ No

Hot Reload

✅ Enabled

❌ Disabled

User

root

appuser (1001)

Restart Policy

none

always

Health Checks

❌ No

✅ Yes

Security Headers

❌ No

✅ Yes (with nginx)

Gzip Compression

❌ No

✅ Yes (with nginx)

Install Command

box install

box install --production

Image Size

Larger

Optimized

Nginx Reverse Proxy

When using --nginx, an Nginx reverse proxy is configured between clients and your application.

Benefits

  • Load balancing ready: Upstream configuration supports multiple app instances

  • SSL termination point: Add SSL certificates to nginx without app changes

  • Static asset caching: 1-day cache for images, CSS, JS

  • Security headers: Production mode adds security headers

  • Gzip compression: Production mode compresses responses

  • WebSocket support: Upgrade headers configured

  • Request buffering: Better handling of slow clients

  • Health checks: Dedicated /health endpoint

Port Configuration

  • Development mode: Nginx listens on port 8080, app exposed internally

  • Production mode: Nginx listens on port 80, app exposed internally

  • App is only accessible through nginx when --nginx is used

Nginx Configuration Details

The generated nginx.conf includes:

  • Upstream: app_backend pointing to app:8080 (Docker service)

  • Worker connections: 1024 concurrent connections

  • Client max body size: 100MB for file uploads

  • Proxy headers: Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto

  • WebSocket support: HTTP/1.1 with Upgrade and Connection headers

  • Timeouts: 60s for connect, send, and read operations

  • Static caching: 1 day expiration for assets

  • Health check: /health endpoint with no access logging

Production Security Headers (with --production --nginx)

X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: no-referrer-when-downgrade

Production Gzip Compression (with --production --nginx)

Enabled for:

  • text/plain

  • text/css

  • text/xml

  • text/javascript

  • application/x-javascript

  • application/xml+rss

  • application/json

server.json Docker Configuration

The command automatically updates your server.json with Docker-specific settings:

Before Docker Init

{
  "name": "myapp",
  "web": {
    "host": "localhost",
    "http": {
      "port": "8080"
    }
  },
  "openBrowser": true
}

After Docker Init

{
  "name": "myapp",
  "web": {
    "host": "0.0.0.0",
    "http": {
      "port": "8080"
    }
  },
  "openBrowser": false,
  "CFConfigFile": "CFConfig.json",
  "app": {
    "cfengine": "lucee@6"
  }
}

Why These Changes?

Setting
Value
Reason

web.host

0.0.0.0

Docker containers must bind to all interfaces to accept external connections. Using localhost or 127.0.0.1 prevents access from outside the container.

openBrowser

false

Docker containers run in headless mode with no GUI. Attempting to open a browser will fail and cause errors.

web.http.port

(from --port or existing)

Ensures the application port matches the Dockerfile EXPOSE and docker-compose port mapping.

CFConfigFile

CFConfig.json

Required for datasource configuration to work properly.

Port Priority

The port configuration follows this priority order:

  1. --port command argument (highest priority)

  2. Existing value in server.json

  3. Default: 8080 (lowest priority)

If you specify --port=9000, the command will:

  • Update server.json with port 9000

  • Configure Dockerfile to EXPOSE 9000

  • Set docker-compose port mapping to 9000:9000

Environment Variables

The following environment variables are configured in docker-compose.yml:

Variable
Description
Example

ENVIRONMENT

Application environment mode

development or production

BOX_SERVER_PROFILE

CommandBox server profile (production only)

production

DB_HOST

Database hostname (Docker service name)

db

DB_PORT

Database port

3306, 5432, 1433, 1521

DB_NAME

Database name

wheels

DB_USER

Database username

wheels or sa

DB_PASSWORD

Database password

wheels or Wheels123!

DB_SID

Oracle SID (Oracle only)

FREE

DB_TYPE

Database type (H2 only)

h2

Starting Your Docker Environment

After running wheels docker init, start your containers:

# Start in detached mode
docker-compose up -d

# Start with build (after code changes in production)
docker-compose up -d --build

# View logs
docker-compose logs -f

# View specific service logs
docker-compose logs -f app
docker-compose logs -f nginx

# Stop containers
docker-compose down

# Stop and remove volumes (WARNING: deletes database data)
docker-compose down -v

# Restart a specific service
docker-compose restart app

# Rebuild and restart
docker-compose up -d --build --force-recreate

Notes

  • Requires Docker and Docker Compose installed

  • Use --force to skip confirmation prompts when overwriting existing files

  • server.json is automatically configured for Docker compatibility:

    • web.host changed to 0.0.0.0 (required for Docker networking)

    • openBrowser set to false (no GUI in containers)

    • web.http.port updated to match --port or existing value

    • CFConfigFile added if missing

    • CF engine version set (e.g., lucee@6)

  • Custom --port overrides the port from server.json

  • Port priority: --port argument > server.json > default (8080)

  • Database passwords are set to defaults suitable for development only

  • Production deployments MUST use secrets management and secure passwords

  • Production mode creates security-hardened configurations with non-root users

  • Nginx adds reverse proxy capabilities for load balancing, caching, and security

  • H2 database runs embedded within the application container

  • Volume mounts in development assume Wheels framework development structure

  • When using --nginx, the app is only exposed internally to nginx

  • CFConfig.json is updated with datasource configuration (skipped for H2)

Troubleshooting

Port conflicts

Problem: Port already in use on host machine

Solutions:

  • Update the port in server.json before running wheels docker init

  • Use --port argument to specify a different port

  • Manually edit the ports in docker-compose.yml after generation

  • Stop the conflicting service: lsof -ti:8080 | xargs kill

Database connection issues

Problem: Application cannot connect to database

Solutions:

  • Ensure the database container is fully started: docker-compose logs db

  • Check for initialization errors in database logs

  • Verify CFConfig.json has correct datasource configuration

  • Confirm the wheels-dev datasource name matches your application config

  • For MSSQL, ensure password meets complexity requirements

  • Wait for database to fully initialize (can take 30-60 seconds first time)

Permission issues

Problem: Permission denied errors in production mode

Solutions:

  • The generated files have 777 permissions for development convenience

  • Production Dockerfile runs as appuser (UID 1001)

  • Ensure all files are readable by UID 1001

  • Check volume mount permissions on host

  • Adjust file permissions: chmod -R 755 /path/to/app

Nginx not routing requests

Problem: 502 Bad Gateway or connection refused

Solutions:

  • Verify app service is running: docker-compose ps

  • Check app service logs: docker-compose logs app

  • Ensure app is listening on the correct port internally

  • Verify nginx config syntax: docker-compose exec nginx nginx -t

  • Check nginx logs: docker-compose logs nginx

  • Restart services: docker-compose restart app nginx

Force overwrite not working

Problem: Still getting prompted despite using --force

Solutions:

  • Ensure correct syntax: wheels docker init --force (not --force=true)

  • Check for boolean parameter issues in command

  • Use the exact parameter name: --force

H2 database not working

Problem: H2 extension not loading or database errors

Solutions:

  • Verify H2 extension was added to Dockerfile

  • Check Lucee admin for H2 extension installation

  • Ensure CFConfig.json doesn't have conflicting datasource config

  • Check application logs for H2 initialization errors

  • H2 works with Lucee only (not Adobe ColdFusion)

Container not accessible from host

Problem: Cannot access the application at http://localhost:8080

Solutions:

  • Verify server.json has web.host set to 0.0.0.0 (not localhost or 127.0.0.1)

  • Check if port is already in use: lsof -ti:8080 (Unix) or netstat -ano | findstr :8080 (Windows)

  • Ensure docker-compose port mapping matches server.json port

  • Check container logs: docker-compose logs app

  • Restart containers: docker-compose restart app

  • If manually edited, run wheels docker init --force to regenerate proper configuration

Application attempts to open browser

Problem: Errors about browser opening or display issues

Solutions:

  • Verify server.json has openBrowser: false

  • Run wheels docker init --force to update server.json automatically

  • Manually set "openBrowser": false in server.json

  • Rebuild containers: docker-compose up -d --build

See Also

  • wheels docker deploy - Deploy using Docker

  • wheels deploy - General deployment commands

  • CommandBox Docker Images - Official CommandBox images

  • Docker Compose Documentation - Docker Compose reference

  • Nginx Documentation - Nginx configuration reference

Testing Your Application

A comprehensive guide to testing your Wheels application using TestBox 6...

This guide provides comprehensive instructions for testing your Wheels 3.0 application using TestBox 6. Wheels 3.0 now includes TestBox integration as an enabled option, moving beyond the legacy RocketUnit framework. TestBox is already included in your installation through box.json dependency management.

Overview

TestBox 6 is a next-generation testing framework for ColdFusion (CFML) based on BDD (Behavior Driven Development) and TDD (Test Driven Development), providing a clean, obvious syntax for writing tests. It serves as a comprehensive testing engine with multi-format output capabilities and database testing support.

For comprehensive TestBox documentation, refer to the official TestBox documentation.

TestBox Features

  • BDD style or xUnit style testing

  • Testing life-cycle methods

  • MockBox integration for mocking and stubbing

  • Extensible reporters (JSON, XML, JUnit XML, Text, Console, TAP, HTML)

  • Asynchronous testing

  • Multi-suite capabilities

  • Test skipping and labels

  • Code coverage via FusionReactor

For a complete list of features, see the TestBox Features documentation.

Project Directory Structure

Based on real Wheels 3.0 projects, your test structure should be organized as follows:

your-app/
├── app/
│   ├── controllers/
│   ├── models/
│   └── views/
├── config/
├── public/
├── tests/
│   ├── _assets/
│   ├── specs/
│   │   ├── controllers/
│   │   │   ├── ExampleControllerSpec.cfc
│   │   │   ├── PostControllerSpec.cfc
│   │   │   └── [Other Controller Tests]
│   │   └── functions/
│   │       └── ExampleSpec.cfc
│   ├── populate.cfm
│   ├── routes.cfm
│   └── runner.cfm

Note: By default, TestBox runs tests located in the tests/specs/ directory, unless configured otherwise.

TestBox Test Runner Configuration

Main Test Runner

Update tests/runner.cfm:

For detailed information on TestBox runners and configuration options, refer to the TestBox Runners documentation.

<!--- TestBox Test Runner for Wheels 3.0 --->
<cfsetting requestTimeOut="1800">
<cfscript>
    testBox = new testbox.system.TestBox(directory="tests.specs")

    setTestboxEnvironment()

    if (!structKeyExists(url, "format") || url.format eq "html") {
        result = testBox.run(
            reporter = "testbox.system.reports.JSONReporter"
        );
    }
    else if(url.format eq "json"){
        result = testBox.run(
            reporter = "testbox.system.reports.JSONReporter"
        );
        cfcontent(type="application/json");
        cfheader(name="Access-Control-Allow-Origin", value="*");
        DeJsonResult = DeserializeJSON(result);
        if (DeJsonResult.totalFail > 0 || DeJsonResult.totalError > 0) {
            cfheader(statuscode=417);
        } else {
            cfheader(statuscode=200);
        }
        // Check if 'only' parameter is provided in the URL
        if (structKeyExists(url, "only") && url.only eq "failure,error") {
            allBundles = DeJsonResult.bundleStats;
            if(DeJsonResult.totalFail > 0 || DeJsonResult.totalError > 0){  

                // Filter test results
                filteredBundles = [];
                
                for (bundle in DeJsonResult.bundleStats) {
                    if (bundle.totalError > 0 || bundle.totalFail > 0) {
                        filteredSuites = [];
                
                        for (suite in bundle.suiteStats) {
                            if (suite.totalError > 0 || suite.totalFail > 0) {
                                filteredSpecs = [];
                
                                for (spec in suite.specStats) {
                                    if (spec.status eq "Error" || spec.status eq "Failed") {
                                        arrayAppend(filteredSpecs, spec);
                                    }
                                }
                
                                if (arrayLen(filteredSpecs) > 0) {
                                    suite.specStats = filteredSpecs;
                                    arrayAppend(filteredSuites, suite);
                                }
                            }
                        }
                
                        if (arrayLen(filteredSuites) > 0) {
                            bundle.suiteStats = filteredSuites;
                            arrayAppend(filteredBundles, bundle);
                        }
                    }
                }
            
                DeJsonResult.bundleStats = filteredBundles;
                // Update the result with filtered data

                count = 1;
                for(bundle in allBundles){
                    writeOutput("Bundle: #bundle.name##Chr(13)##Chr(10)#")
                    writeOutput("CFML Engine: #DeJsonResult.CFMLEngine# #DeJsonResult.CFMLEngineVersion##Chr(13)##Chr(10)#")
                    writeOutput("Duration: #bundle.totalDuration#ms#Chr(13)##Chr(10)#")
                    writeOutput("Labels: #ArrayToList(DeJsonResult.labels, ', ')##Chr(13)##Chr(10)#")
                    writeOutput("╔═══════════════════════════════════════════════════════════╗#Chr(13)##Chr(10)#║ Suites  ║ Specs   ║ Passed  ║ Failed  ║ Errored ║ Skipped ║#Chr(13)##Chr(10)#╠═══════════════════════════════════════════════════════════╣#Chr(13)##Chr(10)#║ #NumberFormat(bundle.totalSuites,'999')#     ║ #NumberFormat(bundle.totalSpecs,'999')#     ║ #NumberFormat(bundle.totalPass,'999')#     ║ #NumberFormat(bundle.totalFail,'999')#     ║ #NumberFormat(bundle.totalError,'999')#     ║ #NumberFormat(bundle.totalSkipped,'999')#     ║#Chr(13)##Chr(10)#╚═══════════════════════════════════════════════════════════╝#Chr(13)##Chr(10)##Chr(13)##Chr(10)#")
                    if(bundle.totalFail > 0 || bundle.totalError > 0){
                        for(suite in DeJsonResult.bundleStats[count].suiteStats){
                            writeOutput("Suite with Error or Failure: #suite.name##Chr(13)##Chr(10)##Chr(13)##Chr(10)#")
                            for(spec in suite.specStats){
                                writeOutput("       Spec Name: #spec.name##Chr(13)##Chr(10)#")
                                writeOutput("       Error Message: #spec.failMessage##Chr(13)##Chr(10)#")
                                writeOutput("       Error Detail: #spec.failDetail##Chr(13)##Chr(10)##Chr(13)##Chr(10)##Chr(13)##Chr(10)#")
                            }
                        }
                        count += 1;
                    }
                    writeOutput("#Chr(13)##Chr(10)##Chr(13)##Chr(10)##Chr(13)##Chr(10)#")
                }
                
            }else{
                for(bundle in DeJsonResult.bundleStats){
                    writeOutput("Bundle: #bundle.name##Chr(13)##Chr(10)#")
                    writeOutput("CFML Engine: #DeJsonResult.CFMLEngine# #DeJsonResult.CFMLEngineVersion##Chr(13)##Chr(10)#")
                    writeOutput("Duration: #bundle.totalDuration#ms#Chr(13)##Chr(10)#")
                    writeOutput("Labels: #ArrayToList(DeJsonResult.labels, ', ')##Chr(13)##Chr(10)#")
                    writeOutput("╔═══════════════════════════════════════════════════════════╗#Chr(13)##Chr(10)#║ Suites  ║ Specs   ║ Passed  ║ Failed  ║ Errored ║ Skipped ║#Chr(13)##Chr(10)#╠═══════════════════════════════════════════════════════════╣#Chr(13)##Chr(10)#║ #NumberFormat(bundle.totalSuites,'999')#     ║ #NumberFormat(bundle.totalSpecs,'999')#     ║ #NumberFormat(bundle.totalPass,'999')#     ║ #NumberFormat(bundle.totalFail,'999')#     ║ #NumberFormat(bundle.totalError,'999')#     ║ #NumberFormat(bundle.totalSkipped,'999')#     ║#Chr(13)##Chr(10)#╚═══════════════════════════════════════════════════════════╝#Chr(13)##Chr(10)##Chr(13)##Chr(10)##Chr(13)##Chr(10)#")
                }
            }
        }else{
            writeOutput(result)
        }
    }
    else if (url.format eq "txt") {
        result = testBox.run(
            reporter = "testbox.system.reports.TextReporter"
        )        
        cfcontent(type="text/plain");
        writeOutput(result)
    }
    else if(url.format eq "junit"){
        result = testBox.run(
            reporter = "testbox.system.reports.ANTJUnitReporter"
        )
        cfcontent(type="text/xml");
        writeOutput(result)
    }
    // reset the original environment
    application.wheels = application.$$$wheels
    structDelete(application, "$$$wheels")
    if(!structKeyExists(url, "format") || url.format eq "html"){
        // Use our html template
        type = "App";
        include "/wheels/tests_testbox/html.cfm";
    }

    private function setTestboxEnvironment() {
        // creating backup for original environment
        application.$$$wheels = Duplicate(application.wheels)

        // load testbox routes
        application.wo.$include(template = "/tests/routes.cfm")
        application.wo.$setNamedRoutePositions()

        local.AssetPath = "/tests/_assets/"
        
        application.wo.set(rewriteFile = "index.cfm")
        application.wo.set(controllerPath = local.AssetPath & "controllers")
        application.wo.set(viewPath = local.AssetPath & "views")
        application.wo.set(modelPath = local.AssetPath & "models")
        application.wo.set(wheelsComponentPath = "/wheels")

        /* turn off default validations for testing */
        application.wheels.automaticValidations = false
        application.wheels.assetQueryString = false
        application.wheels.assetPaths = false

        /* redirections should always delay when testing */
        application.wheels.functions.redirectTo.delay = true

        /* turn off transactions by default */
        application.wheels.transactionMode = "none"

        /* turn off request query caching */
        application.wheels.cacheQueriesDuringRequest = false

        // CSRF
        application.wheels.csrfCookieName = "_wheels_test_authenticity"
        application.wheels.csrfCookieEncryptionAlgorithm = "AES"
        application.wheels.csrfCookieEncryptionSecretKey = GenerateSecretKey("AES")
        application.wheels.csrfCookieEncryptionEncoding = "Base64"

        // Setup CSRF token and cookie. The cookie can always be in place, even when the session-based CSRF storage is being
        // tested.
        dummyController = application.wo.controller("dummy")
        csrfToken = dummyController.$generateCookieAuthenticityToken()

        cookie[application.wheels.csrfCookieName] = Encrypt(
            SerializeJSON({authenticityToken = csrfToken}),
            application.wheels.csrfCookieEncryptionSecretKey,
            application.wheels.csrfCookieEncryptionAlgorithm,
            application.wheels.csrfCookieEncryptionEncoding
        )
        if(structKeyExists(url, "db") && listFind("mysql,sqlserver,postgres,h2", url.db)){
            application.wheels.dataSourceName = "wheelstestdb_" & url.db;
        } else if (application.wheels.coreTestDataSourceName eq "|datasourceName|") {
            application.wheels.dataSourceName = "wheelstestdb"; 
        } else {
            application.wheels.dataSourceName = application.wheels.coreTestDataSourceName;
        }
        application.testenv.db = application.wo.$dbinfo(datasource = application.wheels.dataSourceName, type = "version")

        local.populate = StructKeyExists(url, "populate") ? url.populate : true
        if (local.populate) {
            include "populate.cfm"
        }
    }
</cfscript>

Test Data Population

Update tests/populate.cfm for test database setup:

<cfscript>
    // Populate test database with sample data
    try {
        // Create test users
        testUser = model("User").create({
            username: "testuser",
            email: "[email protected]",
            password: "password123",
            firstName: "Test",
            lastName: "User"
        });
        
        // Create test blog posts
        testPost = model("Post").create({
            title: "Test Blog Post",
            content: "This is a test blog post content.",
            userId: testUser.id,
            published: true
        });
        
        // Create test community content
        testCommunityPost = model("CommunityPost").create({
            title: "Test Community Post",
            content: "Community test content",
            userId: testUser.id
        });
        
        writeOutput("Test data populated successfully.<br>");
        
    } catch (any e) {
        writeOutput("Error populating test data: " & e.message & "<br>");
    }
</cfscript>

Writing Controller Tests

TestBox 6 test bundles should extend wheels.Testbox and use BDD syntax with describe(), it(), and expect().

For comprehensive information on TestBox BDD syntax and expectations, see the TestBox BDD documentation and TestBox Expectations documentation.

Example Controller Testing

Create tests/specs/controllers/ExampleControllerSpec.cfc:

component extends="wheels.Testbox" {
    
    function beforeAll() {
        variables.baseUrl = "http://localhost:8080";
        variables.home = variables.baseUrl & "/";
    }
    
    function run() {
        describe("Front Page Functions Tests", () => {
            
            it("should return 200 status code for home page", () => {
                cfhttp(url=variables.home, method="GET", result="response");
                expect(response.status_code).toBe(200);
                expect(response.responseheader.status_code).toBe(200);
            });
            
            it("should contain expected home page content", () => {
                cfhttp(url=variables.home, method="GET", result="response");
                expect(response.filecontent).toInclude("<title>");
                expect(response.filecontent).toInclude("html");
            });
            
            it("should have proper content type", () => {
                cfhttp(url=variables.home, method="GET", result="response");
                expect(response.responseheader["Content-Type"]).toInclude("text/html");
            });
            
        });
    }
}

API Controller Testing

Create tests/specs/controllers/ApiControllerSpec.cfc:

component extends="wheels.Testbox" {
    
    function beforeAll() {
        variables.baseUrl = "http://localhost:8080";
        variables.apiUrl = variables.baseUrl & "/api";
    }
    
    function run() {
            
        describe("GET /api/users", () => {

            beforeEach(() => {
                // Set up API authentication if needed
                variables.headers = {
                    "Content-Type": "application/json",
                    "Accept": "application/json"
                };
            });
            
            it("should return JSON response with 200 status", () => {
                cfhttp(
                    url=variables.apiUrl & "/users",
                    method="GET",
                    result="response"
                ) {
                    cfhttpparam(type="header", name="Content-Type", value="application/json");
                    cfhttpparam(type="header", name="Accept", value="application/json");
                }
                
                expect(response.status_code).toBe(200);
                
                // Parse JSON response
                var jsonResponse = deserializeJSON(response.filecontent);
                expect(jsonResponse).toBeStruct();
                expect(jsonResponse).toHaveKey("data");
            });
            
        });
        
        describe("POST /api/users", () => {
            
            it("should create new user with valid data", () => {
                var userData = {
                    username: "apitest_#createUUID()#",
                    email: "[email protected]",
                    password: "password123"
                };
                
                cfhttp(
                    url=variables.apiUrl & "/users",
                    method="POST",
                    result="response"
                ) {
                    cfhttpparam(type="header", name="Content-Type", value="application/json");
                    cfhttpparam(type="body", value=serializeJSON(userData));
                }
                
                expect(response.status_code).toBe(201);
                
                var jsonResponse = deserializeJSON(response.filecontent);
                expect(jsonResponse.data.username).toBe(userData.username);
            });
            
        });
    }
}

Authentication Controller Testing

Create tests/specs/controllers/AuthenticationControllerSpec.cfc:

component extends="wheels.Testbox" {
    
    function beforeAll() {
        variables.baseUrl = "http://localhost:8080";
        variables.authUrl = variables.baseUrl & "/auth";
    }
    
    function run() {

		describe("Login Flow", () => {

			beforeEach(() => {
				// Create test user for authentication tests
				variables.testUser = {
					username: "authtest",
					email: "[email protected]",
					password: "password123"
				};
			});
			
			it("should display login page", () => {
				cfhttp(url=variables.authUrl & "/login", method="GET", result="response");
				expect(response.status_code).toBe(200);
				expect(response.filecontent).toInclude("login");
			});
			
			it("should authenticate valid user", () => {
				cfhttp(
					url=variables.authUrl & "/login",
					method="POST",
					result="response"
				) {
					cfhttpparam(type="formfield", name="username", value=variables.testUser.username);
					cfhttpparam(type="formfield", name="password", value=variables.testUser.password);
					cfhttpparam(type="formfield", name="csrf_token", value=session.csrf_token);
				}
				
				// Should redirect after successful login
				expect(response.status_code).toBe(302);
				expect(response.responseheader).toHaveKey("Location");
			});
			
			it("should reject invalid credentials", () => {
				cfhttp(
					url=variables.authUrl & "/login", 
					method="POST",
					result="response"
				) {
					cfhttpparam(type="formfield", name="username", value="invalid");
					cfhttpparam(type="formfield", name="password", value="invalid");
					cfhttpparam(type="formfield", name="csrf_token", value=session.csrf_token);
				}
				
				expect(response.status_code).toBe(200);
				expect(response.filecontent).toInclude("error");
			});
			
		});
            
		describe("Logout Flow", () => {
			
			it("should logout user successfully", () => {
				cfhttp(url=variables.authUrl & "/logout", method="POST", result="response") {
					cfhttpparam(type="formfield", name="csrf_token", value=session.csrf_token);
				}
				
				expect(response.status_code).toBe(302);
			});
			
		});
    }
}

Post Controller Testing

Create tests/specs/controllers/PostControllerSpec.cfc:

component extends="wheels.Testbox" {
    
    function beforeAll() {
        variables.baseUrl = "http://localhost:8080";
        variables.blogUrl = variables.baseUrl & "/blog";
    }
    
    function run() {
            
        describe("Blog Index", () => {
            
            it("should display blog posts list", () => {
                cfhttp(url=variables.blogUrl, method="GET", result="response");
                expect(response.status_code).toBe(200);
                expect(response.filecontent).toInclude("blog");
            });
            
        });
        
        describe("Individual Blog Post", () => {
            
            it("should display specific blog post", () => {
                cfhttp(url=variables.blogUrl & "/1", method="GET", result="response");
                // Either 200 (post exists) or 404 (post doesn't exist)
                expect([200, 404]).toInclude(response.status_code);
            });
            
        });
        
        describe("Blog Post Creation", () => {
            
            it("should create new blog post with valid data", () => {
                var postData = {
                    title: "Test Blog Post",
                    content: "This is test content for the blog post.",
                    published: true
                };
                
                cfhttp(
                    url=variables.blogUrl & "/create",
                    method="POST",
                    result="response"
                ) {
                    cfhttpparam(type="formfield", name="title", value=postData.title);
                    cfhttpparam(type="formfield", name="content", value=postData.content);
                    cfhttpparam(type="formfield", name="published", value=postData.published);
                    cfhttpparam(type="formfield", name="csrf_token", value=session.csrf_token);
                }
                
                // Should redirect after successful creation
                expect([201, 302]).toInclude(response.status_code);
            });
            
        });
        
    }
}

Writing Function Tests

For detailed information on testing functions and utility methods, refer to the TestBox Unit Testing documentation.

Example Function Testing

Create tests/specs/functions/ExampleSpec.cfc:

component extends="wheels.Testbox" {
    
    function run() {
            
        describe("String Helper Functions", () => {
            
            it("should strip spaces correctly", () => {
                var result = stripSpaces(" hello world ");
                expect(result).toBe("helloworld");
            });
            
            it("should format currency properly", () => {
                var result = formatCurrency(1234.56);
                expect(result).toInclude("$");
                expect(result).toInclude("1,234.56");
            });
            
        });
        
        describe("Date Helper Functions", () => {
            
            it("should format date correctly", () => {
                var testDate = createDate(2024, 1, 15);
                var result = formatDisplayDate(testDate);
                expect(result).toInclude("Jan");
                expect(result).toInclude("15");
                expect(result).toInclude("2024");
            });
            
        });
        
        describe("Validation Functions", () => {
            
            it("should validate email addresses", () => {
                expect(isValidEmail("[email protected]")).toBeTrue();
                expect(isValidEmail("invalid-email")).toBeFalse();
                expect(isValidEmail("")).toBeFalse();
            });
            
            it("should validate phone numbers", () => {
                expect(isValidPhone("(555) 123-4567")).toBeTrue();
                expect(isValidPhone("555-123-4567")).toBeTrue();
                expect(isValidPhone("invalid")).toBeFalse();
            });
            
        });
        
    }
}

Running Your Tests

Wheels 3.0 Test URL Structure

Wheels 3.0 provides convenient URL routing for both TestBox and legacy testing:

TestBox Testing URLs:

# Run your application TestBox tests
http://localhost:8080/wheels/app/tests

# Run Wheels core framework TestBox tests  
http://localhost:8080/wheels/core/tests

Legacy Testing URLs (RocketUnit):

# Run your application legacy tests
http://localhost:8080/wheels/legacy/app/tests

# Run Wheels core framework legacy tests
http://localhost:8080/wheels/legacy/core/tests

Web Interface Access

Access your TestBox tests through multiple formats:

# HTML Interface (default)
http://localhost:8080/wheels/app/tests

# JSON Output (for CI/CD)
http://localhost:8080/wheels/app/tests?format=json

# Plain Text Output
http://localhost:8080/wheels/app/tests?format=txt

For more information on running tests and available formats, see the TestBox Web Runner documentation.

Framework Core Testing

You can also run tests for the Wheels framework itself:

# Run Wheels core TestBox tests
http://localhost:8080/wheels/core/tests

# Run Wheels core legacy tests  
http://localhost:8080/wheels/legacy/core/tests

Advanced URL Parameters

Customize your test runs using the convenient URLs:

# Run specific test bundles
http://localhost:8080/wheels/app/tests?bundles=HomeControllerSpec,ApiControllerSpec

# Run tests with specific labels
http://localhost:8080/wheels/app/tests?labels=integration,api

# Exclude certain tests
http://localhost:8080/wheels/app/tests?excludes=slow,external

# Combine parameters
http://localhost:8080/wheels/app/tests?format=json&db=mysql&populate=true&bundles=ApiControllerSpec

Test Coverage Areas

Your test suite should provide comprehensive coverage for:

HTTP Response Testing

  • Status codes (200, 404, 500, 302, etc.)

  • Response headers (Content-Type, Location, etc.)

  • Response content validation

  • Redirect behavior

Controller Functionality

  • Page rendering and templates

  • Form processing and validation

  • Authentication and authorization

  • API endpoints and JSON responses

  • CRUD operations

  • Error handling

Database Operations

  • Model creation and updates

  • Data validation and constraints

  • Relationships and associations

  • Transaction handling

  • Data integrity

Security Features

  • CSRF token validation

  • Authentication flows

  • Authorization checks

  • Input sanitization

  • SQL injection prevention

Business Logic

  • Utility functions

  • Helper methods

  • Date and string formatting

  • Validation rules

  • Custom algorithms

For detailed guidance on what to test and testing strategies, see the TestBox Testing Code Coverage documentation.

Best Practices

For comprehensive testing best practices and advanced techniques, refer to the TestBox Testing documentation.

Naming Convention for Test Specs

Always name your test specs with Test or Spec in their filename, otherwise TestBox 6 won’t detect and run them.


Legacy RocketUnit Testing Framework

Note: This section documents the legacy RocketUnit testing framework used in earlier Wheels versions. For new Wheels 3.0 applications, use the TestBox approach documented above. This information is maintained for reference and migration purposes.

Legacy RocketUnit Overview

Prior to Wheels 3.0, the framework used RocketUnit as its testing infrastructure. RocketUnit was a comprehensive testing framework that provided both unit testing and integration testing capabilities specifically tailored for Wheels applications.

At some point, your code is going to break. Upgrades, feature enhancements, and bug fixes are all part of the development lifecycle. Quite often with deadlines, you don't have the time to test the functionality of your entire application with every change you make.

The problem is that today's fix could be tomorrow's bug. What if there were an automated way of checking if that change you're making is going to break something? That's where writing tests for your application can be invaluable.

Legacy RocketUnit Features

RocketUnit provided a complete testing solution with the following features:

  • Unit Testing: Testing individual functions and methods in isolation

  • Integration Testing: Testing complete request flows and component interactions

  • Assertion Library: Comprehensive set of assertion methods for validation

  • Test Lifecycle Management: Setup and teardown methods for test preparation

  • Package Organization: Hierarchical test organization and execution

  • Multiple Output Formats: HTML, text, and custom reporting formats

  • Database Testing Support: Built-in database seeding and cleanup capabilities

Legacy Directory Structure

The legacy RocketUnit testing framework used a specific directory structure within your Wheels application:

your-app/
├── tests/
│   ├── Test.cfc                 # Parent test component (extends wheels.Test)
│   ├── functions/               # Function-level unit tests
│   │   └── Example.cfc         # Example function tests
│   └── requests/               # Integration tests for request flows
│       └── Example.cfc         # Example request tests
└── wheels/
    ├── Test.cfc                # Core testing framework component
    └── test/
        └── functions.cfm       # Testing framework implementation

Legacy Test Components

1. tests/Test.cfc - Parent Test Component

This was the base component that all test components should extend:

component extends="wheels.Test" hint="I am the parent test." {
    
    function beforeAll() {
        // Executes once before the test suite runs
    }
    
    function setup() {
        // Executes before every test case
    }
    
    function teardown() {
        // Executes after every test case
    }
    
    function afterAll() {
        // Executes once after the test suite runs
    }
}

Key Features:

  • Lifecycle Methods: Provided complete test lifecycle management

  • Framework Integration: Extended wheels.Test for full framework integration

  • Documentation Support: Included section and category metadata

2. tests/functions/Example.cfc - Function Testing

Example test file for testing controller, model, and global functions:

component extends="app.tests.Test" hint="I am an example test for functions." {
    
    function packageSetup() {
        // Executes once before this package's first test case
    }
    
    function packageTeardown() {
        // Executes once after this package's last test case
    }
    
    function testExample() {
        // Example test with simple assertion
        assert('true');
    }
}

Key Features:

  • Package-level Lifecycle: Setup and teardown for entire test packages

  • Unit Testing Focus: Designed for testing individual functions in isolation

  • Simple Assertions: Used basic assert() statements for validation

3. tests/requests/Example.cfc - Integration Testing

Example test file for integration testing where controllers, models, and other components work together:

component extends="app.tests.Test" hint="I am an example test for requests." {
    
    function packageSetup() {
        // Setup for integration test package
    }
    
    function packageTeardown() {
        // Cleanup for integration test package
    }
    
    function testRequestFlow() {
        // Test complete request processing
        assert('true');
    }
}

Key Features:

  • Integration Testing: Tested complete request flows and component interactions

  • Request Context: Simulated full HTTP request/response cycles

  • Component Interaction: Validated how controllers, models, and views worked together

Legacy Core Testing Framework

wheels/Test.cfc - Core Testing Component

The core testing framework component provided all testing functionality:

component hint="I am the testing framework for Wheels applications." {
    
    public any function $runTest() {
        // Main test execution method
    }
    
    public void function assert(required any expression) {
        // Basic assertion method
    }
    
    public void function fail(string message = "") {
        // Explicit test failure
    }
    
    public any function debug(required any expression) {
        // Debug expression evaluation
    }
    
    public string function raised(required any expression) {
        // Error catching and type return
    }
}

wheels/test/functions.cfm - Framework Implementation

The comprehensive testing framework implementation included:

Assertion Functions:

  • assert(expression) - Evaluated expressions and recorded failures

  • fail(message) - Explicitly failed tests with custom messages

  • debug(expression) - Examined expressions during testing

  • raised(expression) - Caught and returned error types

Legacy Testing Philosophy and Structure

The RocketUnit framework followed these organizational principles:

Test Packages

Collections of test cases organized in directories, allowing for:

  • Logical grouping of related tests

  • Package-level setup and teardown

  • Hierarchical test execution

  • Modular test organization

Test Cases

Components containing multiple tests for specific functionality:

  • Focused on single areas of functionality

  • Contained multiple related test methods

  • Provided component-level lifecycle management

  • Extended the base Test component

Tests

Methods starting with "test" that contained assertions:

  • Named with descriptive test names (e.g., testUserValidation())

  • Contained one or more assertion statements

  • Focused on specific behaviors or outcomes

  • Used simple assertion syntax

Assertions

Statements that should evaluate to true for tests to pass:

  • assert(expression) - Basic true/false validation

  • fail(message) - Explicit test failure with custom message

  • debug(expression) - Expression evaluation for debugging

  • raised(expression) - Error handling and type checking

Legacy Test Execution and Management

Running Tests

The RocketUnit framework supported multiple execution patterns:

Entire Test Packages:

# Run all tests in functions package
http://localhost:8080/wheels/packages/app?package=functions

# Run all tests in requests package  
http://localhost:8080/wheels/packages/app?package=requests

Specific Test Cases:

# Run specific test component
http://localhost:8080/wheels/packages/app?package=functions&test=Example

# Run individual test method
http://localhost:8080/wheels/packages/app?package=functions&test=Example&method=testExample

With Filtering Options:

# Run tests with specific labels
http://localhost:8080/wheels/packages/app?labels=unit,fast

# Exclude slow tests
http://localhost:8080/wheels/packages/app?exclude=slow,integration

Test Types Supported

The framework supported different categories of tests:

Core Tests: Framework-level tests for Wheels components App Tests: Application-specific tests for your code Plugin Tests: Tests for Wheels plugins and extensions

Advanced Legacy Features

Database Seeding Capabilities:

  • Automatic test database population

  • Data cleanup between tests

  • Transaction-based test isolation

  • Custom seeding scripts

Test Environment Initialization:

  • Isolated test environment setup

  • Configuration override capabilities

  • Mock service integration

  • Resource allocation and cleanup

Debugging and Output Formatting:

  • Detailed test execution reports

  • Error stack traces and debugging info

  • Performance timing information

  • Custom output formatters

Error Handling and Reporting:

  • Comprehensive error capture

  • Test failure analysis

  • Exception type categorization

  • Detailed failure reporting

Legacy Usage Patterns

Test Organization Structure

tests/functions/     # Unit testing individual functions
tests/requests/     # Integration testing request flows
tests/models/       # Model-specific testing (optional)
tests/controllers/  # Controller-specific testing (optional)

Lifecycle Management Hierarchy

  1. Suite Level: beforeAll() and afterAll() for entire test suite

  2. Package Level: packageSetup() and packageTeardown() for test packages

  3. Test Case Level: setup() and teardown() for individual test cases

Example Legacy Test Structure

component extends="app.tests.Test" {
    
    function packageSetup() {
        variables.testData = {
            user: { username: "testuser", email: "[email protected]" }
        };
    }
    
    function setup() {
        variables.testUser = model("User").create(variables.testData.user);
    }
    
    function testUserCreation() {
        assert('StructKeyExists(variables, "testUser")');
        assert('variables.testUser.persisted()');
        assert('variables.testUser.username eq "testuser"');
    }
    
    function testUserValidation() {
        invalidUser = model("User").new({username: "", email: "invalid"});
        assert('NOT invalidUser.valid()');
        assert('invalidUser.hasErrors()');
    }
    
    function teardown() {
        if (StructKeyExists(variables, "testUser")) {
            variables.testUser.delete();
        }
    }
    
    function packageTeardown() {
        // Cleanup package-level resources
    }
}

Legacy Framework Integration

The RocketUnit framework was tightly integrated with the Wheels framework, providing:

Model Testing Integration:

  • Automatic database transaction management

  • Model validation testing helpers

  • Relationship testing utilities

  • Database seeding and cleanup

Controller Testing Integration:

  • Request simulation capabilities

  • Response validation helpers

  • Session and cookie management

  • Route testing functionality

View Testing Integration:

  • Template rendering validation

  • Output content verification

  • Helper function testing

  • Layout and partial testing

Migration from Legacy RocketUnit to TestBox

When migrating from the legacy RocketUnit system to TestBox 6, consider the following mapping:

Syntax Migration

  • assert(expression) → expect(result).toBeTrue()

  • fail(message) → fail(message) (same syntax)

  • debug(expression) → debug(expression) (same syntax)

  • Test methods → Wrapped in describe() and it() blocks

Structure Migration

  • tests/functions/ → tests/specs/functions/

  • tests/requests/ → tests/specs/controllers/

  • Component extensions change from app.tests.Test to wheels.Testbox

  • File names changed to include Spec or Test to align with TestBox 6 naming requirements

Lifecycle Migration

  • packageSetup() → beforeAll() in describe block

  • packageTeardown() → afterAll() in describe block

  • setup() → beforeEach() in describe block

  • teardown() → afterEach() in describe block

Legacy Reference and Historical Context

The RocketUnit framework served the Wheels community well for many years, providing a solid foundation for test-driven development in CFML applications. While TestBox now provides more modern BDD/TDD capabilities, understanding the legacy system helps with:

  • Migration Planning: Understanding existing test structures for conversion

  • Historical Context: Appreciating the evolution of CFML testing frameworks

  • Legacy Maintenance: Supporting older Wheels applications still using RocketUnit

  • Framework Archaeology: Understanding the testing heritage of the Wheels framework

The comprehensive testing infrastructure provided by RocketUnit established many of the testing patterns and practices that continue in the modern TestBox implementation, ensuring continuity and familiarity for developers transitioning between the frameworks.


This comprehensive testing approach ensures your Wheels 3.0 application is thoroughly validated across all components, provides multi-format output for different environments, and supports various database configurations for complete coverage while maintaining reference information for legacy RocketUnit systems.

Running Legacy RocketUnit Tests in Wheels 3.0

If you already have application-level tests written with RocketUnit in a Wheels 2.x app, you don’t need to rewrite them immediately when upgrading to Wheels 3.0. Wheels 3.0 still provides backward compatibility for running RocketUnit tests alongside TestBox. To run your existing RocketUnit tests:

  1. Copy the entire contents of that tests/ folder.

  2. Paste the copied folder into your Wheels 3.0 application under tests/RocketUnit/.

  3. Run the legacy tests by visiting the RocketUnit runner in your browser: http://localhost:8080/wheels/legacy/app/tests

This approach lets you continue running your legacy RocketUnit test suite unchanged inside a Wheels 3.0 project while you gradually migrate to TestBox. It’s particularly useful for teams upgrading large applications where a complete migration cannot be done in one step.