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

Loading...

Loading...

Working with Wheels

Loading...

Loading...

Loading...

Core Commands

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.

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.

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.

Encourage clean and pragmatic design.

  • Simplify saving data to a storage layer.

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

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

    Setting up Docker Development Environment

    Ensure you are in the application root directory.

    Initialize your Docker development environment:

    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

    db options:

    • mysql - MySQL database

    • postgres - PostgreSQL database

    • mssql - Microsoft SQL Server

    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

    Starting Your Development Environment

    After running the init command, start your containers:

    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

    Additional Configuration

    Custom Ports

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

    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:

    Database connection issues:

    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

    Major versions for BoxLang: 1

    h2 - H2 embedded database
  • oracle - Oracle database

  • sqlite - SQLite embedded database

  • CFConfig.json - CFML engine configuration with datasource setup

    box install wheels-cli
    wheels docker init cfengine=adobe cfversion=2018 db=mysql
    docker-compose up -d
    # 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
    {
        "name":"wheels",
        "web":{
            "host":"localhost",
            "http":{
                "port":3000
            },
            "webroot":"public",
            "rewrites":{
                "enable":true,
                "config":"public/urlrewrite.xml"
            }
        }
    }
    # 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
    # Check database container logs
    docker-compose logs db
    
    # Restart just the database
    docker-compose restart db

    Getting Started

    Install Wheels and get a local development server running

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

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

    Install CommandBox

    The first step is to get 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.

    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.

    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.

    CommandBox
    Chocolatey
    Homebrew
    Figure: Wheels congratulations screen

    wheels env switch

    Switch to a different environment in your Wheels application.

    Synopsis

    Description

    The wheels env switch command changes the active environment for your Wheels application 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

    Options

    Option
    Description
    Default

    Examples

    Switch to staging

    Switch with application restart

    Force switch without validation

    Switch with backup

    Quiet switch for scripts

    Combine multiple options

    What It Does

    1. Validates Target Environment (if --check is enabled):

      • Checks for .env.[environment] file

      • Checks for config/[environment]/settings.cfm file

    Output Example

    Environment File Updates

    .env File

    The command updates or creates the wheels_env variable:

    Before:

    After:

    If no environment variable exists, it adds:

    Validation Process

    The validation checks for required environment files:

    1. Environment Configuration File:

      • Checks: .env.[environment]

      • Location: Project root

    2. Wheels Settings File:

    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:

    --backup (default: false)

    Creates timestamped backups before switching:

    --restart (default: false)

    Automatically restarts the application:

    --force (default: false)

    Bypasses validation and confirmation prompts:

    --quiet (default: false)

    Minimal output for scripting:

    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:

    Error Handling

    If the switch fails:

    • Displays error message

    • Provides troubleshooting suggestions

    • Sets exit code 1 for scripting

    Example error output:

    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:

    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:

    Manual Restart

    If --restart is not used, you need to manually restart:

    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:

    Integration Examples

    CI/CD Pipeline

    Deployment Scripts

    Automated Testing

    Troubleshooting

    Switch Failed

    • Check validation errors in output

    • Verify .env.[environment] file exists

    • Verify config/[environment]/settings.cfm exists

    • Use --force

    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

    Security Considerations

    • Production switches require explicit confirmation

    • Backup files contain sensitive configuration

    • .env files should be in .gitignore

    • Use --quiet

    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

    Exit Codes

    • 0 - Success

    • 1 - Failure (validation error, write error, or user cancellation)

    See Also

    • - Environment management overview

    • - List available environments

    • - Setup new environments

    • - Show current environment

    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

    wheels env switch [name] [options]

    --quiet

    Suppress output

    false

    Validates environment configuration

  • Creates Backup (if --backup is enabled):

    • Backs up current .env file

    • Backs up server.json if it exists

    • Creates timestamped backup files

  • 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

  • Restarts Application (if --restart is enabled):

    • Stops and starts CommandBox server if server.json exists

    • Falls back to wheels reload command

  • Checks: config/[environment]/settings.cfm

  • Location: Project config directory

  • Execute wheels reload command

    to bypass validation
    Use --restart: Automatically restart to apply changes immediately
  • Document changes: Log environment switches in deployment notes

  • mode carefully in automated scripts
  • Review environment-specific configurations regularly

  • 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

  • wheels reload - Reload application

    name

    Target environment name

    Required

    --check

    Validate before switching

    true

    --restart

    Restart application after switch

    false

    --backup

    Backup current environment

    false

    --force

    Force switch even with issues

    false

    wheels env
    wheels env list
    wheels env setup
    wheels env current
    wheels env switch staging
    wheels env switch production --restart
    wheels env switch testing --force
    wheels env switch production --backup
    wheels env switch development --quiet
    wheels env switch production --backup --restart --check
    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
    wheels_env=development
    # or
    environment=development
    wheels_env=staging
    wheels_env=staging
    # With validation (default)
    wheels env switch production
    
    # Skip validation
    wheels env switch production --no-check
    wheels env switch production --backup
    # Creates: .env.backup-20240115-103045
    # Creates: server.json.backup-20240115-103045 (if exists)
    wheels env switch production --restart
    # Force switch even if validation fails
    wheels env switch production --force
    
    # Combine with other options
    wheels env switch production --force --no-check
    wheels env switch production --quiet
    # Output: Environment switched to production
    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):
    [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
    # Restore .env file
    cp .env.backup-20240115-103045 .env
    
    # Restore server.json
    cp server.json.backup-20240115-103045 server.json
    
    # Reload application
    wheels reload
    # CommandBox server
    server restart
    
    # Or Wheels development server
    wheels reload
    database=wheels_production
    debug=false
    cache=full
    - name: Switch to staging
      run: |
        wheels env switch staging --check
        wheels test run
        wheels deploy exec staging
    #!/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
    # 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
    Install TestBox CLI

    Synopsis

    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

    -

    Examples

    Run all tests

    Filter tests by pattern

    Run specific bundles

    Run tests with specific labels

    Generate coverage report

    Use different output format

    Run tests from specific directory

    Verbose output with coverage

    Run tests for different type

    Test Structure

    Standard test directory layout:

    Writing Tests

    Model Test Example

    Controller Test Example

    Test Configuration

    /tests/Application.cfc

    Reporters

    txt (Default)

    • Plain txt output

    • Good for CI systems

    • No colors

    JSON

    JUnit

    • JUnit XML format

    • For CI integration

    • Jenkins compatible

    Filtering Tests

    By Bundle

    By Label

    By Name Filter

    Benefits:

    • Faster execution

    • Better CPU utilization

    • Finds concurrency issues

    Code Coverage

    Generate coverage reports:

    View report:

    Test Helpers

    Create reusable test utilities:

    Database Strategies

    Transaction Rollback

    Database Cleaner

    Fixtures

    CI/CD Integration

    GitHub Actions

    Pre-commit Hook

    Common Issues

    Out of Memory

    Test Pollution

    • Use beforeEach/afterEach

    • Reset global state

    • Use transactions

    Flaky Tests

    • Avoid time-dependent tests

    • Mock external services

    • Use fixed test data

    See Also

    • wheels test - Run framework tests

    • wheels test coverage - Generate coverage

    • wheels test debug - Debug tests

    • wheels generate test - Generate test files

    box install testbox-cli --global
    wheels test run [spec] [options]
    wheels test run
    # Named parameter (recommended for string values)
    wheels test run filter="User"
    wheels test run filter="test_user_validation"
    # Named parameter (recommended)
    wheels test run bundles="tests.models"
    wheels test run bundles="tests.models,tests.controllers"
    # Named parameter (recommended)
    wheels test run labels="unit"
    wheels test run labels="critical,auth"
    # Flag (recommended for boolean)
    wheels test run --coverage
    
    # OR named
    wheels test run coverage=true
    # Named (recommended)
    wheels test run format=json
    wheels test run format=junit
    
    # OR flag with value
    wheels test run --format=json
    # Named parameters (recommended)
    wheels test run directory="tests/specs"
    wheels test run directory="tests/specs/unit" recurse=false
    # Flags + named (recommended)
    wheels test run --verbose --coverage format=txt
    
    # OR all named
    wheels test run verbose=true coverage=true format=txt
    # Named (recommended)
    wheels test run type=core
    wheels test run type=app
    /tests/
    ├── Application.cfc      # Test configuration
    ├── models/             # Model tests
    │   ├── UserTest.cfc
    │   └── ProductTest.cfc
    ├── controllers/        # Controller tests
    │   ├── UsersTest.cfc
    │   └── ProductsTest.cfc
    ├── views/             # View tests
    ├── integration/       # Integration tests
    └── helpers/          # Test helpers
    component extends="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");
                });
    
            });
        }
    
    }
    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");
                });
    
            });
        }
    
    }
    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 = {}
        };
    }
    wheels test run format=txt
    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       ║
    ╚═════════════════════════════════════════════════════════════════════╝
    wheels test run format=junit
    # Run only model tests
    wheels test run bundles=tests.models
    
    # Run multiple bundles
    wheels test run bundles=tests.models,tests.controllers
    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
    # Run tests matching pattern
    wheels test run filter="user"
    wheels test run filter="validate*"
    wheels test run --coverage coverageOutputDir=coverage/
    open coverage/index.html
    // /tests/helpers/TestHelper.cfc
    component {
    
        function createTestUser(struct overrides={}) {
            var defaults = {
                email: "test#CreateUUID()#@example.com",
                username: "user#CreateUUID()#",
                password: "testpass123"
            };
    
            return model("User").create(
                argumentCollection = defaults.append(arguments.overrides)
            );
        }
    
        function loginAs(required user) {
            session.userId = arguments.user.id;
            session.isAuthenticated = true;
        }
    
    }
    function beforeAll() {
        transaction action="begin";
    }
    
    function afterAll() {
        transaction action="rollback";
    }
    function beforeEach() {
        queryExecute("DELETE FROM users");
        queryExecute("DELETE FROM products");
    }
    function loadFixtures() {
        var users = deserializeJSON(
            fileRead("/tests/fixtures/users.json")
        );
    
        for (var userData in users) {
            model("User").create(userData);
        }
    }
    - name: Run tests
      run: |
        wheels test run  format=junit outputFile=test-results.xml
    
    - name: Upload results
      uses: actions/upload-artifact@v4
      with:
        name: test-results
        path: test-results.xml
    #!/bin/bash
    # .git/hooks/pre-commit
    
    echo "Running tests..."
    wheels test run labels=unit
    
    if [ $? -ne 0 ]; then
        echo "Tests failed. Commit aborted."
        exit 1
    fi
    # Increase memory
    box server set jvm.heapSize=1024
    box server restart

    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

    Environment Management

    Migration Commands

    Docker Commands

    Testing Commands

    Code Generation

    Asset Management

    CLI Development Guides

    Database Commands

    Config

    Plugins

    Code Analysis

    Get Commands

    Documentation

    Database Operations

    wheels generate route

    Generate route definitions for your Wheels application's /config/routes.cfm file.

    Synopsis

    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)

    Note: Flag syntax (--flag) avoids positional/named parameter conflicts and is recommended for boolean options.

    Arguments

    Argument
    Description
    Default

    Options

    Option
    Description
    Example
    Default

    Examples

    Resources Route (default)

    Generates in /config/routes.cfm:

    This creates all standard RESTful routes:

    • GET /products (index)

    • GET /products/new (new)

    • POST /products (create)

    GET Route

    Generates:

    POST Route

    Generates:

    PUT Route

    Generates:

    DELETE Route

    Generates:

    Root Route

    Generates:

    Explicit Resources Route

    Generates:

    Route Patterns

    Dynamic Segments

    Routes support dynamic segments using [key] notation:

    Generates:

    The [key] parameter will be available as params.key in your controller.

    Custom Parameters

    You can use any parameter name:

    Generates:

    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:

    Generates:

    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:

    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:

    Custom Route Helpers

    For custom routes, you'll need to manually create route names in your routes.cfm:

    Detailed Parameter Usage

    Command Line Parameter Formats

    Building on CommandBox's parameter syntax, Wheels route generation supports:

    1. Named Parameters (Recommended)

    2. Positional Parameters

    3. Mixed Parameters

    Parameter Validation Rules

    HTTP Method Parameters

    • Format: --method="pattern,handler" or --method="pattern"

    • Methods: get, post, put, patch, delete

    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

    String Parameters without Handlers

    Boolean Parameters

    Mixed Parameter Combinations

    Common Parameter Mistakes

    ❌ Missing equals sign:

    ❌ Missing quotes:

    ❌ Single hash instead of double:

    ❌ Missing objectname with resources:

    ✅ Correct formats:

    Advanced Parameter Usage

    Dynamic URL Segments

    API-Style Routes

    Namespace-Style Controllers

    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)

    Manual Route Organization

    After using the CLI, you may want to reorganize routes manually:

    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

    Troubleshooting

    Common Issues and Solutions

    1. Parameter Syntax Errors

    Issue: "Missing argument" errors when using HTTP method parameters

    ❌ Incorrect:

    ✅ Correct:

    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:

    ✅ Correct:

    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:

    4. Parameter Parsing Issues

    Issue: Complex patterns not parsed correctly

    ❌ Problematic:

    ✅ Solutions:

    5. Resources Route Issues

    Issue: Resources flag not working or missing objectname

    ❌ Common Mistakes:

    ✅ Correct Usage:

    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:

    Validation and Testing

    Pre-Generation Checklist

    Before generating routes, verify:

    Post-Generation Validation

    After generating routes, always:

    Testing Generated Routes

    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

    2. Route Planning

    3. Testing Strategy

    4. Documentation

    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

    See Also

    • - Generate complete CRUD with routes

    • - Generate controllers

    • - Generate models

    • - Complete routing documentation

    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

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

    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.

    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]
    wheels info
    wheels dbmigrate up

    Flag with value: --flag=value equals flag=value (e.g., --get="pattern,handler")

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

    GET /products/[key] (show)
  • GET /products/[key]/edit (edit)

  • PUT/PATCH /products/[key] (update)

  • DELETE /products/[key] (delete)

  • Separator: Comma (,) between pattern and handler

  • Quotes: Always use quotes around the value

  • Handler Format: controller##action (double hash required in CFML)

  • // CLI-Appends-Here (no tabs)
    : Specific routes before general ones
  • Test after generation: Visit URLs to ensure routes work

  • Reload application: Use ?reload=true after route changes

  • Reload frequently: Always reload after route changes

    objectname

    The name of the resource for resources route

    Optional (required for resources routes)

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

    wheels generate scaffold
    wheels generate controller
    wheels generate model
    Wheels Routing Guide

    wheels init
    wheels deps

    Automatically runs dbmigrate info after successful migration

  • Always backup your database before running migrations in production

  • wheels dbmigrate down
    wheels dbmigrate latest
    wheels dbmigrate info
    wheels dbmigrate reset
    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

    • security scan - The replacement command with enhanced features

    Deprecation Timeline

    • Deprecated: v1.5.0

    • Warning Added: v1.6.0

    • Removal Planned: v2.0.0

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

    wheels generate route products
    .resources("products")
    wheels generate route --get="products/sale,products##sale"
    .get(pattern="products/sale", to="products##sale")
    wheels generate route --post="api/users,api.users##create"
    .post(pattern="api/users", to="api.users##create")
    wheels generate route --put="users/[key]/activate,users##activate"
    .put(pattern="users/[key]/activate", to="users##activate")
    wheels generate route --delete="sessions,sessions##destroy"
    .delete(pattern="sessions", to="sessions##destroy")
    wheels generate route --root="pages##home"
    .root(to="pages##home", method="get")
    wheels generate route --resources=true users
    .resources("users")
    wheels generate route --get="users/[key]/profile,users##profile"
    .get(pattern="users/[key]/profile", to="users##profile")
    wheels generate route --get="posts/[year]/[month],posts##archive"
    .get(pattern="posts/[year]/[month]", to="posts##archive")
    wheels generate route --get="products/search"
    .get(pattern="products/search")
    <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>
    <!--- 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 -->
    <!--- 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")#
    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"
    wheels generate route products              # objectname (resources route)
    wheels g route users                        # Short alias with objectname
    wheels generate route users --resources=true
    wheels generate route --objectname=products --resources=true
    # 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"
    # Pattern-only routes (uses convention)
    wheels generate route --get="products/search"
    wheels generate route --post="newsletter/signup"
    # Resources flag (explicit true/false)
    wheels generate route --resources=true products
    wheels generate route --resources=false    # Invalid - needs objectname
    # 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"
    wheels generate route --get products/sale,products##sale
    wheels generate route --get=products/sale,products##sale
    wheels generate route --get="products/sale,products#sale"
    wheels generate route --resources=true        # No objectname
    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
    # 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
    # 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"
    # 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"
    <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>
    wheels generate route --get products/sale,products##sale    # Missing =
    wheels generate route --post contact send                   # Missing quotes and =
    wheels generate route --get="products/sale,products##sale"  # With = and quotes
    wheels generate route --post="contact,sessions##create"     # Proper format
    wheels generate route --get="users,users#show"   # Single # causes CFML errors
    wheels generate route --get="users,users##show"  # Double ## for CFML escaping
    # 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
    # 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"
    # 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"
    wheels generate route --resources=true              # Missing objectname
    wheels generate route --resources products          # Missing =true
    wheels generate route products --resource           # Wrong flag name
    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)
    <!-- 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>
    # 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
    # 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
    # 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
    # Always use consistent formatting
    wheels generate route --get="pattern,handler"    # ✅ Consistent
    wheels generate route --get pattern,handler      # ❌ Inconsistent
    # 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"
    # 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
    # Document custom routes in routes.cfm
    .get(name="productSearch", pattern="products/search", to="products##search")
    // Custom search endpoint for products - returns JSON
    ,--.   ,--.,--.                   ,--.            ,-----.,--.   ,--. 
    |  |   |  ||  ,---.  ,---.  ,---. |  | ,---.     '  .--./|  |   |  | 
    |  |.'.|  ||  .-.  || .-. :| .-. :|  |(  .-'     |  |    |  |   |  | 
    |   ,'.   ||  | |  |\   --.\   --.|  |.-'  `)    '  '--'\|  '--.|  | 
    '--'   '--'`--' `--' `----' `----'`--'`----'      `-----'`-----'`--' 
    ============================ 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 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 analyze security
    wheels security scan [path] [--fix] [--output=<format>] [--detailed]

    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:

    The shorthand method would look like:

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

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

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

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

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

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

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

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

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

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

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

    AJAX in Wheels Explained

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

    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 section of GitHub, and the Git repository is available at our .

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

    3. Setup the Database (Optional)

    Create a new database in MySQL, PostgreSQL, Microsoft SQL Server, Oracle, SQLite 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 function.

    4. Test It

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

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

    wheels dbmigrate reset

    Reset all database migrations by migrating to version 0.

    Synopsis

    Description

    The dbmigrate reset

    wheels dbmigrate exec

    Execute a specific database migration by version number.

    Synopsis

    Alias: wheels db exec

    wheels reload

    Reload the Wheels application in different modes.

    Synopsis

    Description

    The wheels reload

    Screencasts

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

    Wheels 2.x

    Create a basic CRUD interface in Wheels 2.x

    Create a basic JSON API in Wheels 2.x

    Routing in Wheels 2.x - Part 1

    Routing in Wheels 2.x - Part 2

    Introduction to Unit Testing in Wheels 2.x

    Unit Testing Controllers in Wheels 2.x

    wheels dbmigrate down

    Rollback the last executed database migration.

    Synopsis

    Alias: wheels db down

    Releases
    GitHub repo
    Requirements
    URL Rewriting
    Set()

    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

    https://youtu.be/K5HLItTru1g
    https://youtu.be/qZr5JzO0vo4
    https://youtu.be/BnPGApAvMVQ
    https://youtu.be/0CiGxJyJEIQ
    https://youtu.be/XgMuzzmBQ98
    https://youtu.be/cygj9WDqHjY
    javaScriptIncludeTag()
    provides()
    renderWith()
    provides()
    renderWith()
    provides()
    renderWith()
    Responding with Multiple Formats
    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

    The reload password configured in your application

    Empty

    Configuration

    Set the reload password in your Wheels settings.cfm:

    Reload Modes

    Development Mode

    • Enables debugging

    • Shows detailed error messages

    • Disables caching

    • Ideal for active development

    Testing Mode

    • Optimized for running tests

    • Consistent environment

    • Predictable caching

    Maintenance Mode

    • Shows maintenance page to users

    • Allows admin access

    • Useful for deployments

    Production Mode

    • Full caching enabled

    • Minimal error information

    • Optimized performance

    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

    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

    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

    • wheels dbmigrate up - Run the next migration

    • wheels dbmigrate reset - Reset all migrations

    • wheels dbmigrate info - View migration status

    • wheels dbmigrate exec - Run a specific migration

    /config/routes.cfm
    mapper()
      .get(name="sayHello", controller="say", action="hello")
    .end()
    /config/routes.cfm
    mapper()
      .get(name="sayHello", to="say##hello")
    .end()
    /app/views/say/hello.cfm
    <cfoutput>
    
    <!--- View code --->
    <h1></h1>
    <p></p>
    
    #linkTo(text="Alert me!", route="sayHello", id="alert-button")#
    
    </cfoutput>
    JavaScript
    (function($) {
        // Listen to the "click" event of the "alert-button" link and make an AJAX request
        $("#alert-button").on("click", function(event) {
            $.ajax({
                type: "GET",
                // References "/say/hello?format=json"
                url: $(this).attr("href") + "?format=json",
                dataType: "json",
                success: function(response) {
                    $("h1").html(response.message);
                    $("p").html(response.time);
                }
            });
    
            event.preventDefault();
        });
    })(jQuery);
    /app/controllers/Say.cfc
    component extends="Controller" {
        function config() {
            provides("html,json");
        }
    
        function hello() {
            // Prepare the message for the user.
            greeting = {};
            greeting["message"] = "Hi there";
            greeting["time"] = Now();
    
            // Respond to all requests with `renderWith()`.
            renderWith(greeting);
        }
    }
    wheels reload [options]
    wheels r [options]
    set(reloadPassword="mySecretPassword");
    wheels reload password=mypassword
    wheels reload mode=testing password=mypassword
    wheels reload mode=maintenance password=mypassword
    wheels reload mode=production password=mypassword
    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
    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

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

    Use Cases

    Fresh Development Database

    Start with a clean slate during development:

    Testing Migration Sequence

    Verify that all migrations run correctly from scratch:

    Fixing Migration Order Issues

    When migrations have dependency problems:

    Continuous Integration Setup

    Reset database for each 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

    • - Seed the database with data

    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

    Migrate to version 0 (revert all migrations)

    Use Cases

    Migrating to a Specific Version

    Move to any point in migration history:

    Rolling Back to Previous Version

    Move to an earlier migration state:

    Reset Database

    Clear all migrations:

    Important Considerations

    Migration Order

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

    Version Tracking

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

    Best Practices

    1. Check Dependencies: Ensure required migrations are already applied

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

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

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

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

    Version Number Format

    Migration versions are typically timestamps in the format:

    • YYYYMMDDHHmmss (e.g., 20240115123456)

    • Year: 2024

    • Month: 01

    • Day: 15

    • Hour: 12

    • Minute: 34

    • Second: 56

    Notes

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

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

    • The migration file must exist in the migrations directory

    • The command displays the migration progress message

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

    Related Commands

    • 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

    • - Create a 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.)

    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

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

    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

    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

    See Also

    • - Run all pending migrations

    • - Run next migration

    • - Rollback migration

    • - Create new migration

    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

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

    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

    wheels docs serve

    Serves generated documentation locally for development and review.

    Usage

    Parameters

    wheels dbmigrate reset
    wheels dbmigrate reset
    # Reset all migrations
    wheels dbmigrate reset
    
    # Re-run all migrations
    wheels dbmigrate latest
    
    # Seed with test data
    wheels db seed
    # Reset all migrations
    wheels dbmigrate reset
    
    # Run migrations one by one to test
    wheels dbmigrate up
    wheels dbmigrate up
    # ... continue as needed
    # Reset all migrations
    wheels dbmigrate reset
    
    # Manually fix migration files
    # Re-run all migrations
    wheels dbmigrate latest
    # CI script
    wheels dbmigrate reset
    wheels dbmigrate latest
    wheels test run
    wheels dbmigrate exec version=<version>
    wheels dbmigrate exec version=20240115123456
    wheels dbmigrate exec version=0
    # Check current status
    wheels dbmigrate info
    
    # Migrate to specific version
    wheels dbmigrate exec version=20240115123456
    # Check migration history
    wheels dbmigrate info
    
    # Go back to specific version
    wheels dbmigrate exec version=20240101000000
    # Migrate to version 0
    wheels dbmigrate exec version=0
    
    # Verify empty state
    wheels dbmigrate info
    wheels plugins info <name>
    wheels dbmigrate info
    wheels init
    wheels plugins list [--format=<format>] [--available]
    wheels db seed
    wheels dbmigrate create blank
    Author information
  • Description

  • Homepage URL

  • Repository URL

  • Documentation URL

  • Issues/Bugs URL

  • Keywords

  • Reads plugin's local box.json for detailed information
  • Display Local Information: If installed, shows all metadata from the plugin's box.json

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

  • Installation Commands: Shows appropriate commands based on installation status

  • name

    Yes

    string

    Name or slug of the plugin to inspect

    Current Version: The latest migration that has been run

  • Latest Version: The newest migration available

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

  • Identify pending migrations

  • Confirm database version

  • Use info to verify production deployments
    wheels dbmigrate latest
    wheels dbmigrate up
    wheels dbmigrate down
    wheels dbmigrate create blank

    Selected CF engine

  • Default port and settings

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

    • Application name

    • Wheels version dependency

    • Project metadata

  • wheels generate app
    wheels reload
    wheels info

    Show available plugins from ForgeBox

    ForgeBox Integration: Uses forgebox show type=cfwheels-plugins for available plugins
    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

  • format

    No

    string

    table, json

    table

    Output format for the plugin list

    available

    No

    boolean

    true, false

    false

    Parameter
    Description
    Default

    --root

    Root directory to serve

    docs/api

    --port

    Port to serve on

    35729

    --open

    Open browser automatically

    true

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

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

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

    SQLite (File-Based)

    • Use Case: Development, testing, prototyping, portable applications, embedded systems

    • Connection: No network port required (file-based, serverless)

    • Database Path: ./db/[database_name].db

    • Default Credentials

    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

    Environment Naming Conventions

    Recommended Names:

    • development or dev - Local development

    • testing or test - Automated testing

    • staging - Pre-production testing

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

    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

    Output Example

    Documentation Features

    Auto-generated Content

    • Class hierarchies and inheritance

    • Method signatures and parameters

    • Property types and defaults

    • Relationship diagrams

    Notes

    • Documentation is generated from code comments

    • Use consistent JavaDoc format for best results

    • Private methods are excluded by default

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

    Examples

    Basic destroy

    This will prompt this along with a confirmation:

    Safety Features

    1. Confirmation: 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

    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

    Important

    • 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

    Requirements

    What you need to know and have installed before you start programming in Wheels.

    We can identify 3 different types of requirements that you should be aware of:

    • Project Requirements. Is Wheels a good fit for your project?

    • Developer Requirements. Do you have the knowledge and mindset to program effectively in Wheels?

    • System Requirements. Is your server ready for Wheels?

    Project Requirements

    Before you start learning Wheels and making sure all the necessary software is installed on your computer, you really need to take a moment and think about the project you intend to use Wheels on. Is it a ten page website that won't be updated very often? Is it a space flight simulator program for NASA? Is it something in between?

    Most websites are, at their cores, simple data manipulation applications. You fetch a row, make some updates to it, stick it back in the database and so on. This is the "target market" for Wheels--simple CRUD (create, read, update, delete) website applications.

    A simple ten page website won't do much data manipulation, so you don't need Wheels for that (or even ColdFusion in some cases). A flight simulator program will do so much more than simple CRUD work, so in that case, Wheels is a poor match for you (and so perhaps, is ColdFusion).

    If your website falls somewhere in between these two extreme examples, then read on. If not, go look for another programming language and framework. ;)

    Another thing worth noting right off the bat (and one that ties in with the simple CRUD reasoning above) is that Wheels takes a very data-centric approach to the development process. What we mean by that is that it should be possible to visualize and implement the database design early on in the project's life cycle. So, if you're about to embark on a project with an extensive period of object oriented analysis and design which, as a last step almost, looks at how to persist objects, then you should probably also look for another framework.

    Still reading?

    Good!

    Moving on...

    Developer Requirements

    Yes, there are actually some things you should familiarize yourself with before starting to use Wheels. Don't worry though. You don't need to be an expert on any on of them. A basic understanding is good enough.

    • CFML. You should know CFML, the ColdFusion programming language. (Surprise!)

    • Object Oriented Programming. You should grasp the concept of object oriented programming and how it applies to CFML.

    • Model-View-Controller. You should know the theory behind the Model-View-Controller development pattern.

    CFML

    Simply the best web development language in the world! The best way to learn it, in our humble opinion, is to get the free developer edition of Adobe ColdFusion, buy Ben Forta's ColdFusion Web Application Construction Kit series, and start coding using your programming editor of choice. Remember it's not just the commercial Adobe offering that's available; offers an excellent open source alternative. Using is a great and simple way to get a local development environment of your choice up and running quickly.

    Object Oriented Programming (OOP)

    This is a programming methodology that uses constructs called objects to design applications. Objects model real world entities in your application. OOP is based on several techniques including inheritance, modularity, polymorphism, and encapsulation. Most of these techniques are supported in CFML, making it a fairly functional object oriented language. At the most basic level, a .cfc file in CFML is a class, and you create an instance of a class by using the CreateObject function or the <cfobject> tag.

    Trying to squeeze an explanation of object oriented programming and how it's used in CFML into a few sentences is impossible, and a detailed overview of it is outside the scope of this chapter. There is lots of high quality information online, so go ahead and Google it.

    Model-View-Controller

    Model-View-Controller, or MVC for short, is a way to structure your code so that it is broken down into 3 easy-to-manage pieces:

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

    • View. What the user sees and interacts with (a web page in our case).

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

    MVC is how Wheels structures your code for you. As you start working with Wheels applications, you'll see that most of the code you write is very nicely separated into one of these 3 categories.

    System Requirements

    Wheels requires that you use one of these CFML engines:

    • 2018 / 2021 / 2023 / 2025

    • 5.2.1.9+ / 6 / 7

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

    Don't worry though. Wheels will adopt to your setup and run just fine, but the URLs that it creates might differ a bit. You can read more about this in the chapter.

    Database Engines

    Finally, to build any kind of meaningful website application, you will likely interact with a database. These are the currently supported databases:

    • SQL Server 7+

    • MySQL 5+ *

    • PostgreSQL 8.4+

    • H2 1.4+

    MySQL

    • Wheels maybe incompatible with newer MySQL JDBC drivers. It is recommended you downgrade the driver to version 5.1.x for full ORM functionality.

    • If you're using MySQL 5.7.5+ you should be aware that the ONLY_FULL_GROUP_BY setting is enabled by default and it's currently not compatible with the Wheels ORM. However, you can work around this by either disabling the ONLY_FULL_GROUP_BY

    OK, hopefully this chapter didn't scare you too much. You can move on knowing that you have the basic knowledge needed, the software to run Wheels, and a suitable project to start with.

    wheels env setup environment=<name> [options]
    wheels docs generate [--output=<dir>] [--format=<format>]  [--include=<components>] [--serve] [--verbose]
    wheels destroy <name>
    wheels d <name>
    Oracle 19c+
  • SQLite 3.50+

  • setting or using
    ANY_VALUE()
    in a calculated property. You can read more about it
    .
  • We also recommend using the InnoDB engine if you want Transactions to work.

  • MySQL 4 is not supported.

  • Lucee
    CommandBox
    Adobe ColdFusion
    Lucee
    BoxLang
    CommandBox
    URL Rewriting
    here

    Server.json updates for environment-specific configurations

  • Updates config/environment.cfm with the current environment setting

  • Oracle SID (Oracle only)

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

    -

    sa
    , password=
    (empty)
  • Creation: Database file created on first connection (lazy creation)

  • : No username/password required (file-based authentication)
  • Creation: Database file created immediately (eager creation)

  • JDBC Driver: org.sqlite.JDBC (org.xerial.sqlite-jdbc v3.47.1.0) - included with Lucee/CommandBox

  • Auxiliary Files: Creates .db-wal, .db-shm, .db-journal during operation

  • Configuration: Uses absolute paths in datasource configuration

  • Advantages:

    • Zero configuration - no server setup required

    • Portable - single file database, easy to backup/move

    • Fast - ideal for local development and testing

    • Self-contained - all data in one file

    • Cross-platform - works on Windows, macOS, Linux

  • Limitations:

    • Single writer - not suitable for high-concurrency scenarios

    • File locking - can cause issues on network drives

    • Not recommended for production with multiple concurrent users

  • wheels[new_environment]
    )

    production or prod - Live environment

  • qa - Quality assurance

  • demo - Client demonstrations

  • environment

    Environment name (e.g., development, staging, production, testing)

    Yes

    --template

    Deployment template type

    local

    local, docker, vagrant

    --dbtype

    Database type

    h2

    h2, sqlite, mysql, postgres, mssql, oracle

    --database

    Custom database name

    wheels_[environment]

    wheels env list
    wheels env switch
    wheels env current
    Environment Configuration Guide

    Any valid database name

    Database schema
  • API endpoints

  • Database Schema: Tables, columns, indexes

  • Configuration: Settings and environment variables

  • Route mappings
  • Database ERD

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

  • name

    Name of the resource to destroy

    Yes

    wheels generate resource
    wheels generate scaffold
    wheels dbmigrate remove table
    # Create development environment with H2 database (default)
    wheels env setup environment=development
    
    # Create development environment with SQLite database (file-based, no server required)
    wheels env setup environment=development --dbtype=sqlite --database=myapp_dev
    
    # 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: development
    ## Generated on: 2025-01-18 12:30:00
    
    ## Application Settings
    WHEELS_ENV=development
    WHEELS_RELOAD_PASSWORD=wheelsdevelopment
    
    ## Database Settings
    DB_TYPE=sqlite
    DB_HOST=
    DB_PORT=
    DB_DATABASE=./db/myapp_dev.db
    DB_USER=
    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
    # Basic SQLite environment setup
    wheels env setup environment=dev --dbtype=sqlite --database=myapp_dev
    
    # SQLite with custom database name
    wheels env setup environment=test --dbtype=sqlite --database=integration_tests
    
    # SQLite for prototyping
    wheels env setup environment=prototype --dbtype=sqlite --database=prototype_v1
    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 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\
    # 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]
    # 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 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)

    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

    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

    Generated Files

    Database Migration

    File: app/migrator/migrations/[timestamp]_create_column__[tableName]_[columnName].cfc

    View Updates

    When views exist, the command adds the new property:

    Form View: Adds appropriate input field

    Index View: Adds column to table

    Show View: Adds property display

    Best Practices

    1. Run migrations immediately when prompted

    2. Use semantic property names (firstName, not fname)

    3. Set appropriate defaults for boolean and numeric fields

    4. Consider null constraints based on business logic

    See Also

    • - Generate models

    • - Create columns

    • - Generate tests

    wheels dbmigrate create table

    Generate a migration file for creating a new database table.

    Synopsis

    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)

    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

    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

    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

    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

    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

    Asset Commands

    wheels assets precompile

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

    Usage

    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

    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)

    Generated Manifest Example

    wheels assets clean

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

    Usage

    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

    wheels assets clobber

    Completely removes all compiled assets and the manifest file.

    Usage

    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

    Best Practices

    Production Deployment Workflow

    1. Before deployment:

    1. After deployment verification:

    Development Workflow

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

    Continuous Integration

    Example CI/CD pipeline step:

    File Structure

    After running wheels assets precompile:

    Configuration

    Configure asset handling in your Wheels application:

    Troubleshooting

    Assets not updating in production

    Disk space issues

    Missing assets after deployment

    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

    wheels dbmigrate remove table

    Generate a migration file for dropping a database table.

    Synopsis

    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

    Examples

    Basic table removal

    Remove user table

    Remove archive table

    Generated Migration Example

    For the command:

    Generates:

    Use Cases

    Removing Temporary Tables

    Clean up temporary or staging tables:

    Refactoring Database Schema

    Remove tables during schema refactoring:

    Cleaning Up Failed Features

    Remove tables from cancelled features:

    Archive Table Cleanup

    Remove old archive tables:

    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

    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:

    2. Backup Data First

    Before removing tables, create data backups:

    3. Staged Removal

    For production systems, consider staged removal:

    4. Check Dependencies

    Verify no active dependencies before removal:

    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:

    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

    • - Create tables

    • - Create custom migrations

    • - Run migrations

    • - Rollback migrations

    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:

    wheels dbmigrate latest

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

    Synopsis

    Alias: wheels db latest

    wheels analyze performance

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

    Usage

    Parameters

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

    wheels generate property name=<modelName> columnName=<propertyName> [options]
    
    #Can also be used as:
    wheels g property name=<modelName> columnName=<propertyName> [options]
    wheels dbmigrate create table name=<table_name> [--force] [--id] primaryKey=<key_name>
    wheels env list [options]
    wheels assets precompile
    wheels assets clean
    wheels assets clobber
    wheels dbmigrate remove table name=<table_name>

    Flag with value: --flag=value equals flag=value (e.g., --dataType=boolean)

    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

    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

    Updates Show View: Adds the property to show.cfm if it exists
  • Offers Migration: Prompts to run the migration immediately

  • Add one property at a time for better change tracking

    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

    biginteger

    BIGINT

    Large integer values

    binary

    BLOB

    Binary data

    boolean

    BOOLEAN

    Boolean (true/false) values

    date

    DATE

    wheels generate model
    wheels dbmigrate create column
    wheels generate test

    true

    Date only

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

    boolean

    No

    true

    Auto create ID column as autoincrement ID

    primaryKey

    string

    No

    "id"

    Overrides the default primary key column name

    wheels dbmigrate info - View migration status

    name

    string

    Yes

    -

    The name of the table to create

    --force

    boolean

    No

    false

    Force the creation of the table

    wheels dbmigrate create column
    wheels dbmigrate create blank
    wheels dbmigrate remove table
    wheels dbmigrate up

    --id

    --sort

    Sort by (name, type, modified)

    name

    --help

    Show help information

    Database:

    • Database name

    • Datasource name

  • Settings:

    • Debug mode

    • Cache settings

    • Custom configurations

  • Security: Don't expose production details

    --format

    Output format (table, json, yaml)

    table

    --verbose

    Show detailed configuration

    false

    --check

    Validate environment configurations

    false

    --filter

    Filter by environment type

    All

    wheels env
    wheels env setup
    wheels env switch
    wheels config list
    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

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

  • Dry run option: Preview deletions without making changes

    : Useful for fresh starts or troubleshooting
    Performance: Compiled assets significantly improve load times
  • Cache Busting: Hash fingerprints ensure browsers load updated assets

  • Application code

    wheels db schema - Export table schemas

    name

    string

    Yes

    The name of the table to remove

    wheels dbmigrate create table
    wheels dbmigrate create blank
    wheels dbmigrate up
    wheels dbmigrate down
    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

    • wheels dbmigrate info - Check migration status

    • wheels dbmigrate up - Run single migration

    • wheels dbmigrate down - Rollback migration

    • wheels dbmigrate create blank - Create migration

    Parameter
    Description
    Default

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

    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 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");
            }
        }
    }
    #textField(objectName="user", property="firstName")#
    <th>First Name</th>
    <td>#user.firstName#</td>
    <p><strong>First Name:</strong> #user.firstName#</p>
    # 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 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
    # 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
    {
      "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"
    }
    # 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
    # Remove all compiled assets (with confirmation prompt)
    wheels assets clobber
    
    # Skip confirmation prompt
    wheels assets clobber --force
    # Compile assets for production
    wheels assets precompile --environment=production
    
    # Clean old versions to save space
    wheels assets clean --keep=3
    # If rollback needed, previous versions are still available
    # If deployment successful, further cleanup can be done
    wheels assets clean --keep=2
    # Remove all compiled assets in development
    wheels assets clobber --force
    
    # Precompile only when testing production builds
    wheels assets precompile --environment=development
    # .github/workflows/deploy.yml
    - name: Compile Assets
      run: |
        wheels assets precompile --environment=production
        wheels assets clean --keep=3
    /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)
    // 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");
    # Force recompilation
    wheels assets precompile --force
    
    # Verify manifest exists and is current
    cat public/assets/compiled/manifest.json
    # 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
    # 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
    wheels dbmigrate remove table name=temp_import_data
    wheels dbmigrate remove table name=user
    wheels dbmigrate remove table name=orders_archive_2023
    wheels dbmigrate remove table name=product_archive
    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") { ... }
            }
        }
    
    }
    # Remove import staging table
    wheels dbmigrate remove table name=temp_customer_import
    
    # Remove data migration table
    wheels dbmigrate remove table name=migration_backup_20240115
    # Remove old table after data migration
    wheels dbmigrate remove table name=legacy_orders
    
    # Remove deprecated table
    wheels dbmigrate remove table name=user_preferences_old
    # Remove tables from abandoned feature
    wheels dbmigrate remove table name=beta_feature_data
    wheels dbmigrate remove table name=beta_feature_settings
    # Remove yearly archive tables
    wheels dbmigrate remove table name=orders_archive_2020
    wheels dbmigrate remove table name=orders_archive_2021
    # 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
    # First backup the data
    wheels db schema format=sql > backup_before_removal.sql
    
    # Then create removal migration
    wheels dbmigrate remove table name=user_preferences
    # 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
    -- 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%';
    # Export entire database schema
    wheels db schema format=sql --save file=schema_backup.sql
    
    # Then remove table
    wheels dbmigrate remove table name=user_preferences
    # 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");
        }
    }
    wheels dbmigrate down
    wheels dbmigrate down
    mysql myapp_production < backup.sql
    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);
        }
    }
    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
    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 analyze performance [--target=<target>] [--duration=<seconds>] [--report] [--threshold=<ms>] [--profile]
    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'
        }
    }
    • 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

    • wheels dbmigrate create table - Create a table migration

    • wheels dbmigrate create column - Create a column migration

    • wheels dbmigrate up - Run migrations

    • wheels dbmigrate down - Rollback migrations

    • - View migration status

    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.

    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:

    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:

    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:

    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.

    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.

    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.

    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:

    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.

    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.

    Getting Started
    Figure 1: Wheels congratulations screen

    wheels generate view

    Generate view files for controllers.

    Synopsis

    CommandBox Parameter Syntax

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

    Recommended: Use positional parameters: wheels generate view user show With template: wheels generate view user show crud/show

    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

    Options

    Option
    Description
    Default

    Template Options

    Available templates:

    • crud/_form - Form partial for new/edit views

    • crud/edit - Edit form view

    • crud/index - List/index view

    Examples

    Basic view (no template)

    Creates: /views/users/show.cfm with empty content

    View with CRUD template

    Creates: /views/users/show.cfm using the show template

    Edit form with template

    Creates: /views/users/edit.cfm using the edit template

    Form partial

    Creates: /views/users/_form.cfm using the form partial template

    Index view

    Creates: /views/products/index.cfm using the index template

    Force overwrite existing file

    Overwrites existing /views/users/show.cfm

    Generated Code Examples

    Without Template (blank file)

    With CRUD Index Template

    With CRUD New Template

    With CRUD Show Template

    With CRUD Form Partial Template

    Partial Views

    Partials Views start with underscore (_form.cfm, _item.cfm):

    Naming Convention

    Partials start with underscore:

    • _form.cfm - Form partial

    • _item.cfm - List item partial

    • _sidebar.cfm - Sidebar partial

    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 (partials start with _)

    Common Patterns

    Empty State

    Error Display

    See Also

    • - Generate controllers

    • - Generate complete CRUD

    • - Generate view tests

    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

    Drop SQLite Database

    Drop file-based SQLite database:

    Output:

    If server is running, it will automatically stop and retry:

    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

    H2

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

    • Shows which files were deleted

    • No server connection required

    SQLite

    • File-based deletion - removes database and auxiliary files

    • Deletes main database file (.db extension)

    • Automatically deletes auxiliary files:

      • .db-wal (Write-Ahead Log)

    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.

    "Database file is locked" (SQLite-specific)

    The SQLite database file cannot be deleted because it's currently in use.

    Fix:

    The command will automatically attempt to:

    1. Detect if the server is running

    2. Stop the server

    3. Retry deletion up to 5 times

    4. Wait 1 second between retries

    If the error persists:

    • Close any database tools (DB Browser for SQLite, etc.)

    • Check if another process has the file open

    • Wait a few seconds and try again

    "No SQLite database files found" (SQLite-specific)

    The SQLite database file doesn't exist at the expected location.

    This is normal if:

    • The database was already dropped

    • The database was never created

    • A different database path is configured

    No action needed.

    "File still locked after stopping server" (SQLite-specific)

    The database file remains locked even after stopping the server.

    Fix:

    1. Wait 10-30 seconds for Windows to release the file handle

    2. Close any open database management tools

    3. Check Task Manager for lingering processes

    4. Try the command again

    Related Commands

    • - Create a new database

    • - Drop and recreate database

    • - Backup before dropping

    wheels plugin update

    Update a Wheels plugin to the latest or a specified version from ForgeBox.

    Usage

    Parameters

    wheels plugin install

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

    Synopsis

    CommandBox Parameter Syntax

    wheels plugin remove

    Removes an installed Wheels CLI plugin.

    Synopsis

    CommandBox Parameter Syntax

    This command supports multiple parameter formats:

    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
            ");
        }
    }
    /app/controllers/Say.cfc
    component extends="Controller"{
    }
    /app/controllers/Say.cfc
    component extends="Controller" {
        function hello() {
        }
    }
    /app/views/say/hello.cfm
    <h1>Hello World!</h1>
    /app/controllers/Say.cfc
    component extends="Controller" {
        function hello() {
            time = Now();
        }
    }
    /app/views/say/hello.cfm
    <h1>Hello World!</h1>
    <p>Current time: <cfoutput>#time#</cfoutput></p>
    /app/controllers/Say.cfc
    component extends="Controller" {
        function hello() {
            time = Now();
        }
    
        function goodbye() {
        }
    }
    /app/views/say/goodbye.cfm
    Goodbye World!
    /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>
    /app/views/say/goodbye.cfm
    <h1>Goodbye World!</h1>
    <p>Time to say <cfoutput>#linkTo(text="hello", action="hello")#?</cfoutput></p>
    wheels generate view [objectName] [name] [template]
    wheels g view [objectName] [name] [template]
    wheels db drop [--datasource=<name>] [--environment=<env>] [--database=<dbname>] [--force]
    wheels dbmigrate info
    crud/new - New form view
  • crud/show - Show/detail view

  • Use semantic HTML markup

    objectName

    View path folder (e.g., user)

    Required

    name

    Name of the file to create (e.g., edit)

    Required

    template

    Optional template to use

    --force

    Overwrite existing code

    false

    wheels generate controller
    wheels scaffold
    wheels generate test

    force

    boolean

    false

    Skip the confirmation prompt. Useful for scripting.

    session variable for non-C## users
  • Important: Database names cannot contain hyphens (use underscores)

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

  • .db-shm (Shared Memory)

  • .db-journal (Rollback Journal)

  • Handles file locking automatically:

    • Detects if application server is running

    • Stops server automatically if file is locked

    • Retries deletion up to 5 times with 1-second delays

    • Provides clear error messages if deletion fails

  • Recommendation: Stop server before dropping: box server stop

  • No network connection required (file-based)

  • Clean output with minimal messages

  • 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

    wheels db create
    wheels db reset
    wheels db dump

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

    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

    Output:

    Plugin already up to date

    Output:

    Update to specific version

    Output:

    Force reinstall

    Output:

    Plugin not installed

    Output:

    Cannot reach ForgeBox

    Output (network error):

    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:

    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

    Plugin Not Installed

    ForgeBox Query Failed

    Installation Failed

    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

  • 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

    Force removal (skip confirmation)

    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)

    With force flag

    Plugin not installed

    Cancellation

    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

    # Positional
    wheels generate view user show
    
    # Named Parameters
    wheels g view objectName=user name=show
    wheels generate view user show crud/show
    wheels generate view user edit crud/edit
    wheels generate view user _form crud/_form
    wheels generate view product index crud/index
    wheels generate view user show --force
    <!--- View file created by wheels generate view --->
    <!--- Product Index --->
    <cfparam name="products">
    <cfoutput>
    	<h1>Products index</h1>
    	<p>#linkTo(route="newProduct", text="Create New Product", class="btn btn-default")#</p>
    
    	<cfif products.recordcount>
    		<table class="table">
    			<thead>
    				<tr>
    					<th>ID</th>
                        <!--- CLI-Appends-thead-Here --->
    					<th>Actions</th>
    				</tr>
    			</thead>
    			<tbody>
    				<cfloop query="products">
    				<tr>
    					<td>
    						#id#
    					</td>
                        <!--- CLI-Appends-tbody-Here --->
    					<td>
    						<div class="btn-group">
    							#linkTo(route="Product", key=id, text="View", class="btn btn-xs btn-info", encode=false)#
    							#linkTo(route="editProduct", key=id, text="Edit", class="btn btn-xs btn-primary", encode=false)#
    						</div>
    						#buttonTo(route="Product", method="delete", key=id, text="Delete", class="pull-right", inputClass="btn btn-danger btn-xs", encode=false)#
    					</td>
    				</tr>
    				</cfloop>
    			</tbody>
    		</table>
    	<cfelse>
    		<p>Sorry, there are no Products yet</p>
    	</cfif>
    </cfoutput>
    
    <!--- Product Creation Form --->
    <cfparam name="product">
    <cfoutput>
    <h1>Create New Product</h1>
    #errorMessagesFor("product")#
    #startFormTag(id="productNewForm", action="create")#
    	#includePartial("form")#
    	#submitTag(value="Create Product")#
    #endFormTag()#
    </cfoutput>
    <!--- Product Show --->
    <cfparam name="product">
    <cfoutput>
    <h1>View Product</h1>
    <!--- CLI-Appends-Here --->
    </cfoutput>
    <!--- Product Form Contents --->
    <cfoutput>
    |FormFields|
    <!--- CLI-Appends-Here --->
    </cfoutput>
    <!--- In layout or view --->
    #includePartial("/shared/header")#
    #includePartial("/products/form", product=product)#
    <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>
    <cfif product.hasErrors()>
        <div class="alert alert-danger">
            #errorMessagesFor("product")#
        </div>
    </cfif>
    # 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
    # Using existing datasource
    wheels db drop --datasource=myapp_dev
    
    # With confirmation prompt
    wheels db drop --datasource=sqlite_app
    [WARN] WARNING: This will permanently drop the database!
    Are you sure you want to drop the database 'D:\MyApp\db\myapp_dev.db'? Type 'yes' to confirm: yes
    
    [OK] SQLite database dropped successfully!
    [WARN] Server is running - stopping it to release database lock...
    [OK] SQLite database dropped successfully!
    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
    # Stop the server first
    box server stop
    
    # Wait a moment for file handles to release
    # Then try again
    wheels db drop
    wheels plugin update <name> [--version=<version>] [--force]
    wheels plugin update bcrypt
    ===========================================================
      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
    wheels plugin update bcrypt
    ===========================================================
      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
    wheels plugin update bcrypt --version=0.0.3
    ===========================================================
      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
    wheels plugin update bcrypt --force
    ===========================================================
      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
    wheels plugin update nonexistent-plugin
    ===========================================================
      Updating Plugin: nonexistent-plugin
    ===========================================================
    
    [ERROR] Plugin not found
    
    Plugin 'nonexistent-plugin' is not installed
    
    Install this plugin:
      wheels plugin install nonexistent-plugin
    wheels plugin update bcrypt
    ===========================================================
      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
    wheels plugin update bcrypt
    wheels plugin update cfwheels-bcrypt
    wheels plugin update "CFWheels Bcrypt"
    [ERROR] Plugins directory not found
    Plugin 'bcrypt' is not installed
    [ERROR] Plugin not found
    Plugin 'bcrypt' is not installed
    
    Install this plugin:
      wheels plugin install bcrypt
    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
    [ERROR] Error updating plugin
    Error: [error message]
    wheels plugins remove <name> [--force]
    wheels plugins remove wheels-vue-cli
    wheels plugins remove wheels-testing --force
    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
    [*] Removing plugin: wheels-vue-cli...
    
    [OK] Plugin removed successfully
    Run 'wheels plugins list' to see remaining plugins
    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
    Are you sure you want to remove the plugin 'wheels-vue-cli'? (y/n): n
    Plugin removal cancelled.
    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

    name

    Yes

    string

    Plugin name or slug from ForgeBox

    dev

    No

    boolean

    Install as development dependency (not used)

    version

    No

    string

    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

    • wheels plugin list - List installed plugins

    • wheels plugin info - View plugin details

    • wheels plugin update - Update a plugin

    • wheels plugin remove - Remove a plugin

    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

    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

    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

    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

    See Also

    • - Update a single plugin

    • - Check for outdated plugins

    • - List installed plugins

    • - View plugin details

    CLI Overview

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

    What is Wheels CLI?

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

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

    • Database Migrations - Manage database schema changes with version control

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

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

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

    • Plugin Management - Install and manage Wheels plugins

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

    Documentation Structure

    Complete reference for all CLI commands organized by category:

    • - Essential commands like init, reload.

    • - Generate applications, models, controllers, views

    • - Complete database management and migrations

    • - Run tests and generate coverage

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

    • Install Wheels CLI

    • Create your first application

    • Generate CRUD scaffolding

    • Run tests and migrations

    Guides

    Development Guides

    • - Understand the CLI's architecture

    • - Extend the CLI with your own commands

    • - Customize code generation templates

    • - Write and run tests effectively

    Best Practices

    • - Database migration best practices

    • - Security scanning and hardening

    • - Optimization techniques

    Reference

    • - All available configuration settings

    • - Variables available in templates

    • - Understanding command exit codes

    • - Environment configuration

    Key Features

    Code Generation

    Generate complete applications or individual components:

    Database Management

    Complete database lifecycle management:

    Testing

    Comprehensive testing support:

    Development Tools

    Enhance your development workflow:

    Getting Started

    1. Install CommandBox (if not already installed):

    2. Install Wheels CLI:

    3. Create Your First App:

    4. Explore Commands:

    Version Compatibility

    Wheels CLI
    Wheels
    CommandBox
    CFML Engine

    Community & Support

    • Documentation:

    • GitHub:

    • Slack: - #wheels channel

    • Forums:

    Contributing

    We welcome contributions! See our for details on:

    • Reporting issues

    • Suggesting features

    • Submitting pull requests

    • Creating custom commands

    Recent Updates

    Version 3.0.0

    • Modernized service architecture

    • Enhanced testing capabilities with watch mode

    • Security scanning and performance optimization

    • Plugin and environment management

    Quick Links

    • - Complete command reference

    • - Get started in minutes

    • - Extend the CLI

    • - Technical deep dive

    License

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


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

    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

    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)

    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)

    See Also

    • - Update a single plugin

    • - Update all plugins

    • - List installed plugins

    • - Show plugin details

    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

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

    Configure Custom Port in server.json

    Best Practices

    1. Use descriptive application names

    2. Choose appropriate template for project type

    3. Set secure reload password for production

    4. Configure datasource before starting

    Common Issues

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

    • Template not found: Check available templates with wheels info

    • Datasource errors: Configure database connection

    See Also

    • - Initialize existing application

    • - Interactive app creation

    • - Generate CRUD scaffolding

    Command Reference

    Complete reference for all Wheels CLI commands organized by category.

    Quick Reference

    Most Common Commands

    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.

    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.

    wheels analyze code

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

    Synopsis

    CommandBox Parameter Syntax

    This command supports multiple parameter formats:

    Directory Structure

    A comprehensive guide to the Wheels directory structure...

    Understanding the Wheels directory structure is essential whether you're building applications or 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.

    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]

    wheels plugins install <name> [--dev] [--version=<version>]
    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 update:all [--dryRun]
    wheels plugin outdated [--format=<format>]
    wheels generate app [name] [template] [directory] [options]
    wheels g app [name] [template] [directory] [options]

    Specific version to install (default: latest)

    Individual plugin update tracking
  • Helpful error reporting

  • : Shows color-coded status for each plugin
  • Update List: Displays table of plugins that need updating

  • Sequential Updates: Updates each plugin one at a time

  • Progress Tracking: Shows success/failure for each update

  • Summary Report: Displays final update statistics

  • Helpful Commands: Suggests next steps

  • : Check ForgeBox for breaking changes
  • Commit First: Commit your code before updating plugins

  • Update Individually: For critical plugins, use wheels plugin update <name>

  • 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

  • dryRun

    No

    boolean

    Preview updates without actually installing them

    wheels plugin update
    wheels plugin outdated
    wheels plugin list
    wheels plugin info

    Configuration - Manage application settings

  • And more...

  • Improved code generation with more options
  • Better error handling and user feedback

  • Comprehensive documentation

  • Testing Guide - Testing best practices

    3.0.x

    2.5+

    5.0+

    Lucee 5.3+, Adobe 2018+

    2.0.x

    2.0-2.4

    4.0+

    Lucee 5.2+, Adobe 2016+

    Command Reference
    Core Commands
    Code Generation
    Database Commands
    Testing Commands
    Quick Start Guide
    Service Architecture
    Creating Custom Commands
    Template System
    Testing Guide
    Migration Guide
    Security Guide
    Performance Guide
    Configuration Options
    Template Variables
    Exit Codes
    Environment Variables
    https://wheels.dev/docs
    https://github.com/wheels-dev/wheels
    CFML Slack
    https://groups.google.com/forum/#!forum/wheels
    Contributing Guide
    All Commands
    Quick Start
    Creating Commands
    Service Architecture
    LICENSE
    Quick Start Guide
    Command Reference

    Helpful update commands

    Display Results: Shows outdated plugins with current and latest versions
  • Update Suggestions: Provides appropriate update commands

  • 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

  • format

    No

    string

    table, json

    table

    Output format for outdated plugin list

    wheels plugin update
    wheels plugin update:all
    wheels plugin list
    wheels plugin info
    equals
    flag=true
    (e.g.,
    --useBootstrap
    equals
    useBootstrap=true
    )

    --setupH2

    Setup H2 embedded database

    true

    --init

    Initialize as CommandBox package

    false

    --force

    Overwrite existing directory

    false

    Run tests after generation
    Port conflicts: Change port in server.json

    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

    wheels init
    wheels generate app-wizard
    wheels scaffold
    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

    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:

    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:

    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.

    contributing
    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
    # Create new application
    wheels new blog
    
    # Generate complete CRUD scaffolding
    wheels scaffold name=post properties=title:string,content:text,published:boolean
    
    # Generate individual components
    wheels generate model user
    wheels generate controller users --rest
    wheels generate view users index
    # Database operations
    wheels db create              # Create database
    wheels db setup              # Create + migrate + seed
    wheels db reset              # Drop + recreate + migrate + seed
    wheels db shell              # Interactive database shell
    wheels db dump               # Backup database
    wheels db restore backup.sql # Restore from backup
    
    # Migrations
    wheels dbmigrate create table posts
    wheels dbmigrate latest
    wheels db status            # Check migration status
    wheels db rollback          # Rollback migrations
    # Run all tests
    wheels test run
    
    # Advanced testing with TestBox CLI
    wheels test:all              # Run all tests
    wheels test:unit             # Run unit tests only
    wheels test:integration      # Run integration tests only
    wheels test:watch            # Watch mode
    wheels test:coverage         # Generate coverage reports
    
    # Reload application
    wheels reload development
    
    # Analyze code
    wheels analyze code
    wheels security scan
    # macOS/Linux
    curl -fsSl https://downloads.ortussolutions.com/debs/gpg | sudo apt-key add -
    or
     brew install commandbox
    
    # Windows
    choco install commandbox
    box install wheels-cli
    wheels new myapp
    cd myapp
    box server start
    wheels --help
    wheels generate --help
    wheels dbmigrate --help
    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 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 + named (recommended)
    wheels generate app myapp --directory=./projects/
    # Positional + flag (recommended)
    wheels generate app portfolio --useBootstrap
    # Positional + flag (recommended)
    wheels generate app demo --setupH2
    # Positional + flags (recommended)
    wheels generate app enterprise --template=HelloDynamic --directory=./apps/ --reloadPassword=secret --datasourceName=enterprise_db --cfmlEngine=adobe --useBootstrap --setupH2
    myapp/
    ├── .gitignore              # Github gitignore file
    ├── box.json                # Dependencies
    ├── server.json             # Server configuration
    ├── README.md               # Description about application
    ├── config/
    │   ├── development/
    │   │   └── settings.cfm    # Environment specific settings
    │   ├── maintenance/
    │   │   └── settings.cfm    # Environment specific settings
    │   ├── production/
    │   │   └── settings.cfm    # Environment specific settings
    │   ├── testing/
    │   │   └── settings.cfm    # Environment specific settings
    │   ├── app.cfm             # App configuration
    │   ├── routes.cfm          # URL routes
    │   ├── environment.cfm     # Environment
    │   └── settings.cfm        # Framework settings
    ├── app/
    │   ├── controllers/
    │   │   └── Controller.cfc  # Default controller
    │   ├── events/             # Default event handlers
    │   ├── migrator/           # Contains migrations
    │   ├── models/
    │   │   └── Model.cfc       # Default model
    │   ├── snippets/
    │   ├── views/
    │   │   ├── helpers.cfm     # Default helpers
    │   │   └── layout.cfm       # Default layout
    ├── public/
    │   ├── files/
    │   ├── stylesheets/
    │   ├── javascripts/
    │   ├── images/
    │   ├── miscellaneous/
    │   ├── Application.cfc     # Application settings
    │   ├── index.cfm           # Home page
    │   └── urlrewrite.xml       
    ├── plugins/
    ├── tests/
    └── vendor/                 # Framework files
        ├── testbox/
        ├── wheels/
        └── wirebox/
    {
      "name": "myapp",
      "version": "1.0.0",
      "author": "Wheels Core Team and Community",
      "installPaths": {
        "wheels-core": "vendor/wheels/",
        "wirebox": "vendor/wirebox/",
        "testbox": "vendor/testbox/"
      },
      "dependencies": {
        "wheels-core": "3.0.0",
        "wirebox": "^7",
        "testbox": "^6",
      }
    }
    {
        "name":"myapp",
        "web":{
            "host":"localhost",
            "webroot":"public",
            "rewrites":{
                "enable":true,
                "config":"public/urlrewrite.xml"
            }
        },
        "app":{
            "cfengine":"lucee",
            "libDirs":"app/lib"
        }
    }
    {
      "web": {
        "http": {
          "enable":true,
          "port":"3000"
        }
      }
    }
    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
    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
    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
    Command
    Description

    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

    Core Commands

    Essential commands for managing your Wheels application.

    • wheels init - Bootstrap existing app for CLI Documentation

    • wheels info - Display version information Documentation

    • wheels reload - Reload application Documentation

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

    • wheels generate app-wizard - Interactive app creation Documentation

    • 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 helper (alias: wheels g helper) - Generate global helper functions

    • wheels generate migration (alias: wheels g migration) - Generate database migration

    • 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

    • wheels generate api-resource - Generate RESTful API resource

    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 Documentation

    • wheels db drop - Drop database Documentation

    Migration Management

    • wheels dbmigrate info - Show migration status Documentation

    • wheels dbmigrate latest - Run all pending migrations Documentation

    • wheels dbmigrate up - Run next migration Documentation

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

    • wheels dbmigrate create table [name] - Create table migration Documentation

    • wheels dbmigrate create column [table] [column] - Add column migration Documentation

    • wheels dbmigrate remove table [name] - Drop table migration

    Testing Commands

    Commands for running and managing tests.

    • wheels test run - Run tests Documentation

    • wheels test all - Run all tests Documentation

    • wheels test coverage - Run coverage tests Documentation

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

    • wheels env list - List environments Documentation

    • wheels env merge - Merge env files Documentation

    • wheels env set - Set env variable

    • wheels env show - Show env variables

    • wheels env switch - Switch between environments

    • wheels env validate - Validate environment configuration

    Code Analysis

    Commands for analyzing code quality and patterns.

    • wheels analyze code - Analyze code quality Documentation

    • wheels analyze performance - Performance analysis Documentation

    • wheels analyze security - Security analysis Documentation

    Config Commands

    Commands for managing application configuration.

    • wheels config check - Check configuration validity Documentation

    • wheels config diff - Compare configuration differences Documentation

    • wheels config dump - Dump current configuration Documentation

    Docker Commands

    Commands for Docker container management and deployment.

    • wheels docker init - Initialize Docker configuration Documentation

    Get Commands

    Commands for retrieving application information.

    • wheels get environment - Get current environment details Documentation

    • wheels get settings - Get application settings Documentation

    Documentation Commands

    Commands for generating and serving project documentation.

    • wheels docs generate - Generate project documentation Documentation

    • wheels docs serve - Serve documentation locally Documentation

    Plugin Commands

    Commands for managing Wheels plugins.

    • wheels plugin install - Install a plugin Documentation

    • wheels plugin list - List installed plugins Documentation

    • wheels plugin search - Search for plugins Documentation

    • wheels plugin info - Show plugin information

    • wheels plugin outdated - Check for outdated plugins

    • wheels plugin update - Update a plugin

    • wheels plugin update:all - Update all plugins

    • wheels plugin remove - Remove a plugin

    • wheels plugin init - Initialize new plugin

    Command Patterns

    Command Aliases

    Many commands have shorter aliases:

    Common Workflows

    Creating a new feature:

    Starting development:

    Deployment preparation:

    Environment Variables

    Variable
    Description
    Default

    WHEELS_ENV

    Environment mode

    development

    WHEELS_DATASOURCE

    Database name

    From config

    WHEELS_RELOAD_PASSWORD

    Reload password

    From config

    Exit Codes

    Code
    Description

    0

    Success

    1

    General error

    2

    Invalid arguments

    3

    File not found

    4

    Permission denied

    5

    Database error

    See Also

    • Quick Start Guide

    • CLI Development Guides

    • Service Architecture

    • Migrations Guide

    Basic Usage

    This displays the current environment's configuration in a formatted table in the console.

    Command Syntax

    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)

    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:

    Environment Variables Format (.env)

    Exports as environment variables compatible with .env files:

    CFML Format

    Exports as Wheels set() statements:

    Common Use Cases

    1. Quick Configuration Review

    2. Backup Configuration

    3. Environment Comparison

    4. Generate Environment Files

    5. Migration Between Servers

    6. Generate CFML Settings

    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

    Examples

    Basic Examples

    Advanced Examples

    Pipeline Integration

    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

    • 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

    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:

    Analyze specific directory

    Analyze specific file

    Auto-fix issues

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

    Generate HTML report

    Creates a detailed HTML report with visualizations:

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

    Analyze with JSON output for CI/CD

    JUnit format for CI integration

    Check only errors (skip warnings and info)

    Verbose mode with progress indicators

    Comprehensive analysis with all options

    Output Format

    Console Output (Default)

    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:

    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

    GitLab CI

    Jenkins

    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

    .

    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

    users

    edit

    12

    users

    new

    users

    index

    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 Routing 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 Routing 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 Routing 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 renderView() function and read the Pages 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 Layouts and documentation for the renderView 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 Configuration and Defaults 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 table() function and the chapter on Object Relational Mapping.

    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 property() function and the Object Relational Mapping 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 Configuration and Defaults chapter.

    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

    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

    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:

    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

    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

    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

    wheels dbmigrate create column

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

    Synopsis

    CommandBox Parameter Syntax

    This command supports multiple parameter formats:

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

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

    Parameter Mixing Rules:

    ALLOWED:

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

    • 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=email tableName=users dataType=string 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

    Note: Parameters are listed in the order they appear in the command signature.

    Parameter
    Type
    Required
    Default
    Description

    Column Types

    • string - VARCHAR(255)

    • text - TEXT/CLOB

    • integer - INTEGER

    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

    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

    Install Wheels CLI

    Creating Your First Application

    1. Generate Application

    This creates a new Wheels application with:

    • Complete directory structure

    • Configuration files

    • Sample code

    2. Configure Database

    Edit /config/settings.cfm:

    Or use H2 embedded database:

    Create the database:

    3. Start Server

    Visit http://localhost:3000

    Creating Your First Feature

    Let's create a blog post feature:

    1. Generate Scaffold

    This generates:

    • Model with validations

    • Controller with CRUD actions

    • Views for all actions

    • Database migration

    2. Run Migration

    3. Add Routes

    Edit /config/routes.cfm:

    4. Reload Application

    5. Test Your Feature

    Visit http://localhost:3000/posts

    You now have a fully functional blog post management system!

    Development Workflow

    Running Tests

    Adding Relationships

    Let's add comments to posts:

    Common Tasks

    Adding Authentication

    Adding API Endpoints

    Working with Views

    Best Practices

    1. Use Migrations

    Always use migrations for database changes:

    2. Write Tests

    Generate tests for your code:

    3. Use Environment Configuration

    4. Version Control

    Add to .gitignore:

    Debugging

    Check Logs

    Enable Debug Mode

    In /config/settings.cfm:

    Common Issues

    Port already in use:

    Database connection failed:

    Migration failed:

    Need to reset database:

    Access database directly:

    Next Steps

    1. Read the Guides:

    Example: Complete Blog Application

    Here's a complete blog setup:

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

    wheels generate scaffold

    Generate complete CRUD scaffolding for a resource including model, controller, views, tests, and migration.

    Synopsis

    Description

    The wheels scaffold

    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.

    wheels deps

    Manage application dependencies using box.json.

    Synopsis

    Description

    The wheels deps

    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

    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.

    wheels g controller users      # Same as: wheels generate controller users
    wheels g model user           # Same as: wheels generate model user
    wheels g helper format        # Same as: wheels generate helper format
    wheels g migration CreateUsers # Same as: wheels generate migration CreateUsers
    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 config dump
    wheels config dump [environment] [--format=<type>] [--output=<file>] [--noMask]
    {
      "datasource": "myapp",
      "cacheQueries": false,
      "environment": "development",
      "_environment": {
        "WHEELS_ENV": "development"
      }
    }
    ## Application Settings
    DATASOURCE=myapp
    CACHE_QUERIES=false
    ENVIRONMENT=development
    // Wheels Configuration Export
    set(datasource = "myapp");
    set(cacheQueries = false);
    set(environment = "development");
    # View current configuration
    wheels config dump
    
    # View production configuration
    wheels config dump production
    # 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"
    # 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
    # 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
    # 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
    # Create settings file for another environment
    wheels config dump --format=cfml --output=config/staging/settings.cfm
    # 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
    # View current configuration
    wheels config dump
    
    # View testing environment configuration
    wheels config dump testing
    
    # Export as JSON to console
    wheels config dump --format=json
    # 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"
    # 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
    wheels analyze code [--path=<path>] [--fix] [--format=<format>] [--severity=<severity>] [--report] [--verbose]
    wheels analyze code
    # Flag syntax (recommended)
    wheels analyze code --path=app/controllers
    
    # OR named
    wheels analyze code path=app/controllers
    # Flag syntax (recommended)
    wheels analyze code --path=app/models/User.cfc
    
    # OR named
    wheels analyze code path=app/models/User.cfc
    wheels analyze code --fix
    wheels analyze code --report
    wheels analyze code --format=json
    wheels analyze code --format=junit
    wheels analyze code --severity=error
    wheels analyze code --verbose
    wheels analyze code --path=app/models --fix --report --verbose
    ==================================================
               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...]
    {
      "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/"
      ]
    }
    - name: Code Analysis
      run: |
        wheels analyze code --format=junit --severity=error
    code_quality:
      script:
        - wheels analyze code --format=json > code-quality.json
      artifacts:
        reports:
          codequality: code-quality.json
    stage('Code Analysis') {
        steps {
            sh 'wheels analyze code --format=junit'
            junit 'code-analysis-results.xml'
        }
    }
    wheels dbmigrate create column name=<column_name> tableName=<table> dataType=<type> [options]

    false (sensitive values are masked)

    warning

    --report

    Generate HTML report

    false

    --verbose

    Show detailed progress during analysis

    false

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

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

    --output

    string

    No

    Output file name (default: .env.merge)

    --dryRun

    flag

    No

    Show what would be merged without actually writing the file

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

    The new value and source file

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

    • Generation timestamp

    • Source information

    • Section headers for each variable group

    Value preservation - Values containing = signs are properly preserved

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

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

  • Document your merge strategy in your project's README

  • Backup important configurations before merging

  • Review conflicts - Pay attention to the conflicts report to ensure expected overrides

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

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

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

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

    string

    Yes

    -

    The column type 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

    biginteger - BIGINT
  • float - FLOAT

  • decimal - DECIMAL

  • boolean - BOOLEAN/BIT

  • date - DATE

  • time - TIME

  • datetime - DATETIME/TIMESTAMP

  • timestamp - TIMESTAMP

  • binary - BLOB/BINARY

  • wheels dbmigrate down - Rollback migrations

    name

    string

    Yes

    -

    The column name to add

    tableName

    string

    Yes

    -

    The name of the database table to modify

    wheels dbmigrate create table
    wheels dbmigrate create blank
    wheels dbmigrate remove table
    wheels dbmigrate up

    dataType

    Test files

    Explore Commands:

    • wheels --help

    • wheels generate --help

    • wheels dbmigrate --help

  • Join the Community:

    • Wheels Documentation

    • GitHub Discussions

    • CFML Slack #wheels channel

  • Service Architecture
    Testing Guide
    Migration Guide
    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

    Required

    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.

    Output shows:

    • Package name

    • Version specification

    • Type (Production/Development)

    • Installation status

    Example output:

    Install

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

    Examples:

    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.

    Example:

    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.

    Example:

    Note: Remove action will ask for confirmation before proceeding.

    Report

    Generate a comprehensive dependency report with outdated package check.

    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:

    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

    Development Dependencies

    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:

    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

    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)

    JSON Format

    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:

    Variable Interpolation

    Use ${VAR} syntax to reference other variables:

    Type Casting

    Wheels automatically casts certain values:

    Using Environment Variables in Settings

    Access environment variables in your settings files:

    Configuration Files

    Directory Structure

    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

    Environment-Specific Settings

    CLI Configuration Commands

    Dumping Configuration

    Export current configuration:

    Checking Configuration

    Validate configuration for issues:

    Common checks performed:

    • Missing required settings

    • Hardcoded sensitive values

    • Production security settings

    • Database configuration

    • Caching optimizations

    Comparing Environments

    Find differences between environments:

    Managing Secrets

    Generate secure secrets:

    Environment Variable Management

    Setting Variables

    Validating Files

    Merging Files

    Security Best Practices

    1. Never Commit Secrets

    Always add .env files to .gitignore:

    2. Use Strong Secrets

    Generate cryptographically secure values:

    3. Validate Production Config

    Run checks before deployment:

    4. Mask Sensitive Output

    Always use masking in logs/output:

    5. Environment-Specific Secrets

    Use different secrets per environment:

    Common Patterns

    Multi-Environment Setup

    Deployment Configuration

    Local Development

    Troubleshooting

    Environment Variables Not Loading

    1. Check file exists and is readable

    2. Verify format (properties vs JSON)

    3. Check for syntax errors:

    Wrong Environment Loading

    1. Check WHEELS_ENV variable:

    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:

    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 env merge <source1> <source2> [source3...] [options]
    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
    [timestamp]_[tablename]_[columnname]_create_column.cfc
    20240125160000_user_email_create_column.cfc
    wheels dbmigrate create column name=email tableName=user dataType=string
    wheels dbmigrate create column name=is_active tableName=user dataType=boolean default=true
    wheels dbmigrate create column name=bio tableName=user dataType=string allowNull=true limit=500
    wheels dbmigrate create column name=price tableName=product dataType=decimal precision=10 scale=2
    wheels dbmigrate create column name=phone tableName=user dataType=string 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=newsletter_subscribed tableName=user dataType=boolean default=true
    wheels dbmigrate create column name=theme_preference tableName=user dataType=string default="light"
    wheels dbmigrate create column name=last_modified_by tableName=product dataType=integer allowNull=true
    wheels dbmigrate create column name=last_modified_at tableName=product dataType=datetime allowNull=true
    wheels dbmigrate create column name=price tableName=product dataType=decimal precision=10 scale=2 default=0
    wheels dbmigrate create column name=cost tableName=product dataType=decimal precision=10 scale=2
    # Good - nullable
    wheels dbmigrate create column name=bio tableName=user dataType=text allowNull=true
    
    # Good - with default
    wheels dbmigrate create column name=status tableName=user dataType=string default="active"
    
    # Bad - will fail if table has data (not nullable, no default)
    wheels dbmigrate create column name=required_field tableName=user dataType=string --allowNull=false
    # For short text
    wheels dbmigrate create column name=username tableName=user dataType=string limit=50
    
    # For long text
    wheels dbmigrate create column name=content tableName=post dataType=text
    
    # For money
    wheels dbmigrate create column name=amount tableName=invoice dataType=decimal precision=10 scale=2
    # Create separate migrations for related columns
    wheels dbmigrate create column name=address_line1 tableName=customer dataType=string
    wheels dbmigrate create column name=city tableName=customer dataType=string
    wheels dbmigrate create column name=state tableName=customer dataType=string limit=2
    # Add foreign key column
    wheels dbmigrate create column name=customer_id tableName=order dataType=integer
    
    # 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=required_field tableName=user dataType=string --allowNull=false
    
    # Do this instead
    wheels dbmigrate create column name=required_field tableName=user dataType=string default="pending"
    # Wrong - trying to change existing column type
    wheels dbmigrate create column name=age tableName=user dataType=integer
    
    # Right - use blank migration for modifications
    wheels dbmigrate create blank name=change_user_age_to_integer
    # macOS/Linux
    curl -fsSl https://downloads.ortussolutions.com/debs/gpg | sudo apt-key add -
    echo "deb https://downloads.ortussolutions.com/debs/noarch /" | sudo tee -a /etc/apt/sources.list.d/commandbox.list
    sudo apt-get update && sudo apt-get install commandbox
    
    # Windows (PowerShell as Admin)
    iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
    choco install commandbox
    box install wheels-cli
    wheels new blog
    cd blog
    <cfset set(dataSourceName="blog_development")>
    wheels new blog --setupH2
    # If using external database (MySQL, PostgreSQL, etc.)
    wheels db create
    box server start
    wheels generate scaffold name=post properties=title:string,content:text,published:boolean
    wheels dbmigrate latest
    <cfscript>
        // Add this line
        resources("posts");
    </cfscript>
    wheels reload
    # Run all tests
    wheels test run
    
    # Watch mode
    wheels test run --watch
    
    # Specific tests
    wheels test run tests/models/PostTest.cfc
    # Generate comment model
    wheels generate model comment --properties="author:string,content:text,postId:integer" \
      --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
    # Generate user model
    wheels scaffold name=user properties=email:string,password:string,admin:boolean
    
    # Generate session controller
    wheels generate controller sessions new,create,delete
    
    # Run migrations
    wheels dbmigrate latest
    # Generate API resource
    wheels generate api-resource product --properties="name:string,price:decimal"
    
    # Or convert existing to API
    wheels generate controller api/posts --api
    # Generate specific views
    wheels generate view posts featured
    wheels generate view users profile
    
    # Add layouts
    echo '<cfoutput><!DOCTYPE html>...</cfoutput>' > views/layout.cfm
    # Create tables
    wheels dbmigrate create table products
    
    # Add columns
    wheels dbmigrate create column products featured
    
    # Create indexes
    wheels dbmigrate create blank add_index_to_products
    # After creating a model
    wheels generate test model post
    
    # After creating a controller
    wheels generate test controller posts
    # Development
    wheels reload development
    
    # Testing
    wheels reload testing
    
    # Production
    wheels reload production
    git init
    git add .
    git commit -m "Initial Wheels application"
    /db/sql/
    /logs/
    /temp/
    .env
    tail -f logs/wheels.log
    <cfset set(showDebugInformation=true)>
    box server start port=3001
    # Check datasource
    box server info
    box server show
    # Check status
    wheels db status
    
    # Run specific migration
    wheels dbmigrate exec 20240120000000
    
    # Or rollback and try again
    wheels db rollback
    # Complete reset (careful - destroys all data!)
    wheels db reset --force
    # CLI shell
    wheels db shell
    
    # Web console (H2 only)
    wheels db shell --web
    # Create application
    wheels new myblog --setupH2
    cd myblog
    
    # Generate blog structure
    wheels scaffold post title:string,slug:string,content:text,publishedAt:datetime
    wheels scaffold author name:string,email:string,bio:text
    wheels generate model comment author:string,email:string,content:text,postId:integer \
      --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
    wheels deps <action> [name] [options]
    wheels deps list
    Dependencies:
      wirebox @ ^7.0.0 (Production) - Installed
      testbox @ ^6.0.0 (Production) - Installed
      wheels-core @ ^3.0.0-SNAPSHOT.rc.1 (Production) - Installed
    
    Dev Dependencies:
      shortcodes @ ^0.0.4 (Production) - Not Installed
    wheels deps install <name>
    wheels deps install <name> <version>
    wheels deps install <name> --dev
    # 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
    wheels deps update <name>
    wheels deps update wirebox
    wheels deps remove <name>
    wheels deps remove oldpackage
    wheels deps report
    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
    {
      "dependencies": {
        "wheels-core": "^3.0.0",
        "wirebox": "^7"
      }
    }
    {
      "devDependencies": {
        "testbox": "^6",
        "commandbox-cfformat": "*"
      }
    }
    "installPaths": {
        "testbox": "testbox/",
        "wirebox": "wirebox/",
        "cbvalidation": "modules/cbvalidation/",
        "shortcodes": "plugins/Shortcodes/"
    }
    wheels env validate
    wheels env show --key=WHEELS_ENV
    wheels config check --verbose
    # 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
    {
      "DB_HOST": "localhost",
      "DB_PORT": 3306,
      "DB_NAME": "myapp_development",
      "DB_USER": "wheels",
      "DB_PASSWORD": "secretpassword",
      "WHEELS_ENV": "development",
      "DEBUG_MODE": true
    }
    myapp/
    ├── .env                 # Base configuration
    ├── .env.development     # Development overrides
    ├── .env.testing        # Testing overrides
    ├── .env.production     # Production settings
    └── .gitignore          # MUST exclude .env files!
    # .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
    # 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
    // 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);
    }
    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
    // config/settings.cfm
    set(dataSourceName = application.env['DB_NAME'] ?: 'wheelstutorial');
    set(dataSourceUserName = application.env['DB_USER'] ?: 'root');
    set(dataSourcePassword = application.env['DB_PASSWORD'] ?: '');
    // 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']);
    # 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
    # Basic check
    wheels config check
    
    # Check production with fixes
    wheels config check production --fix
    
    # Verbose output with suggestions
    wheels config check --verbose
    # 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
    # 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
    # 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
    # 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
    # Merge for deployment
    wheels env merge .env .env.production --output=.env.deployed
    
    # Preview merge
    wheels env merge .env.defaults .env.local --dry-run
    # Environment files
    .env
    .env.*
    !.env.example
    !.env.defaults
    # 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
    # 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
    // In your code
    writeOutput("Database: #application.env['DB_NAME']#");
    writeOutput("Password: ***MASKED***"); // Never output passwords
    # .env.development
    SECRET_KEY=dev_secret_key_only_for_local
    
    # .env.production
    SECRET_KEY=prod_0a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0
    # 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
    # 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
    # 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
    command (alias:
    wheels g resource
    ) 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")

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

    • Flag with value: --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

    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

    Examples

    Basic Examples

    Advanced Examples

    Parameter Details

    Properties Format:

    • Format: --properties="name:type,name2:type2"

    • Types: string, text, integer, decimal, boolean, date, datetime, time

    • Always use quotes around the entire properties string

    Associations:

    • --belongsTo=Model1,Model2 (PascalCase parent models)

    • --hasMany=model1,model2 (camelCase plural child models)

    Resource Name:

    • Must be singular (e.g., Product, not Products)

    • PascalCase recommended (e.g., OrderItem, UserProfile)

    What Gets Generated

    Standard Scaffold

    1. Model (/models/Product.cfc) - Properties, validations, associations

    2. Controller (/controllers/Products.cfc) - All CRUD actions (index, show, new, create, edit, update, delete)

    3. Views (/views/products/) - index, show, new, edit, _form partial

    4. Migration (/app/migrator/migrations/[timestamp]_create_products.cfc) - Table creation with indexes

    5. Tests - Model, controller, and integration tests (if --tests=true)

    API Scaffold

    1. Model - Same as standard

    2. API Controller - JSON responses only, no views

    3. Migration - Same as standard

    4. API Tests - JSON response tests

    Generated Code Structure

    For wheels scaffold Product --properties="name:string,price:decimal":

    Model: Properties with validations (presence, uniqueness, numerical) Controller: Full CRUD actions with flash messages and error handling Views: List, show, new/edit forms with proper encoding and form helpers Migration: Table with columns, timestamps, and indexes

    Run the command to see the complete generated code.

    Routes Configuration

    Add to /config/routes.cfm:

    This creates all RESTful routes (index, new, create, show, edit, update, 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. Reload application: wheels reload

    4. Test: Visit /products and test CRUD operations

    Common Customizations

    Search:

    Pagination:

    Authentication:

    Template Customization

    Customize generated code by overriding templates:

    1. Copy templates from /cli/templates/ to /app/snippets/

    2. Modify templates to match your project style

    3. CLI uses your custom templates automatically

    Available Templates:

    • crud/index.txt, crud/show.txt, crud/new.txt, crud/edit.txt, crud/_form.txt

    • ModelContent.txt, ControllerContent.txt

    Template Placeholders:

    • |ObjectNameSingular|, |ObjectNamePlural| - Lowercase names

    • |ObjectNameSingularC|, |ObjectNamePluralC| - Capitalized names

    • |FormFields| - Generated form fields

    Troubleshooting

    Common Issues

    Parameter Syntax Errors

    ❌ Wrong:

    ✅ Correct:

    Rules:

    • Always use quotes around properties: --properties="name:string"

    • Use PascalCase for belongsTo: --belongsTo=User

    • Use camelCase plural for hasMany: --hasMany=posts,comments

    • Never mix positional and named parameters

    File Generation Issues

    Problem: Files not created or partially generated

    Solutions:

    • Check directory permissions: ls -la app/models app/controllers

    • Use --force to overwrite existing files

    • Use singular PascalCase names (e.g., Product, not products or product-item)

    Migration Issues

    Problem: Migrations not created or contain errors

    Solutions:

    • Use valid property types: string, text, integer, decimal, boolean, date, datetime

    • Check generated migration: ls app/migrator/migrations/*create*

    • Foreign keys are auto-generated for associations

    Route Issues

    Problem: Routes not working

    Solution: Add resources to /config/routes.cfm:

    Validation Checklist

    Before scaffolding:

    1. Verify Wheels directory structure exists

    2. Check for naming conflicts

    3. Plan properties and associations

    After scaffolding:

    1. Check files were created: ls app/models/ app/controllers/ app/views/

    2. Run migrations: wheels dbmigrate latest

    3. Add routes to routes.cfm

    4. Test at /products

    Best Practices

    1. Plan First: Define properties, associations, and validations before scaffolding

    2. Start Simple: Begin with basic properties, add complexity later

    3. Use Templates: Customize templates in /app/snippets/ to match your project standards

    4. Test Immediately: Run generated tests and visit the scaffold in browser

    5. Security: Add authentication/authorization filters after generation

    6. Incremental: Scaffold one resource at a time, test, then move to the next

    See Also

    • wheels generate model - Generate models only

    • wheels generate controller - Generate controllers only

    • wheels generate view - Generate views only

    • wheels dbmigrate latest - Run migrations

    Synopsis

    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

    Sets DB_HOST to localhost in the .env file

    Set Multiple Variables

    Sets multiple database-related variables in a single command

    Update Specific File

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

    Complex Values

    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)

    JSON Format

    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

    Common Use Cases

    Initial Setup

    Database Configuration

    API Configuration

    Environment-Specific Settings

    Updating Existing Values

    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:

    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

    Best Practices

    1. Use quotes for complex values containing spaces or special characters:

    2. Update multiple related variables together to maintain consistency:

    3. Keep sensitive values in separate files not tracked by version control:

    4. Always check .gitignore to ensure sensitive files are not committed:

    5. Use environment-specific files for different deployments:

    Tips and Tricks

    Batch Updates

    Update multiple variables from different categories in one command:

    Quick Environment Switch

    Creating Templates

    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

    Command Syntax

    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

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

    Compare Environment Variables Only

    Compare Settings Only

    JSON Output Format

    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

    Environment Files

    Output Format

    Table Format (Default)

    The table output is organized into clear sections:

    JSON Format

    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

    Common Use Cases

    Pre-Deployment Verification

    Environment Synchronization Check

    Security Audit

    CI/CD Pipeline Integration

    Environment Variable Validation

    Quick Similarity Check

    Examples

    Example 1: Basic Comparison

    Shows all differences and similarities between development and production configurations.

    Example 2: Changes Only

    Shows only the differences, useful for quick reviews.

    Example 3: Environment Variables Focus

    Shows only environment variable differences between development and staging.

    Example 4: JSON for Automation

    Outputs similarity percentage for automated checks.

    Example 5: Settings Validation

    Validates only Wheels settings differences.

    Error Handling

    Environment Not Found

    The command continues with available data and shows warnings for missing files.

    No Configuration File

    The command will still compare environment variables if available.

    Same Environment Comparison

    You must specify two different environments.

    Invalid Format

    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)

    Environment File (.env.production)

    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 generate app-wizard

    Interactive wizard for creating a new Wheels application with guided setup.

    Synopsis

    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)

    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.

    Options

    Option
    Description
    Valid Values
    Default

    Interactive Wizard Steps

    Step 1: Application Name

    • Validates name format (alphanumeric, underscores, hyphens)

    • Checks for reserved names

    • Creates directory with this name

    Step 2: Template Selection

    Step 3: Reload Password

    • Used for ?reload=true&password=xxx functionality

    • Secures application reload via URL

    Step 4: Database Configuration

    • Sets datasource name in configuration files

    • You'll configure the actual datasource in your CFML server admin

    Step 5: CFML Engine Selection

    Step 6: H2 Database Setup (Lucee Only, if skipInstall=false)

    • Only shown when using Lucee engine

    • Only asked if skipInstall=false

    • Sets up embedded H2 database for development

    Step 7: Dependencies (if skipInstall=false)

    • Only shown if skipInstall=false

    • Configures Bootstrap CSS framework

    • Additional dependencies may be added here

    Step 8: Package Initialization

    • Creates box.json for ForgeBox package management

    • Always asked regardless of skipInstall setting

    Step 9: Expert Mode (if expert=true)

    Step 10: Configuration Review

    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

    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

    Dependencies Skipped Message

    Examples

    Basic Interactive Wizard

    Runs full interactive wizard with all prompts.

    Skip All Dependencies

    Runs wizard but skips H2 database and Bootstrap questions.

    Expert Mode

    Includes advanced configuration options like custom ports and JVM settings.

    Non-Interactive Mode

    Uses all defaults, no prompts. Creates app immediately.

    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

    Non-Interactive Mode

    Use --nonInteractive to bypass all prompts:

    Default Values Used

    • Name: MyWheelsApp

    • Template: wheels-base-template@BE

    • Reload Password: changeMe

    Override Defaults

    Post-Creation Steps

    After successful creation:

    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

    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:

    Directory not empty:

    Missing dependencies:

    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

    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

    • - Non-interactive app generation

    • - Generate controllers

    • - Generate models

    • - Generate complete CRUD

    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.

    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.

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

    wheels generate scaffold name [options]
    wheels g scaffold name [options]
    wheels g resource name [options]    # Alias for scaffold
    # Basic scaffold
    wheels g scaffold Product
    wheels g resource Product                    # Same as scaffold
    
    # With properties
    wheels g scaffold Product --properties="name:string,price:decimal,stock:integer"
    wheels g resource Product --properties="name:string,price:decimal,stock:integer"
    
    # With associations
    wheels g scaffold Order --properties="total:decimal,status:string" --belongsTo=User --hasMany=orderItems
    
    # API-only scaffold (no views)
    wheels g scaffold Product --api --properties="name:string,price:decimal"
    wheels g resource Product --api --properties="name:string,price:decimal"
    
    # With auto-migration
    wheels g scaffold Category --properties="name:string" --migrate
    # Complete e-commerce product
    wheels generate scaffold Product --properties="name:string,description:text,price:decimal,inStock:boolean" --belongsTo=Category --hasMany=orderItems,reviews --migrate
    
    # Blog system
    wheels generate scaffold Post --properties="title:string,content:text,published:boolean" --belongsTo=User --hasMany=comments --api
    <cfset resources("products")>
    function index() {
        products = StructKeyExists(params, "search")
            ? model("Product").findAll(where="name LIKE :search", params={search: "%#params.search#%"})
            : model("Product").findAll();
    }
    products = model("Product").findAll(page=params.page ?: 1, perPage=20);
    function init() {
        filters(through="authenticate", except="index,show");
    }
    wheels generate scaffold Product properties="name:string"    # Positional + named (ERROR)
    wheels generate scaffold Product --properties=name:string    # Missing quotes
    wheels generate scaffold Comment --belongsTo=product         # Wrong case (should be Product)
    wheels generate scaffold Product --properties="name:string,price:decimal"
    wheels generate scaffold Comment --belongsTo=Product --hasMany=replies
    <cfscript>
    mapper()
        .resources("products")
        .wildcard()
    .end();
    </cfscript>
    wheels env set CONNECTION_STRING="Server=localhost;Database=myapp;User=root"
    wheels env set DB_HOST=newhost DB_PORT=3306 DB_NAME=newdb
    wheels env set --file=.env.local API_SECRET=very-secret-key
    echo ".env" >> .gitignore
    echo ".env.local" >> .gitignore
    wheels env set KEY=VALUE [KEY2=VALUE2 ...] [--file=filename]
    wheels env set DB_HOST=localhost
    wheels env set DB_PORT=3306 DB_NAME=myapp DB_USER=root
    wheels env set --file=.env.production API_KEY=secret
    wheels env set DATABASE_URL="mysql://user:pass@localhost:3306/db"
    wheels env set API_ENDPOINT=https://api.example.com/v1
    DATABASE_HOST=localhost
    DATABASE_PORT=3306
    API_KEY=your-secret-key
    {
      "DATABASE_HOST": "localhost",
      "DATABASE_PORT": "3306",
      "API_KEY": "your-secret-key"
    }
    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.
    # Create a new .env file with basic configuration
    wheels env set APP_NAME=MyApp APP_ENV=development DEBUG=true
    # Set all database variables at once
    wheels env set DB_HOST=localhost DB_PORT=5432 DB_NAME=myapp DB_USER=appuser DB_PASSWORD=secret
    # Configure API endpoints and keys
    wheels env set API_BASE_URL=https://api.example.com API_KEY=abc123 API_TIMEOUT=30
    # 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
    # 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
    # Wheels Environment Configuration
    # Generated by wheels env set command
    
    APP_NAME=MyApplication
    APP_ENV=development
    DEBUG=true
    # 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]
    wheels env set \
      APP_NAME=MyApp \
      DB_HOST=localhost \
      DB_PORT=5432 \
      CACHE_DRIVER=redis \
      MAIL_HOST=smtp.example.com
    # 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
    # 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
    wheels config diff <env1> <env2> [--changesOnly] [--format=<format>] [--env] [--settings]
    # Compare everything between development and production
    wheels config diff development production
    # Show only the differences, hide identical values
    wheels config diff development production --changesOnly
    # Compare only .env files between environments
    wheels config diff development production --env
    # Compare only Wheels settings files
    wheels config diff development production --settings
    # Output comparison as JSON for parsing
    wheels config diff development production --format=json
    config/
    ├── settings.cfm                 # Base settings
    ├── development/
    │   └── settings.cfm             # Development overrides
    ├── testing/
    │   └── settings.cfm             # Testing overrides
    └── production/
        └── settings.cfm             # Production overrides
    project_root/
    ├── .env                         # Base/development environment variables
    ├── .env.development             # Development-specific variables
    ├── .env.testing                 # Testing-specific variables
    └── .env.production              # Production-specific variables
    ========================================
    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%
    {
      "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
        }
      }
    }
    ┌──────────────┬────────────────┬────────────────┐
    │ Variable     │ development    │ production     │
    ├──────────────┼────────────────┼────────────────┤
    │ DB_PASSWORD  │ ***MASKED***   │ ***MASKED***   │
    │ API_KEY      │ ***MASKED***   │ ***MASKED***   │
    └──────────────┴────────────────┴────────────────┘
    # Verify configuration differences before deploying to production
    wheels config diff testing production --changesOnly
    # Check if development and testing have similar configurations
    wheels config diff development testing
    # Review all security-related settings between environments
    wheels config diff development production --settings | grep -i "debug\|error\|ssl"
    # In your deployment script
    wheels config diff staging production --format=json > config-diff.json
    # Parse JSON to validate critical settings match expectations
    # Ensure all required environment variables exist in production
    wheels config diff development production --env --changesOnly
    # Get a quick overview of configuration similarity
    wheels config diff development testing | grep "Similarity:"
    wheels config diff development production
    wheels config diff testing production --changesOnly
    wheels config diff development staging --env --changesOnly
    wheels config diff development production --format=json | jq '.summary.overall.similarity'
    wheels config diff development production --settings --changesOnly
    Warning: Settings for environment 'staging' not found!
    Warning: No settings.cfm file found in config directory
    Error: Cannot compare an environment to itself
    Error: Invalid format: xml. Valid formats are: table, json
    <cfscript>
    // Production-specific settings
    set(showDebugInformation = false);
    set(showErrorInformation = false);
    set(sendEmailOnError = true);
    set(errorEmailAddress = "[email protected]");
    set(cacheQueries = true);
    set(forceSSL = true);
    </cfscript>
    # 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
    wheels generate app-wizard [options]
    
    #Can also be used as:
    wheels g app-wizard [options]
    wheels new [options]

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

    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

    Param with value: --param=value equals param=value (e.g., --skipInstall=true)

    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

    Installs dependencies after app creation

    Datasource Name: Same as app name

  • CFML Engine: lucee

  • Directory: {current directory}/{name}

  • Cannot be a reserved name (con, prn, aux, nul, wheels, etc.)
    : Use
    --expert
    for production-ready configurations
  • Save time with non-interactive: Use --nonInteractive in automated scripts

  • Template selection: Choose templates that match your project requirements

  • 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

    wheels generate app
    wheels generate controller
    wheels generate model
    wheels scaffold

    {app name}

    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 Syntax

    Options

    Option
    Description
    Default

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

    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 env set --file=.env.production APP_ENV=production
    wheels env set --file=.env.staging APP_ENV=staging
    Please enter a name for your application: MyWheelsApp
    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
    Please enter a 'reload' password for your application: changeMe
    Please enter a datasource name if different from MyWheelsApp: MyWheelsApp
    Please select your preferred CFML engine?
    ❯ Lucee (Latest)
      Adobe ColdFusion (Latest)
      BoxLang (Latest)
      Lucee 7.x
      Lucee 6.x
      Lucee 5.x
      Adobe ColdFusion 2025
      Adobe ColdFusion 2023
      Adobe ColdFusion 2021
      Adobe ColdFusion 2018
      Enter a custom engine endpoint
    H2 Java embedded SQL database for development? [y,n]
    ========= Dependencies ======================
    Configure dependencies and plugins for your application.
    
    Would you like us to setup some default Bootstrap settings? [y/n]
    Finally, shall we initialize your application as a package
    by creating a box.json file? [y,n]
    ========= 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]
    +-----------------------------------------------------------------------------------+
    | 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]
    ========= Dependencies Skipped ================
    Dependency installation is disabled (skipInstall=true).
    Dependencies like Bootstrap and H2 database will not be configured or installed.
    wheels generate app-wizard
    wheels generate app-wizard --skipInstall
    wheels generate app-wizard --expert
    wheels generate app-wizard --nonInteractive
    wheels generate app-wizard --nonInteractive name=CustomApp template=wheels-starter-app --cfmlEngine=adobe
    Model generation complete!
    
    Next steps:
       1. Review generated configuration files
       2. Configure your datasource in CFML server admin
       3. box server start (to start development server)
       4. 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)
    '123app' is not valid. Application name must start with a letter.
    Please try again: MyApp
    Target directory is not empty. Use --force to overwrite, or choose a different location.
    Warning: Some dependencies could not be installed.
    Run 'box install' in your application directory to install them manually.
    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 env validate [options]
    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
    Command Syntax

    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

    Basic Usage

    Check Current Environment

    Check Specific Environment

    Check with Detailed Output

    Auto-fix Issues

    Combined Options

    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:

    Status Indicators

    • [OK] - Check passed successfully

    • [WARNING] - Non-critical issues found

    • [FAILED] - Critical errors detected

    • [FIXED] - Issue was automatically fixed

    Results Display

    Error Output

    Warning Output

    Fixed Issues Output

    Summary Output

    Or with issues:

    Verbose Mode

    When using --verbose, each issue includes detailed fix suggestions:

    Output:

    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

    Output:

    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

    Development Setup Validation

    CI/CD Pipeline Integration

    Security Audit

    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

    Solution: Run the command from your Wheels application root directory

    Cannot Read Configuration

    Solution: Check file permissions and ensure configuration files are valid CFML

    Datasource Not Found

    Solution: Configure the datasource in your CFML administrator or Application.cfc

    Permission Issues with .env

    Solution: Run chmod 600 .env to restrict file permissions

    Configuration File Examples

    Basic config/settings.cfm

    Environment-specific config/production/settings.cfm

    Sample .env file

    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

    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

    BoxLang Module Dependencies

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

    Installing Dependencies

    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

    • SQLite Server: box install bx-sqlite

    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:

    Development Workflow

    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:

    Installation Steps

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

    2. Prepare Your Application Structure

    3. Setup BoxLang Entry Point

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

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

    Starting BoxLang Mini-Server

    Basic Command

    Full Configuration Example

    For Wheels Template Structure

    If using the Wheels base template structure:

    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

    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

    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.

    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

    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.

    wheels config check [environment] [--verbose] [--fix]
    wheels config check
    wheels config check production
    wheels config check --verbose
    wheels config check --fix
    wheels config check production --verbose --fix
    ========================================
    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]
    
    ========================================
    [ERRORS] (2):
       - Missing config/settings.cfm file
       - Datasource 'myapp_db' not found
    [WARNINGS] (3):
       - Possible hardcoded sensitive value in 'apiKey'
       - No environment-specific config directory for 'production'
       - Automatic database migration is enabled
    [FIXED] Issues:
       - Created sample .env file
       - Added .env to .gitignore
    [PASSED] Configuration validation successful!
      All checks completed successfully.
    [FAILED] Configuration check failed
      Found: 2 errors, 3 warnings
      
      Tip: Run with --verbose flag for detailed fix suggestions
    wheels config check --verbose
    [ERRORS] (1):
       - Debug mode is enabled in production
         --> Fix: Set showDebugInformation = false in config/production/settings.cfm
    wheels config check --fix
    [FIXED] Issues:
       - Created sample .env file
       - Added .env to .gitignore
    # Check production configuration before deployment
    wheels config check production --verbose
    # Ensure development environment is properly configured
    wheels config check development --fix
    # In your CI/CD script
    wheels config check production
    if [ $? -ne 0 ]; then
        echo "Configuration validation failed!"
        exit 1
    fi
    # Check for security issues across all environments
    wheels config check development --verbose
    wheels config check testing --verbose
    wheels config check production --verbose
    Error: This command must be run from a Wheels application directory
    Error reading configuration: [error message]
    Datasource 'myapp_db' not found
    .env file has overly permissive permissions
    <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>
    <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>
    # 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
    # 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
    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
    <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" />
    # 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]
    {
      "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"
      }
    }
    # 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
    {
        "name":"wheels",
        "web":{
            "host":"localhost",
            "http":{
                "port":3000
            },
            "webroot":"public",
            "rewrites":{
                "enable":true,
                "config":"public/urlrewrite.xml"
            }
        },
        "app": {
            "cfengine": "boxlang"
        }
    }
    # 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
    # Download the latest BoxLang Mini-Server JAR file
    curl -LO https://downloads.ortussolutions.com/ortussolutions/boxlang-runtimes/boxlang-miniserver/boxlang-miniserver-latest.jar
    java -jar /path/to/boxlang-miniserver-1.6.0.jar \
      --webroot /path/to/your/app/public \
      --rewrite
    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
    java -jar /path/to/boxlang-miniserver-1.6.0.jar \
      --webroot /path/to/your/app/templates/base/src/public \
      --rewrite \
      --port 8080
    # 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

    Attempt to automatically fix certain issues

    --debug

    Enable debug mode

    false

    --config

    Path to configuration file

    None

    --libs

    Additional library paths

    None

    Command Syntax

    Parameters

    Optional Parameters

    Parameter
    Description
    Default

    --key

    Show a specific environment variable by key name

    -

    --format

    Output format: table or json

    table

    --file

    Specific .env file to read

    .env

    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 env show [options]
    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
    Runs all tests in your application using TestBox CLI.

    Options

    Option
    Type
    Default
    Description

    --type

    string

    app

    Type of tests to run: (app, core)

    --format

    string

    txt

    Output format (txt, json, junit, html)

    --coverage

    boolean

    false

    Examples

    test:unit - Run Unit Tests

    Runs only unit tests located in the tests/specs/unit directory.

    Options

    Option
    Type
    Default
    Description

    --type

    string

    app

    Type of tests to run: (app, core)

    --format

    string

    txt

    Output format (txt, json, junit, html)

    --verbose

    boolean

    true

    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

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

    Examples

    test:coverage - Code Coverage

    Runs tests with code coverage analysis (requires FusionReactor).

    Options

    Option
    Type
    Default
    Description

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

    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

    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.

    wheels plugin search

    Search for available Wheels plugins on ForgeBox.

    Synopsis

    CommandBox Parameter Syntax

    This command supports multiple parameter formats:

    wheels plugin init

    Initialize a new CFWheels plugin in the /plugins directory.

    Usage

    Parameters

    wheels test:all
    # 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
    # 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
    # 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
    # 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
    box install testbox-cli
    box reload

    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

    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

    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

    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

    it("should process payments", function() {
        // test code
    }).labels("critical", "payments");
    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.

    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:

    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:

    Example .env File

    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

    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

    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:

    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

    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:

    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

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

    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:

    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:

    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

    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:

    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

    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:

    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

    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:

    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

    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

    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

    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

  • 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

    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

    Output:

    Search for specific plugin

    Output:

    No results found

    Output:

    Sort by name

    Results will be sorted alphabetically by plugin name.

    Sort by last updated

    Results will be sorted by most recently updated plugins first.

    Export as JSON

    Output:

    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)

    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:

    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:

    If no plugins of type cfwheels-plugins exist:

    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

    Parameter
    Required
    Type
    Options
    Default
    Description

    name

    Yes

    string

    -

    -

    Plugin name (will be prefixed with 'wheels-')

    author

    No

    string

    -

    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

    • wheels plugin install - Install plugins from ForgeBox

    • wheels plugin list - List installed plugins

    • wheels reload - Reload application

    • Developing Plugins - Full plugin development guide

    wheels generate snippets

    Generate code snippets and boilerplate code for common Wheels patterns.

    Synopsis

    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

    Options

    Option
    Description
    Valid Values
    Default

    Available Snippets

    List All Snippets

    Output:

    Authentication Snippets

    Login Form

    Generates:

    Note: This is a basic snippet. You can customize it by saving to a file and editing:

    Authentication Filter

    Generates:

    Password Reset

    Generates:

    User Registration

    Generates:

    Model Patterns

    Soft Delete

    Generates:

    Audit Trail

    Generates:

    Sluggable

    Generates:

    Versionable

    Generates:

    Searchable

    Generates:

    Controller Patterns

    CRUD Actions

    Generates:

    API Controller

    Generates:

    Nested Resource

    Generates:

    Admin Controller

    Generates:

    View Patterns

    Form with Errors

    Generates:

    Pagination Links

    Generates:

    Search Form

    Generates:

    AJAX Form

    Generates:

    Database Snippets

    Migration Indexes

    Generates:

    Seed Data

    Generates:

    Constraints

    Generates:

    Custom Snippets

    Create Custom Snippet

    This creates a directory structure in app/snippets/my-custom-snippet/:

    You can then edit the template file and use your custom snippet:

    Output Options

    Output to Console (Default)

    Save to File

    Use --force to overwrite existing files:

    Customization Options

    Shows available customization options for snippets.

    Filter by Category

    List snippets from a specific category:

    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

    See Also

    • - Generate controllers

    • - Generate models

    • - Generate complete resources

    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

    Core Components

    1. Commands (/commands/wheels/)

    Commands are the user-facing interface:

    2. Services (/models/)

    Services contain reusable business logic:

    3. Base Classes

    BaseCommand (/models/BaseCommand.cfc)

    All commands extend from BaseCommand:

    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/

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

    Creating a New Service

    1. Create Service Component

    2. Register Service

    In /ModuleConfig.cfc:

    3. Use in Commands

    Service Patterns

    1. Singleton Pattern

    Most services are singletons:

    2. Factory Pattern

    For creating multiple instances:

    3. Strategy Pattern

    For different implementations:

    Testing Services

    Unit Testing Services

    Mocking Dependencies

    Best Practices

    1. Single Responsibility

    Each service should have one clear purpose:

    2. Dependency Injection

    Always inject dependencies:

    3. Error Handling

    Services should handle errors gracefully:

    4. Configuration

    Services should be configurable:

    Service Communication

    Event-Driven

    Services can emit events:

    Direct Communication

    Services can call each other:

    Extending Services

    Creating Service Plugins

    Service Decorators

    Performance Considerations

    1. Lazy Loading

    Load services only when needed:

    2. Caching

    Cache expensive operations:

    3. Async Operations

    Use async for long-running tasks:

    Debugging Services

    Enable Debug Logging

    Service Inspection

    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

    See Also

    wheels generate test

    wheels generate test

    Generate test files for models, controllers, views, and other components using TestBox BDD syntax.

    Synopsis

    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.

    wheels generate model

    Generate a model with properties, validations, and associations.

    Synopsis

    CommandBox Parameter Syntax

    CFScript
    if (get("environment") == "production") {
        // Do something for production environment
    }
    config/app.cfm
    this.name = "TheNextSiteToBeatTwitter";
    this.sessionManagement = false;
    
    this.customTagPaths = ListAppend(
      this.customTagPaths,
      ExpandPath("../customtags")
    );
    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"]
    };
    .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
    .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
    config/environment.cfm
    set(environment="development");
    CFScript
    set(urlRewriting="Off");
    config/settings.cfm
    set(dataSourceName="yourDataSourceName");
    set(dataSourceUserName="yourDataSourceUsername");
    set(dataSourcePassword="yourDataSourcePassword");
    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"]
    };
    CFScript
    set(functionName="findAll", perPage=20);
    CFScript
    // config/development/settings.cfm
    set(showDebugInformation=false);
    CFScript
    set(cacheRoutes=false);
    CFScript
    set(tableNamePrefix="blog_");
    CFScript
    set(overwritePlugins=false);
    wheels plugin search [query] [--format=<format>] [--orderBy=<field>]
    wheels plugin search
    ===========================================================
      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
    wheels plugin search bcrypt
    ===========================================================
      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
    wheels plugin search nonexistent
    ===========================================================
      Searching ForgeBox for Wheels Plugins
    ===========================================================
    
    Search term: nonexistent
    
    No plugins found matching 'nonexistent'
    
    Try:
      wheels plugin search <different-keyword>
      wheels plugin list --available
    wheels plugin search --orderBy=name
    wheels plugin search --orderBy=updated
    wheels plugin search --format=json
    {
      "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": ""
    }
    # View detailed information
    wheels plugin info cfwheels-bcrypt
    
    # Install directly
    wheels plugin install cfwheels-bcrypt
    
    # List installed plugins
    wheels plugin list
    [ERROR] Error searching for plugins
    Error: Connection timeout
    No plugins found
    
    Try:
      wheels plugin search <different-keyword>
      wheels plugin list --available
    wheels plugin init <name> [--author=<name>] [--description=<text>] [--version=<version>] [--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 generate snippets [pattern] [options]
    wheels g snippets [pattern] [options]
    Author username (from user.username)
  • Download count (from hits)

  • Last updated date

  • table, json

    table

    Output format for the results

    orderBy

    No

    string

    name, downloads, updated

    downloads

    Sort results by specified field

    ""

    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

    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.

    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.

    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.

    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.

    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.

    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.

    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

    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)

    Debugging and error settings
    Caching settings
    ORM settings
    Plugin settings
    Media settings
    Routing settings
    View helper settings
    CSRF protection settings
    Miscellaneous Settings
    Migrator settings

    --path

    Output file path (required when output=file)

    Any valid file path

    --customize

    Show customization options

    true/false

    false

    --create

    Create custom snippet template

    true/false

    false

    --force

    Overwrite existing files

    true/false

    false

    Document customizations: Note changes made

  • Test generated code: Ensure it works in your context

  • Use consistent patterns: Across your application

  • pattern

    Snippet pattern to generate

    Shows available patterns

    --list

    List all available snippets

    true/false

    false

    --category

    Filter by category

    authentication, model, controller, view, database

    All categories

    --output

    Output format

    console, file

    wheels generate controller
    wheels generate model
    wheels scaffold

    console

    overrides
    /cli/templates/
  • Dynamic Content: Generates form fields, validations, relationships

  • API Gateway: Unified service access

    Template System Guide
    Creating Custom Commands
    Template System
    Testing Guide
    Contributing Guide
    wheels generate snippets --list
    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:
      - soft-delete - Soft delete implementation
      - audit-trail - Audit trail with timestamps
      - sluggable - URL-friendly slugs
      - versionable - Version tracking
      - searchable - Full-text search
    
    Controller:
      - crud-actions - Complete CRUD actions
      - api-controller - JSON API controller
      - nested-resource - Nested resource controller
      - admin-controller - Admin area controller
    
    View:
      - 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
    
    
    Next steps:
       1. Generate a snippet: wheels g snippets <pattern-name>
    wheels generate snippets login-form
    #startFormTag(action="create")#
      #textField(objectName="user", property="email", label="Email")#
      #passwordField(objectName="user", property="password", label="Password")#
      #checkBox(objectName="user", property="rememberMe", label="Remember me")#
      #submitTag(value="Login")#
    #endFormTag()#
    wheels generate snippets login-form --output=file --path=app/views/sessions/new.cfm
    wheels generate snippets auth-filter
    function init() {
      filters(through="authenticate", except="new,create");
    }
    
    private function authenticate() {
      if (!StructKeyExists(session, "userId")) {
        redirectTo(route="login");
      }
    }
    wheels generate snippets password-reset
    function requestReset() {
      user = model("User").findOne(where="email='#params.email#'");
      if (IsObject(user)) {
        token = Hash(CreateUUID());
        user.update(resetToken=token, resetExpiresAt=DateAdd("h", 1, Now()));
        // Send email with token
      }
    }
    wheels generate snippets user-registration
    #startFormTag(action="create")#
      #textField(objectName="user", property="firstName", label="First Name")#
      #textField(objectName="user", property="email", label="Email")#
      #passwordField(objectName="user", property="password", label="Password")#
      #submitTag(value="Register")#
    #endFormTag()#
    wheels generate snippets soft-delete
    function init() {
      property(name="deletedAt", sql="deleted_at");
      beforeDelete("softDelete");
    }
    
    private function softDelete() {
      this.deletedAt = Now();
      this.save(validate=false, callbacks=false);
      return false;
    }
    wheels generate snippets audit-trail
    function init() {
      property(name="createdBy", sql="created_by");
      property(name="updatedBy", sql="updated_by");
      beforeSave("setAuditFields");
    }
    
    private function setAuditFields() {
      if (StructKeyExists(session, "userId")) {
        if (this.isNew()) this.createdBy = session.userId;
        this.updatedBy = session.userId;
      }
    }
    wheels generate snippets sluggable
    function init() {
      property(name="slug");
      beforeSave("generateSlug");
    }
    
    private function generateSlug() {
      if (!len(this.slug) && len(this.title)) {
        this.slug = lCase(reReplace(this.title, "[^a-zA-Z0-9]", "-", "all"));
      }
    }
    wheels generate snippets versionable
    function init() {
      property(name="version", default=1);
      beforeUpdate("incrementVersion");
    }
    
    private function incrementVersion() {
      this.version = this.version + 1;
    }
    wheels generate snippets searchable
    function search(required string query) {
      return findAll(where="title LIKE '%#arguments.query#%' OR content LIKE '%#arguments.query#%'");
    }
    wheels generate snippets crud-actions
    function index() {
      users = model("User").findAll();
    }
    
    function show() {
      user = model("User").findByKey(params.key);
    }
    
    function create() {
      user = model("User").create(params.user);
      if (user.valid()) {
        redirectTo(route="user", key=user.id);
      } else {
        renderView(action="new");
      }
    }
    wheels generate snippets api-controller
    function init() {
      provides("json");
    }
    
    function index() {
      users = model("User").findAll();
      renderWith(data={users=users});
    }
    wheels generate snippets nested-resource
    function init() {
      filters(through="findParent");
    }
    
    private function findParent() {
      user = model("User").findByKey(params.userId);
    }
    wheels generate snippets admin-controller
    function init() {
      filters(through="requireAdmin");
    }
    
    private function requireAdmin() {
      if (!currentUser().isAdmin()) {
        redirectTo(route="home");
      }
    }
    wheels generate snippets form-with-errors
    #errorMessagesFor("user")#
    
    #startFormTag(action="create")#
      #textField(objectName="user", property="firstName", label="First Name")#
      <cfif user.errors("firstName").len()>
        <span class="error">#user.errors("firstName").get()#</span>
      </cfif>
      #submitTag(value="Submit")#
    #endFormTag()#
    wheels generate snippets pagination-links
    <cfif users.totalPages gt 1>
      <nav>
        <cfif users.currentPage gt 1>
          #linkTo(text="Previous", params={page: users.currentPage - 1})#
        </cfif>
        <cfloop from="1" to="#users.totalPages#" index="pageNum">
          #linkTo(text=pageNum, params={page: pageNum})#
        </cfloop>
        <cfif users.currentPage lt users.totalPages>
          #linkTo(text="Next", params={page: users.currentPage + 1})#
        </cfif>
      </nav>
    </cfif>
    wheels generate snippets search-form
    #startFormTag(method="get")#
      #textField(name="q", value=params.q, placeholder="Search...")#
      #submitTag(value="Search")#
    #endFormTag()#
    wheels generate snippets ajax-form
    #startFormTag(action="create", id="userForm")#
      #textField(objectName="user", property="name")#
      #submitTag(value="Submit")#
    #endFormTag()#
    
    <script>
    $("#userForm").submit(function(e) {
      e.preventDefault();
      $.post($(this).attr("action"), $(this).serialize());
    });
    </script>
    wheels generate snippets migration-indexes
    t.index("email");
    t.index(["last_name", "first_name"]);
    t.index("email", unique=true);
    t.index("user_id");
    wheels generate snippets seed-data
    execute("INSERT INTO users (name, email) VALUES ('Admin', '[email protected]')");
    wheels generate snippets constraints
    t.references("user_id", "users");
    t.references("category_id", "categories");
    wheels generate snippets --create my-custom-snippet
    my-custom-snippet/
    └── template.txt
    wheels generate snippets my-custom-snippet
    wheels generate snippets login-form
    # or explicitly:
    wheels generate snippets login-form --output=console
    wheels generate snippets api-controller --output=file --path=app/controllers/Api.cfc
    wheels generate snippets api-controller --output=file --path=app/controllers/Api.cfc --force
    wheels generate snippets [pattern] --customize
    wheels generate snippets --list --category=model
    wheels generate snippets --list --category=authentication
    wheels generate snippets --list --category=controller
    wheels generate snippets --list --category=view
    wheels generate snippets --list --category=database
    ┌─────────────────┐
    │    Commands     │  ← User interacts with
    ├─────────────────┤
    │    Services     │  ← Business logic
    ├─────────────────┤
    │     Models      │  ← Data and utilities
    ├─────────────────┤
    │   Templates     │  ← Code generation
    └─────────────────┘
    // 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");
        }
    }
    // 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
            );
        }
    }
    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);
        }
    }
    // 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();
    }
    // 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();
        }
    }
    binder.map("MyNewService@wheels-cli")
        .to("wheels.cli.models.MyNewService")
        .asSingleton();
    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);
        }
    }
    component singleton {
        // Shared instance across commands
    }
    component {
        function createGenerator(required string type) {
            switch(arguments.type) {
                case "model":
                    return new ModelGenerator();
                case "controller":
                    return new ControllerGenerator();
            }
        }
    }
    component {
        function setStrategy(required component strategy) {
            variables.strategy = arguments.strategy;
        }
        
        function execute() {
            return variables.strategy.execute();
        }
    }
    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");
                });
                
            });
        }
    }
    function beforeAll() {
        // Create mock
        mockFileService = createEmptyMock("FileService");
        
        // Define behavior
        mockFileService.$("writeFile").$results(true);
        
        // Inject mock
        templateService.$property(
            propertyName = "fileService",
            mock = mockFileService
        );
    }
    // Good: Focused service
    component name="ValidationService" {
        function validateModel() {}
        function validateController() {}
    }
    
    // Bad: Mixed responsibilities
    component name="UtilityService" {
        function validateModel() {}
        function sendEmail() {}
        function generatePDF() {}
    }
    // Good: Injected dependency
    property name="fileService" inject="FileService@wheels-cli";
    
    // Bad: Direct instantiation
    variables.fileService = new FileService();
    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
            };
        }
    }
    component {
        property name="settings" inject="coldbox:modulesettings:wheels-cli";
        
        function getTimeout() {
            return variables.settings.timeout ?: 30;
        }
    }
    // In service
    announce("wheels-cli:modelGenerated", {model: modelName});
    
    // In listener
    function onModelGenerated(event, data) {
        // React to event
    }
    component {
        property name="validationService" inject="ValidationService@wheels-cli";
        
        function generateModel() {
            // Validate first
            if (!validationService.isValidModelName(name)) {
                throw("Invalid model name");
            }
            // Continue generation
        }
    }
    // models/plugins/MyServicePlugin.cfc
    component implements="IServicePlugin" {
        
        function enhance(required component service) {
            // Add new method
            arguments.service.myNewMethod = function() {
                return "Enhanced!";
            };
        }
    }
    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();
        }
    }
    function getTemplateService() {
        if (!structKeyExists(variables, "templateService")) {
            variables.templateService = getInstance("TemplateService@wheels-cli");
        }
        return variables.templateService;
    }
    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];
        }
    }
    function analyzeLargeCodebase() {
        thread name="analysis-#createUUID()#" {
            // Long running analysis
        }
    }
    component {
        property name="log" inject="logbox:logger:{this}";
        
        function doSomething() {
            log.debug("Starting doSomething with args: #serializeJSON(arguments)#");
            // ... logic ...
            log.debug("Completed doSomething");
        }
    }
    # In CommandBox REPL
    repl> getInstance("TemplateService@wheels-cli")
    repl> getMetadata(getInstance("TemplateService@wheels-cli"))
    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 6 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

    type

    Type of test: model, controller, view, unit, integration, api

    Required

    target

    Name of the component/object to test

    Required

    Options

    Option
    Description
    Default

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

    Test Types

    The command generates different test structures based on the type:

    Type
    Location
    Purpose
    Testing Method

    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

    Examples

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

    With factory pattern for test data

    Output: tests/specs/models/UserSpec.cfc

    Generated Code:

    Key Features:

    • Validation testing with model().new() and valid()

    • Association testing

    • CRUD operations: model().create(), findByKey(), save(), delete() (with --crud)

    • Factory pattern for reusable test data (with --factory)

    Controller Tests

    Output: tests/specs/controllers/UsersControllerSpec.cfc

    Generated Code:

    With CRUD actions

    Output: tests/specs/controllers/UsersControllerSpec.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:

    Key Features:

    • HTTP-based testing using cfhttp()

    • Tests for index, show, create, update, delete actions (with --crud)

    • Response status code assertions

    • Content verification

    View Tests

    Output: tests/specs/views/users/editViewSpec.cfc

    Generated Code:

    Key Features:

    • HTTP-based rendering tests

    • Status code verification

    • HTML content assertions

    Unit Tests

    Output: tests/specs/unit/OrderProcessorSpec.cfc

    Generated Code:

    With mocking examples

    Output: tests/specs/unit/OrderProcessorSpec.cfc

    Additional Mock Test:

    Key Features:

    • Direct component instantiation

    • Edge case testing

    • Error handling tests

    • MockBox examples (with --mock)

    Integration Tests

    Output: tests/specs/integration/CheckoutFlowIntegrationSpec.cfc

    Generated Code:

    Key Features:

    • Multi-step workflow testing

    • Complete user journey via HTTP

    • Performance timing assertions

    API Tests

    Output: tests/specs/integration/api/UsersAPISpec.cfc

    Generated Code:

    Key Features:

    • JSON request/response handling with serializeJSON() and deserializeJSON()

    • Content-Type and Accept headers

    • Authentication testing placeholders

    • RESTful status code assertions (200, 201, 401, etc.)

    Additional Options

    Generated Test Structure

    All generated tests include:

    • TestBox 6 BDD Syntax: Modern describe() and it() syntax

    • Lifecycle Methods: beforeAll(), beforeEach(), afterEach() hooks

    • Proper Testing Patterns:

      • Models: model().new(), model().create(), findByKey()

      • Controllers/Views/Integration: cfhttp() with status code assertions

      • APIs: JSON serialization with proper headers

    • Helpful Placeholders: Comments guiding implementation

    Running Tests

    Best Practices

    1. Fill in Test Attributes: Replace // Add test attributes comments 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

    • wheels test run - Run tests

    • Testing Guide - Testing documentation

    • TestBox Documentation - TestBox framework docs

    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 addRoute() function has been removed in Wheels 2.0 in favor of a new routing API. See the Routing chapter for information about the new RESTful routing system.

    A limited version of the "wildcard" route ([controller]/[action]/[key]) is available as [controller]/[action]) if you use the new wildcard() mapper method:

    By default, this is limited to GET requests for security reasons.

    Cross-Site Request Forgery (CSRF) Protection

    It is strongly recommended that you enable Wheels 2.0's built-in CSRF protection.

    For many applications, you need to follow these steps:

    1. In controllers/Controller.cfc, add a call to protectsFromForgery() to the config method.

    2. Add a call to the csrfMetaTags() helper in your layouts' <head> sections.

    3. Configure any AJAX calls that POST data to your application to pass the authenticityToken from the <meta>tags generated by as an X-CSRF-TOKEN HTTP header.

    4. Update your route definitions to enforce HTTP verbs on actions that manipulate data (get, post, patch, delete, etc.)

    5. Make sure that forms within the application are POSTing data to the actions that require post, patch, and delete verbs.

    See documentation for the CSRF Protection Plugin for more information.

    Note: If you had previously installed the CSRF Protection plugin, you may remove it and rely on the functionality included in the Wheels 2 core.

    Database Migrations

    If you have previously been using the dbmigrate plugin, you can now use the inbuilt version within the Wheels 2 core.

    Database Migration files in /db/migrate/ should now be moved to /app/migrator/migrations/ and extend wheels.migrator.Migration, not plugins.dbmigrate.Migration which can be changed with a simple find and replace. Note: Oracle is not currently supported for Migrator.

    Upgrading to 1.4.x

    1. Replace the wheels folder with the new one from the 1.4 download.

    2. Replace URL rewriting rule files – i.e, .htaccess, web.config, IsapiRewrite.ini

    In addition, if you're upgrading from an earlier version of Wheels, we recommend reviewing the instructions from earlier reference guides below.

    Upgrading to 1.3.x

    If you are upgrading from Wheels 1.1.0 or newer, follow these steps:

    1. Replace the wheels folder with the new one from the 1.3 download.

    2. Replace the root root.cfm file with the new one from the 1.3 download.

    3. Remove the <cfheader> calls from the following files:

      • events/onerror.cfm

      • events/onmaintenance.cfm

      • events/onmissingtemplate.cfm

    In addition, if you're upgrading from an earlier version of Wheels, we recommend reviewing the instructions from earlier reference guides below.

    Note: To accompany the newest 1.1.x releases, we've highlighted the changes that are affected by each release in this cycle.

    Upgrading to 1.1.x

    If you are upgrading from Wheels 1.0 or newer, the easiest way to upgrade is to replace the wheels folder with the new one from the 1.1 download. If you are upgrading from an earlier version, we recommend reviewing the steps outlined in Upgrading to Wheels 1.0.

    Note: To accompany the newest 1.1.x releases, we've highlighted the changes that are affected by each release in this cycle.

    Plugin Compatibility

    Be sure to review your plugins and their compatibility with your newly-updated version of Wheels. Some plugins may stop working, throw errors, or cause unexpected behavior in your application.

    Supported System Changes

    • 1.1: The minimum Adobe ColdFusion version required is now 8.0.1.

    • 1.1: The minimum Railo version required is now 3.1.2.020.

    • 1.1: The H2 database engine is now supported.

    File System Changes

    • 1.1: The .htaccess file has been changed. Be sure to copy over the new one from the new version 1.1 download and copy any addition changes that you may have also made to the original version.

    Database Structure Changes

    • 1.1: By default, Wheels 1.1 will wrap database queries in transactions. This requires that your database engine supports transactions. For MySQL in particular, you can convert your MyISAM tables to InnoDB to be compatible with this new functionality. Otherwise, to turn off automatic transactions, place a call to set(transactionMode="none").

    • 1.1: Binary data types are now supported.

    CFML Code Changes

    Model Code

    • 1.1: Validations will be applied to some model properties automatically. This may cause unintended behavior with your validations. To turn this setting off, call set(automaticValidations=false) in config/settings.cfm.

    • 1.1: The class argument in hasOne(), hasMany(), and belongsTo() has been deprecated. Use the modelName argument instead.

    • 1.1: afterFind() callbacks no longer require special logic to handle the setting of properties in objects and queries. (The "query way" works for both cases now.) Because arguments will always be passed in to the method, you can't rely on StructIsEmpty() to determine if you're dealing with an object or not. In the rare cases that you need to know, you can now call isInstance() or isClass() instead.

    • 1.1: On create, a model will now set the updatedAt auto-timestamp to the same value as the createdAt timestamp. To override this behavior, call set(setUpdatedAtOnCreate=false) in config/settings.cfm.

    View Code

    • 1.1: Object form helpers (e.g. textField() and radioButton()) now automatically display a label based on the property name. If you left the label argument blank while using an earlier version of Wheels, some labels may start appearing automatically, leaving you with unintended results. To stop a label from appearing, use label=false instead.

    • 1.1: The contentForLayout() helper to be used in your layout files has been deprecated. Use the includeContent() helper instead.

    • 1.1: In production mode, query strings will automatically be added to the end of all asset URLs (which includes JavaScript includes, stylesheet links, and images). To turn off this setting, call set(assetQueryString=false) in config/settings.cfm.

    • 1.1: stylesheetLinkTag() and javaScriptIncludeTag() now accept external URLs for the source/sources argument. If you manually typed out these tags in previous releases, you can now use these helpers instead.

    • 1.1: flashMessages(), errorMessageOn(), and errorMessagesFor() now create camelCased class attributes instead (for example error-messages is now errorMessages). The same goes for the class attribute on the tag that wraps form elements with errors: it is now fieldWithErrors.

    Controller Code

    • 1.1.1: The if argument in all validation functions is now deprecated. Use the condition argument instead.

    Upgrading to 1.0.x

    Our listing of steps to take while upgrading your Wheels application from earlier versions to 1.0.x.

    Upgrading from an earlier version of 1.x? Then the upgrade path is simple. All you need to do is replace the wheels folder with the new wheels folder from the download.

    The easiest way to upgrade is to setup an empty website, deploy a fresh copy of Wheels 1.0, and then transfer your application code to it. When transferring, please make note of the following changes and make the appropriate changes to your code.

    Note: To accompany the newest 1.0 release, we've highlighted the changes that are affected by that release.

    Supported System Changes

    • 1.0: URL rewriting with IIS 7 is now supported.

    • 1.0: URL rewriting in a sub folder on Apache is now supported.

    • ColdFusion 9 is now supported.

    • Oracle 10g or later is now supported.

    • PostgreSQL is now supported.

    • Railo 3.1 is now supported.

    File System Changes

    • 1.0: There is now an app.cfm file in the config folder. Use it to set variables that you'd normally set in Application.cfc (i.e., this.name, this.sessionManagement, this.customTagPaths, etc.)

    • 1.0: There is now a web.config file in the root.

    • 1.0: There is now a Wheels.cfc file in the models folder.

    • 1.0: The Wheels.cfc file in the controllers folder has been updated.

    • 1.0: The IsapiRewrite4.ini and .htaccess files in the root have both been updated.

    • The controller folder has been changed to controllers.

    • The model folder has been changed to models.

    • The view folder has been changed to views.

    • Rename all of your CFCs in models and controllers to UpperCamelCase. So controller.cfc will become Controller.cfc, adminUser.cfc will become AdminUser.cfc, and so on.

    • All images must now be stored in the images folder, files in the files folder, JavaScript files in the javascripts folder, and CSS files in the stylesheets folder off of the root.

    Database Structure Changes

    • deletedOn, updatedOn, and createdOn are no longer available as auto-generated fields. Please change the names to deletedAt, updatedAt, and createdAt instead to get similar functionality, and make sure that they are of type datetime, timestamp, or equivalent.

    CFML Code Changes

    Config Code

    • 1.0: The action of the default route (home) has changed to wheels. The way configuration settings are done has changed quite a bit. To change a Wheels application setting, use the new set() function with the name of the Wheels property to change. (For example, <cfset set(dataSourceName="mydatasource")>.) To see a list of available Wheels settings, refer to the Configuration and Defaults chapter. Model Code

    • 1.0: The extends attribute in models/Model.cfc should now be Wheels.

    • findById() is now called findByKey(). Additionally, its id argument is now named key instead. For composite keys, this argument will accept a comma-delimited list.

    • When using a model's findByKey() or findOne() functions, the found property is no longer available. Instead, the functions return false if the record was not found.

    • A model's errorsOn() function now always returns an array, even if there are no errors on the field. When there are errors for the field, the array elements will contain a struct with name, fieldName, and message elements.

    • The way callbacks are created has changed. There is now a method for each callback event ( beforeValidation(), beforeValidationOnCreate(), etc.) that should be called from your model's init() method. These methods take a single argument: the method within your model that should be invoked during the callback event. See the chapter on Object Callbacks for an example.

    View Code

    • 1.0: The contents of the views/wheels folder have been changed.

    • The wrapLabel argument in form helpers is now replaced with labelPlacement. Valid values for labelPlacement are before, after, and around.

    • The first argument for includePartial() has changed from name to partial. If you're referring to it through a named argument, you'll need to replace all instances with partial.

    • The variable that keeps a counter of the current record when using includePartial() with a query has been renamed from currentRow to current.

    • There is now an included wheels view folder in views. Be sure to copy that into your existing Wheels application if you're upgrading.

    • The location of the default layout has changed. It is now stored at /views/layout.cfm. Now controller-specific layouts are stored in their respective view folder as layout.cfm. For example, a custom layout for www.domain.com/about would be stored at /views/about/layout.cfm.

    • In linkTo(), the id argument is now called key. It now accepts a comma-delimited list in the case of composite keys.

    • The linkTo() function also accepts an object for the key argument, in which case it will automatically extract the keys from it for use in the hyperlink.

    • The linkTo() function can be used only for controller-, action-, and route-driven links now. * The url argument has been removed, so now all static links should be coded using a standard "a" tag.

    Controller Code

    • 1.0: The extends attribute in controllers/Controller.cfc should now be Wheels. Multiple-word controllers and actions are now word-delimited by hyphens in the URL. For example, if your controller is called SiteAdmin and the action is called editLayout, the URL to access that action would be http://www.domain.com/site-admin/edit-layout.

    URL/Routing

    • The default route for Wheels has changed from [controller]/[action]/[id] to [controller]/[action]/[key]. This is to support composite keys. The params.id value will now only be available as params.key.

    • You can now pass along composite keys in the URL. Delimit multiple keys with a comma. (If you want to use this feature, then you can't have a comma in the key value itself).

    semver.org

    Named parameters: param=value (e.g., name=User, properties=name:string,email:string)

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

  • Param with value: --param=value equals param=value (e.g., --primaryKey=uuid)

  • Recommended: Use flags for options: wheels generate model name=User --properties="name:string,email:string" --migration

    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

    name

    Model name (singular)

    Required

    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

    Options

    Option
    Description
    Valid Values
    Default

    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

    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

    Property Types

    Type
    Database Type
    CFML Type

    string

    VARCHAR(255)

    string

    text

    TEXT

    string

    integer

    INTEGER

    numeric

    biginteger

    BIGINT

    numeric

    Examples

    Basic model

    Creates:

    • /models/User.cfc

    • Migration file (if enabled)

    Model with properties

    Model with associations

    Model without migration

    Complex model

    Invalid Model Names

    Invalid Property Types

    Important Validation Rules:

    • Model names must be singular and PascalCase (User, not Users or user)

    • Property format: name:type,name2:type2 (no spaces)

    • Relationship names must be PascalCase (User, not user)

    • Use valid property types from the table above

    Basic Model Examples

    Basic Model

    Model with Properties and Validations

    Model with Associations

    Common Validation Methods

    Common Association Methods

    Belongs To

    Has Many

    Has One

    Many to Many

    Common Callbacks

    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:

    See Also

    • wheels dbmigrate create table - Create migrations

    • wheels generate property - Add properties to existing models

    • wheels generate controller - Generate controllers

    • wheels scaffold - Generate complete CRUD

    wheels generate helper

    Generate global helper functions for use throughout the application.

    Synopsis

    CommandBox Parameter Syntax

    • Named parameters: param=value (e.g., name=Format, functions="truncate,slugify")

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

    • Param with value: --param=value equals param=value (e.g., --description="Formatting utilities")

    Recommended: Use named parameters with flags: wheels generate helper name=StringUtils --functions="truncate,highlight,slugify"

    Description

    The wheels generate helper command creates global helper function files that are automatically available throughout your application. Helper functions are useful for view formatting, data transformation, and reusable utility functions. All generated helpers are automatically included in /app/global/functions.cfm and become globally accessible.

    Arguments

    Argument
    Description
    Default

    Helper Name Validation

    • Helper name must be a valid CFML component name

    • Cannot contain spaces or special characters

    • Will automatically append "Helper" if not present (Format → FormatHelper)

    • Must be unique (cannot overwrite existing helper without --force)

    Options

    Option
    Description
    Valid Values
    Default

    Function Name Validation

    • Function names must be unique across all helper files

    • Must be valid CFML function names

    • Cannot duplicate existing global functions

    • Validation is skipped when using --force

    Examples

    Basic helper

    Creates:

    • /app/helpers/FormatHelper.cfm with default helper function

    • Adds include to /app/global/functions.cfm

    • /tests/specs/helpers/FormatHelperSpec.cfc test file

    Helper with multiple functions

    Creates a helper with three functions: truncate(), highlight(), slugify()

    Helper with description

    Adds description to the helper file header

    Force overwrite existing helper

    Overwrites existing FormatHelper.cfm without prompting

    Complex helper with smart templates

    The generator recognizes common function names and creates appropriate implementations:

    • currency → Currency formatting logic

    • date → Date formatting logic

    • truncate → Text truncation logic

    Generated Code Examples

    Basic Helper Structure

    Helper with Description

    Smart Template: Truncate Function

    Smart Template: Slugify Function

    Smart Template: Currency Function

    Automatic Include in functions.cfm

    When you generate a helper, it's automatically added to /app/global/functions.cfm:

    Using Helpers in Your Application

    In Controllers

    In Views

    In Models

    Smart Template Functions

    The generator recognizes common function name patterns and creates appropriate implementations:

    Function Name Pattern
    Generated Implementation

    Function Conflict Detection

    The generator validates that function names are unique across all helper files:

    Valid: Unique Function Names

    Invalid: Duplicate Function Names

    Override with Force

    Common Use Cases

    Text Formatting Helpers

    Date/Time Helpers

    URL/String Helpers

    Number Formatting Helpers

    Validation Helpers

    Best Practices

    1. Naming: Use descriptive helper names that indicate purpose (StringUtils, DateHelpers)

    2. Function Names: Keep function names concise and action-oriented (truncate, not truncateText)

    3. Global Scope: Remember helpers are global - use unique, descriptive names

    4. Options Pattern: Use options struct for flexibility instead of many parameters

    Common Patterns

    Options Struct Pattern

    Type-Safe Helpers

    Chainable Helpers

    Testing

    The generator automatically creates test files:

    Running Tests

    Error Handling

    Invalid Helper Name

    Function Name Conflict

    File Already Exists

    See Also

    • - Generate controllers

    • - Generate models

    • - Generate tests

    • - Customize generator templates

    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.

    wheels generate test [type] [target] [options]
    wheels g test [type] [target] [options]
    # Basic model test
    wheels generate test model 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
                });
            });
        }
    }
    # With CRUD operations
    wheels generate test model Product --crud
    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);
    });
    # Basic controller test
    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
    });
    # View rendering test
    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");
                });
            });
        }
    }
    # Basic unit test
    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
    });
    # End-to-end workflow test
    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");
                });
            });
        }
    }
    # API endpoint tests with JSON
    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
                });
            });
        }
    }
    # Force overwrite existing files
    wheels generate test model User --force
    
    # Generate and open in editor
    wheels generate test controller Products --crud --open
    # Run all tests
    wheels test run
    
    # Run specific test bundle
    wheels test run --testBundles=ProductSpec
    
    # Run with coverage
    wheels test run --coverage
    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();
    wheels generate model name=<modelName> [options]
    
    #Can also be used as:
    wheels g model name=<modelName> [options]
    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"
    # 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'
    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");
    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 type=model name=User
    wheels generate helper name=<helperName> [options]
    
    # Can also be used as:
    wheels g helper name=<helperName> [options]

    --force

    Overwrite existing files without prompting

    false

    --open

    Open the created file in default editor

    false

    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

    ""

    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

    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

    JS Confirm
    JS Disable
    csrfMetaTags()

    Other names

    Basic template with TODO comment

    Type Checking: Validate input types before processing

  • Documentation: Use hint attributes to document parameters and return values

  • Testing: Always create and run tests for helper functions

  • Validation: Check for function conflicts before creating helpers

  • name

    Helper name (e.g., Format, StringUtils)

    Required

    functions

    Comma-separated list of functions to generate

    Valid CFML function names

    "helperFunction"

    description

    it will be placed as comment at the top of helper file

    Any descriptive text

    ""

    force

    Overwrite existing files and skip validation

    true, false

    Contains "truncate"

    Text truncation with length and suffix options

    Contains "highlight"

    Search term highlighting with CSS class

    Contains "slugify"

    URL-friendly slug generation

    Contains "date"

    Date formatting with relative time support

    Contains "currency" or "money"

    Currency formatting with symbol

    Contains "format"

    Generic formatting based on data type

    wheels generate controller
    wheels generate model
    wheels generate test
    Template System Guide

    false

    Setup for Contributors

    Step 1. Create a box.json if not exist in Directory "cli/src"

    Make sure these properties exist: "name", "version", "slug" and "type"

    Step 2. Link the CLI Module

    Open CommandBox in the directory "cli/src" and link this directory as a module:

    This allows you to develop and test CLI commands locally.

    Basic Command Structure

    Step 3. Create Command File

    Create a new file in cli/src/commands/wheels/:

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

    • Service Architecture

    • Testing Guide

    • CommandBox Documentation

    • Contributing to Wheels CLI

    wheels generate helper name=Format
    wheels generate helper name=StringUtils --functions="truncate,highlight,slugify"
    wheels generate helper name=DateHelpers --description="Date formatting and manipulation utilities" --functions="formatDate,timeAgo"
    wheels generate helper name=Format --force
    wheels generate helper name=Format --functions="currency,date,truncate"
    /**
     * FormatHelper
     */
    <cfscript>
    
    	/**
    	 * Helper function helper function
    	 * @value.hint The value to process
    	 * @options.hint Additional options
    	 * @return Processed value
    	 */
    	public any function helperFunction(
    		required any value,
    		struct options = {}
    	) {
    		// TODO: Implement helperFunction logic
    		return arguments.value;
    	}
    
    </cfscript>
    /**
     * DateHelper
     * Date formatting and manipulation utilities
     */
    <cfscript>
    
    	/**
    	 * Format date helper function
    	 * @value.hint The value to process
    	 * @options.hint Additional options
    	 * @return Processed value
    	 */
    	public any function formatDate(
    		required any value,
    		struct options = {}
    	) {
    		// Format date with relative time support
    		if (!isDate(arguments.value)) {
    			return arguments.value;
    		}
    
    		local.format = arguments.options.format ?: "medium";
    
    		switch(local.format) {
    			case "relative":
    				return timeAgoInWords(arguments.value);
    			case "short":
    				return dateFormat(arguments.value, "m/d/yy");
    			case "long":
    				return dateFormat(arguments.value, "mmmm d, yyyy");
    			default:
    				return dateFormat(arguments.value, "mmm d, yyyy");
    		}
    	}
    
    </cfscript>
    /**
     * Truncate helper function
     * @value.hint The value to process
     * @options.hint Additional options
     * @return Processed value
     */
    public any function truncate(
    	required any value,
    	struct options = {}
    ) {
    	// Truncate text to specified length
    	local.length = arguments.options.length ?: 100;
    	local.suffix = arguments.options.suffix ?: "...";
    
    	if (len(arguments.value) <= local.length) {
    		return arguments.value;
    	}
    
    	return left(arguments.value, local.length - len(local.suffix)) & local.suffix;
    }
    /**
     * Slugify helper function
     * @value.hint The value to process
     * @options.hint Additional options
     * @return Processed value
     */
    public any function slugify(
    	required any value,
    	struct options = {}
    ) {
    	// Convert text to URL-friendly slug
    	local.slug = lCase(trim(arguments.value));
    
    	// Replace spaces with hyphens
    	local.slug = reReplace(local.slug, "\s+", "-", "all");
    
    	// Remove non-alphanumeric characters except hyphens
    	local.slug = reReplace(local.slug, "[^a-z0-9\-]", "", "all");
    
    	// Remove multiple consecutive hyphens
    	local.slug = reReplace(local.slug, "\-+", "-", "all");
    
    	// Trim hyphens from start and end
    	local.slug = reReplace(local.slug, "^\-|\-$", "", "all");
    
    	return local.slug;
    }
    /**
     * Currency helper function
     * @value.hint The value to process
     * @options.hint Additional options
     * @return Processed value
     */
    public any function currency(
    	required any value,
    	struct options = {}
    ) {
    	// Format currency values
    	if (!isNumeric(arguments.value)) {
    		return arguments.value;
    	}
    
    	local.currency = arguments.options.currency ?: "USD";
    	local.symbol = arguments.options.symbol ?: "$";
    
    	return local.symbol & numberFormat(arguments.value, "0.00");
    }
    <cfscript>
    //=====================================================================
    //=     Global Functions
    //=====================================================================
    include "../helpers/FormatHelper.cfm";
    include "../helpers/StringUtilsHelper.cfm";
    include "../helpers/DateHelper.cfm";
    </cfscript>
    component extends="Controller" {
    
    	function index() {
    		users = model("User").findAll();
    
    		// Use helper function
    		for (user in users) {
    			user.displayName = truncate(user.bio, {length: 50});
    		}
    	}
    
    }
    <cfparam name="post">
    <cfoutput>
    	<article>
    		<h1>#post.title#</h1>
    		<p class="meta">
    			Posted #formatDate(post.createdAt, {format: "relative"})# by #post.author#
    		</p>
    		<div class="content">
    			#post.content#
    		</div>
    		<div class="permalink">
    			/posts/#slugify(post.title)#
    		</div>
    	</article>
    </cfoutput>
    component extends="Model" {
    
    	function init() {
    		beforeSave("generateSlug");
    	}
    
    	private function generateSlug() {
    		if (!StructKeyExists(this, "slug") || !len(this.slug)) {
    			this.slug = slugify(this.title);
    		}
    	}
    
    }
    # First helper
    wheels generate helper name=String --functions="truncate,slugify"
    
    # Second helper (different functions)
    wheels generate helper name=Format --functions="currency,date"
    # Success: No conflicts
    # First helper
    wheels generate helper name=String --functions="truncate"
    
    # Second helper (same function)
    wheels generate helper name=Text --functions="truncate"
    # Error: The following function(s) already exist in helper files: truncate (in StringHelper.cfm)
    # Overwrite with force flag (skips validation)
    wheels generate helper name=Text --functions="truncate" --force=true
    # Success: File created (may cause global function conflicts)
    wheels generate helper name=TextFormat --functions="truncate,excerpt,wordCount,capitalize"
    wheels generate helper name=DateHelpers --functions="formatDate,timeAgo,businessDays"
    wheels generate helper name=URLHelpers --functions="slugify,urlEncode,sanitizeFilename"
    wheels generate helper name=NumberFormat --functions="currency,percentage,fileSize"
    wheels generate helper name=ValidationHelpers --functions="isEmail,isPhone,isURL"
    public any function formatText(
    	required any value,
    	struct options = {}
    ) {
    	// Extract options with defaults
    	local.maxLength = arguments.options.maxLength ?: 100;
    	local.suffix = arguments.options.suffix ?: "...";
    	local.preserveWords = arguments.options.preserveWords ?: true;
    
    	// Process with options
    	// ...
    }
    public string function formatCurrency(
    	required any value,
    	struct options = {}
    ) {
    	// Validate input
    	if (!isNumeric(arguments.value)) {
    		throw(type="InvalidArgument", message="Value must be numeric");
    	}
    
    	// Process
    	return dollarFormat(arguments.value);
    }
    public string function sanitize(required string value) {
    	return trim(htmlEditFormat(arguments.value));
    }
    
    // Usage: displayText = sanitize(userInput)
    // /tests/specs/helpers/FormatHelperSpec.cfc
    component extends="wheels.Test" {
    
    	function setup() {
    		// Global helpers are automatically included
    	}
    
    	function test_truncate() {
    		// Test truncate function
    		local.input = "test value";
    		local.result = truncate(local.input);
    
    		assert(isDefined("local.result"), "Function should return a value");
    	}
    
    }
    wheels test run
    wheels generate helper name="My Helper"
    # Error: Invalid helper name: Cannot contain spaces
    wheels generate helper name=Utils --functions="truncate"
    # Error: The following function(s) already exist in helper files: truncate (in StringHelper.cfm)
    wheels generate helper name=Format
    # Error: Helper already exists: FormatHelper.cfm. Use force=true to overwrite.
    {
        "name": "wheels-cli",
        "version": "3.0.0-SNAPSHOT",
        "slug": "wheels-cli",
        "type": "modules"
    }
    box package link --force
    // 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
        ) {
            // Reconstruct arguments for handling -- prefixed options
            arguments = reconstructArgs(argStruct=arguments);
    
            // 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") {
            arguments = reconstructArgs(argStruct=arguments);
    
            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) {
            arguments = reconstructArgs(argStruct=arguments);
    
            var analysisService = application.wirebox.getInstance("AnalysisService@wheels-cli");
            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!");
            }
        }
        
    }

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

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

    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.

    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:

    Example:

    Creating Migrations

    Generate Migration Commands

    Migration Structure

    Basic migration template:

    Table Operations

    Creating Tables

    Table Options

    Dropping Tables

    Column Operations

    Adding Columns

    Modifying Columns

    Renaming Columns

    Removing Columns

    Index Operations

    Creating Indexes

    Removing Indexes

    Foreign Keys

    Adding Foreign Keys

    Removing Foreign Keys

    Data Migrations

    Inserting Data

    Updating Data

    Removing Data

    Advanced Migrations

    Conditional Migrations

    Using Raw SQL

    Environment-Specific

    Running Migrations

    Basic Commands

    Migration Workflow

    1. Create migration

    2. Edit migration file

    3. Test migration

    4. Commit and share

    Best Practices

    1. Always Use Transactions

    2. Make Migrations Reversible

    3. One Change Per Migration

    4. Test Migrations Thoroughly

    5. Never Modify Completed Migrations

    Common Patterns

    Adding Non-Nullable Column

    Renaming Table with Foreign Keys

    Safe Column Removal

    Troubleshooting

    Migration Failed

    Stuck Migration

    Performance Issues

    Integration with CI/CD

    Pre-deployment Check

    Automated Deployment

    See Also

    • - Migration command reference

    • - Schema import/export

    • - Generate models with migrations

    • - Testing migrations

    Provide connection details (JDBC driver, connection string, username, password).
  • Configure optional features like connection pooling and validation queries.

  • 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

    ,
    textField
    , etc. However, you need to escape data that is displayed directly onto the page without a Wheels helper.

    id

    int

    auto increment

    username

    varchar(100)

    email

    varchar(255)

    passwd

    varchar(15)

    users

    GET

    /users

    Lists users

    newUsers

    GET

    /users/new

    Display a form for creating a user record

    users

    POST

    /users

    conventions
    startFormTag()
    linkTo()
    linkTo()
    endFormTag()
    textField()
    passwordField()
    submitTag()
    textField()
    passwordField()
    model()
    new()
    create()
    redirectTo()
    findAll()
    linkTo()
    linkTo()
    update()
    using the Flash
    findByKey()
    delete()

    Form posts a new user record to be created

    wheels dbmigrate commands
    Database Schema
    Model Generation
    Testing Guide
    /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"
        };
    }
    /config/routes.cfm
    mapper()
        .wildcard()
        .root(method = "get")
    .end();
    /config/routes.cfm
    mapper()
        .resources("users")
        .wildcard()
        .root(method = "get")
    .end();
    /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."
        );
    }
    [YYYYMMDDHHmmss]_[description].cfc
    20240125143022_create_users_table.cfc
    20240125143523_add_email_to_users.cfc
    # 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
    component extends="wheels.migrator.Migration" {
        
        function up() {
            transaction {
                // Apply changes
            }
        }
        
        function down() {
            transaction {
                // Reverse changes
            }
        }
        
    }
    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();
        }
    }
    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();
        }
    }
    function down() {
        transaction {
            dropTable("products");
        }
    }
    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();
        }
    }
    function up() {
        transaction {
            changeColumn(
                table="products",
                column="price",
                type="decimal",
                precision=12,
                scale=2,
                allowNull=false,
                default=0
            );
        }
    }
    function up() {
        transaction {
            renameColumn(
                table="users",
                column="email_address",
                newName="email"
            );
        }
    }
    function up() {
        transaction {
            removeColumn(table="users", column="deprecated_field");
            
            // Multiple columns
            t = changeTable("products");
            t.removeColumn("oldPrice");
            t.removeColumn("legacyCode");
            t.update();
        }
    }
    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();
        }
    }
    function down() {
        transaction {
            removeIndex(table="users", name="idx_users_email");
            
            // Or by column
            removeIndex(table="products", column="sku");
        }
    }
    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();
        }
    }
    function down() {
        transaction {
            removeForeignKey(
                table="orders",
                name="fk_orders_users"
            );
        }
    }
    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");
        }
    }
    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
            ");
        }
    }
    function down() {
        transaction {
            removeRecord(
                table="roles",
                where="name = 'temp_role'"
            );
        }
    }
    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");
            }
        }
    }
    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
            ");
        }
    }
    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"
                    );
                }
            }
        }
    }
    # 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
    wheels dbmigrate create table orders
    // Edit /app/migrator/migrations/[timestamp]_create_orders_table.cfc
    # Run migration
    wheels dbmigrate latest
    
    # Verify
    wheels dbmigrate info
    
    # Test rollback
    wheels dbmigrate down
    git add db/migrate/
    git commit -m "Add orders table migration"
    function up() {
        transaction {
            // All operations in transaction
            // Rollback on any error
        }
    }
    function up() {
        transaction {
            addColumn(table="users", column="nickname", type="string");
        }
    }
    
    function down() {
        transaction {
            removeColumn(table="users", column="nickname");
        }
    }
    # 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
    # Test up
    wheels dbmigrate latest
    
    # Test down
    wheels dbmigrate down
    
    # Test up again
    wheels dbmigrate up
    # Bad: Editing existing migration
    # Good: Create new migration to fix issues
    wheels dbmigrate create blank fix_orders_status_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);
        }
    }
    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"
            );
        }
    }
    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");
        }
    }
    # Check error
    wheels dbmigrate info
    
    # Fix migration file
    # Retry
    wheels dbmigrate latest
    -- Manually fix schema_migrations table
    DELETE FROM schema_migrations WHERE version = '20240125143022';
    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)");
        }
    }
    #!/bin/bash
    # Check for pending migrations
    if wheels dbmigrate info | grep -q "pending"; then
        echo "Pending migrations detected!"
        wheels dbmigrate info
        exit 1
    fi
    # .github/workflows/deploy.yml
    - name: Run migrations
      run: |
        wheels dbmigrate latest
        wheels dbmigrate info

    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, H2, and SQLite 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

    Options

    Option
    Type
    Default
    Description

    Examples:

    Examples

    Basic Usage

    Create database using default datasource:

    General Examples

    Database-Specific Guides

    H2 Database (Embedded)

    Characteristics:

    • 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

    Example Commands:


    SQLite Database (File-based)

    Characteristics:

    • Lightweight file-based database - serverless and zero-configuration

    • Database file created immediately (unlike H2's lazy creation)

    • Creates database file with metadata table: wheels_metadata

    • Database stored at: ./db/database_name.db

    Example Commands:

    Output Example:

    Common Issues:

    • "Could not delete existing database file": The database file is locked. Stop the server (box server stop) and close any database tools.

    • "File permission error": Ensure write permissions on the application root directory.

    • "Database file was not created": Check disk space and directory permissions.


    MySQL/MariaDB

    Characteristics:

    • 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

    Example Commands:

    Output Example:


    PostgreSQL

    Characteristics:

    • 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

    Example Commands:

    Special Notes:

    • When using --force, the command automatically terminates active connections before dropping the database

    • Check pg_hba.conf if you encounter authentication issues


    SQL Server (MSSQL)

    Characteristics:

    • Creates database with default settings

    • Connects to master system database

    • Supports Microsoft SQL Server JDBC driver

    • Default port: 1433

    Example Commands:


    Oracle Database

    Characteristics:

    • 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

    Example Commands:

    Oracle JDBC Driver Installation:

    If you see "Driver not found" error, you need to manually install the Oracle JDBC driver:

    1. Download the driver from

      • Download ojdbc11.jar or ojdbc8.jar

    2. Place the JAR file in CommandBox's JRE library directory:

    Common Oracle Errors:

    • "Invalid Oracle identifier": Database name contains hyphens. Use underscores instead.

    • "ORA-65096: common user must start with C##": Either use C##MYAPP as the database name or grant additional privileges to the connecting user.

    • "ORA-28014: cannot drop administrative user": Don't use system usernames (SYS, SYSTEM, etc.).

    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.

    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, not applicable for H2/SQLite)

    3. Network Access: The database server must be accessible (not applicable for H2/SQLite file-based databases)

    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: See the section above for detailed installation instructions.

    "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

    Oracle-Specific Errors

    For Oracle errors, see the section for detailed information on:

    • "Invalid Oracle identifier": Use underscores instead of hyphens

    • "ORA-65096: common user must start with C##": Use C## prefix or grant privileges

    • "ORA-28014: cannot drop administrative user": Don't use system usernames (SYS, SYSTEM, etc.)

    SQLite-Specific Errors

    For SQLite errors, see the section for detailed information on:

    • "Could not delete existing database file": Database is locked. Stop server and close database tools

    • "File permission error": Check write permissions on application root

    • "Database file was not created": Verify disk space and permissions

    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,

    What It Extracts:

    • Database driver type (MySQL, PostgreSQL, MSSQL, Oracle, H2)

    • Connection details:

      • Host and port

      • Database name

    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 generate api-resource

    Generate a complete RESTful API controller with advanced features like pagination, filtering, sorting, and authentication.

    Synopsis

    Description

    The wheels generate api-resource

    wheels db create [--datasource=<name>] [--environment=<env>] [--database=<dbname>] [--dbtype=<type>] [--force]

    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

  • dbtype

    string

    Auto-detected

    Database type: h2, sqlite, mysql, postgres, mssql, oracle. If not specified, the command will prompt you to select a type when creating a new datasource.

    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.

    Ideal for development and testing

  • JDBC Driver included with Lucee/CommandBox

  • Automatically creates db directory if it doesn't exist

  • No username/password required (file-based authentication)

  • No host/port configuration needed

  • JDBC driver: org.sqlite.JDBC (org.xerial.sqlite-jdbc bundle v3.47.1.0)

  • Creates auxiliary files during operation:

    • database.db-wal (Write-Ahead Log)

    • database.db-shm (Shared Memory)

    • database.db-journal (Rollback Journal)

  • Use absolute paths - paths are stored absolutely in configuration

  • Ideal for development, testing, prototyping, and portable applications

  • Limitations: Single writer, not recommended for high-concurrency production

  • Default port: 3306

  • JDBC Driver included with Lucee/CommandBox

  • Default port: 5432

  • JDBC Driver included with Lucee/CommandBox

  • Default username: sa

  • JDBC Driver included with Lucee/CommandBox

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

  • Requires manual JDBC driver installation (see below)

  • Windows: path\to\CommandBox\jre\lib\

  • Mac/Linux: /usr/local/lib/CommandBox/jre/lib/

  • Restart CommandBox completely:

    • Important: Close ALL CommandBox instances (don't just reload)

    • This ensures the JDBC driver is properly loaded

  • Verify installation:

    You should see: [OK] Driver found: oracle.jdbc.OracleDriver

  • JDBC Drivers:
    • MySQL, PostgreSQL, MSSQL, H2, and SQLite drivers are included with CommandBox/Lucee by default

    • Oracle requires manual driver installation - see Oracle Database section for details

  • File Permissions (SQLite/H2 only): Write permissions required in application root directory

  • For PostgreSQL: pg_hba.conf authentication issues
  • For Oracle: TNS listener not running or incorrect SID

  • DB_PASSWORD=MyPass123!
  • Solves the issue where app.cfm contains unresolved placeholders like ##this.env.DB_HOST##

  • Datasource definitions in /config/app.cfm

    • Falls back to parsing connection strings if .env file doesn't exist

    • Maintains backward compatibility

  • Environment-specific settings: /config/[environment]/settings.cfm

    • Detects datasource name from set(dataSourceName="...")

  • General settings: /config/settings.cfm

    • Global datasource configuration

  • Username and password

  • Oracle SID (if applicable)

  • 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

    Oracle's official website
    Oracle Database
    Oracle Database
    SQLite Database
    wheels db drop
    wheels db setup
    wheels dbmigrate latest

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

    wheels db create datasource=myOracleDS
    # 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
    
    # Specify database type
    wheels db create --dbtype=postgres --database=myapp_dev
    
    # Force recreation
    wheels db create --force
    wheels db create
    # Using existing datasource
    wheels db create datasource=myapp_dev
    
    # Specify environment
    wheels db create --environment=testing
    
    # Custom database name
    wheels db create --database=myapp_v2
    
    # Force recreation (drop and recreate)
    wheels db create --force
    # Basic H2 database
    wheels db create --dbtype=h2 --database=myapp_dev
    
    # With specific environment
    wheels db create --dbtype=h2 --database=myapp_test --environment=testing
    
    # Force recreate
    wheels db create --dbtype=h2 --database=myapp_dev --force
    # Basic SQLite database
    wheels db create --dbtype=sqlite --database=myapp_dev
    
    # Force recreate SQLite database
    wheels db create --dbtype=sqlite --database=myapp_dev --force
    
    # With specific environment
    wheels db create --dbtype=sqlite --database=myapp_test --environment=testing
    [OK] SQLite JDBC driver loaded
    [OK] Database connection established
    [OK] Database schema initialized
    [OK] Database file created: D:\MyApp\db\myapp_dev.db
    [OK] File size: 16384 bytes
    
    SQLite database created successfully!
    # Basic MySQL database
    wheels db create --dbtype=mysql --database=myapp_dev
    
    # With specific environment
    wheels db create --dbtype=mysql --database=myapp_production --environment=production
    
    # With custom datasource name
    wheels db create --dbtype=mysql --database=myapp_test --datasource=test_db --environment=testing
    
    # Force recreate
    wheels db create --dbtype=mysql --database=myapp_dev --force
    ==================================================================
      Database Creation Process
    ==================================================================
      Datasource:         myapp_dev
      Environment:        development
    ------------------------------------------------------------------
      Database Type:      MySQL
      Database Name:      myapp_dev
    ------------------------------------------------------------------
    
    >> Initializing MySQL database creation...
      [OK] Driver found: com.mysql.cj.jdbc.Driver
      [OK] Connected successfully to MySQL server!
    
    >> Creating MySQL database 'myapp_dev'...
      [OK] Database 'myapp_dev' created successfully!
    
    >> Verifying database creation...
      [OK] Database 'myapp_dev' verified successfully!
    ------------------------------------------------------------------
      [OK] MySQL database creation completed successfully!
    # Basic PostgreSQL database
    wheels db create --dbtype=postgres --database=myapp_dev
    
    # With specific environment
    wheels db create --dbtype=postgres --database=myapp_staging --environment=staging
    
    # Force recreate (automatically terminates active connections)
    wheels db create --dbtype=postgres --database=myapp_dev --force
    
    # Custom datasource
    wheels db create --dbtype=postgres --database=myapp_prod --datasource=prod_db --environment=production
    # Basic SQL Server database
    wheels db create --dbtype=mssql --database=myapp_dev
    
    # With specific environment
    wheels db create --dbtype=mssql --database=myapp_production --environment=production
    
    # With custom datasource
    wheels db create --dbtype=mssql --database=MyAppDB --datasource=production_ds
    
    # Force recreate
    wheels db create --dbtype=mssql --database=myapp_dev --force
    # Basic Oracle database (creates user/schema)
    wheels db create --dbtype=oracle --database=myapp_dev
    
    # With specific environment
    wheels db create --dbtype=oracle --database=myapp_prod --environment=production
    
    # Force recreate (drops and recreates user)
    wheels db create --dbtype=oracle --database=myapp_dev --force
    
    # IMPORTANT: Use underscores, not hyphens
    wheels db create --dbtype=oracle --database=myapp_test  # ✓ Correct
    # wheels db create --dbtype=oracle --database=myapp-test  # ✗ Wrong! Will fail
    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. SQL Server (MSSQL)
      4. Oracle
      5. H2
      6. SQLite
    
    Select database type [1-6]: 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
    wheels db create --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
    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

    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:

    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:

    Creates a simple controller with only basic CRUD operations.

    With Authentication

    Generate API controller with authentication:

    Includes Bearer token authentication that requires Authorization header for create, update, delete actions.

    Custom Version and Namespace

    Generate API controller with custom versioning:

    Creates /controllers/public/v2/Products.cfc

    Skip Model Generation

    Generate only the controller (model already exists):

    Complete Setup with Documentation

    Generate everything with API documentation:

    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

    Generates:

    Adding Routes

    After generating your API resource, add routes to /config/routes.cfm:

    Default Namespaced Routes

    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

    No Namespace

    If you used --namespace="":

    Feature Details

    Pagination

    When --pagination is enabled (default):

    Request:

    Response:

    Filtering

    When --filtering is enabled (default):

    Request:

    The generated controller includes a parseFilter() method with TODO comments for you to implement your filtering logic.

    Sorting

    When --sorting is enabled (default):

    Request:

    The - prefix indicates descending order.

    Authentication

    When --auth is enabled:

    Request:

    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

    Testing Your API

    Basic Requests

    With Filtering and Sorting

    With Authentication

    Example Responses

    Success with Pagination (200 OK):

    Validation Error (422):

    Not Found (404):

    Unauthorized (401):

    Customization

    Implementing Filter Logic

    Edit the generated parseFilter() method:

    Implementing Authentication

    Edit the generated isValidToken() method:

    Adding More Sortable Fields

    Edit the parseSort() 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

    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

    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

    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

    Model Testing

    Controller Testing

    Integration Testing

    Test Helpers

    Creating Test Factories

    Test Data Management

    Running Tests

    Basic Commands

    Watch Mode

    Filtering Tests

    Code Coverage

    Generate Coverage Report

    Coverage Configuration

    In tests/Application.cfc:

    Test Configuration

    Test Suite Configuration

    Environment Variables

    Testing Best Practices

    1. Test Organization

    2. Test Isolation

    3. Descriptive Tests

    4. AAA Pattern

    Continuous Integration

    GitHub Actions

    Pre-commit Hooks

    Debugging Tests

    Using Debug Output

    Interactive Debugging

    Performance Testing

    Load Testing

    Common Testing Patterns

    Testing Private Methods

    Mocking External Services

    Docker-Based Testing

    Wheels provides a comprehensive Docker environment for testing across multiple CFML engines and databases.

    Quick Start with Docker

    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

    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:

    Running Tests via Docker

    Database Testing

    Test against different databases by using the db parameter:

    See Also

    • - Test execution command

    • - Coverage generation

    • - Generate test files

    • - Complete TestBox guide

    wheels generate api-resource [name] [options]
    wheels g api-resource [name] [options]
    # Positional name only
    wheels generate api-resource products
    # 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
    # Using flag
    wheels generate api-resource products --auth
    # Using flags with values
    wheels generate api-resource products --version=v2 --namespace=public
    # Using flag
    wheels generate api-resource products --skipModel
    # Multiple flags
    wheels generate api-resource products --auth --docs
    wheels generate api-resource products --auth --pagination --filtering --sorting
    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) { /* ... */ }
    }
    // Add inside mapper() block
    namespace(name="api", function() {
        namespace(name="v1", function() {
            resources(name="products", except="new,edit");
        });
    });
    namespace(name="api", function() {
        namespace(name="v2", function() {
            resources(name="products", except="new,edit");
        });
    });
    resources(name="products", except="new,edit");
    curl "http://localhost:8080/api/v1/products?page=2&perPage=10"
    {
      "data": [ /* products */ ],
      "meta": {
        "pagination": {
          "page": 2,
          "perPage": 10,
          "total": 100,
          "pages": 10
        }
      }
    }
    curl "http://localhost:8080/api/v1/products?filter[name]=widget&filter[minPrice]=10"
    # Sort by name ascending, then price descending
    curl "http://localhost:8080/api/v1/products?sort=name,-price"
    curl -X POST http://localhost:8080/api/v1/products \
      -H "Authorization: Bearer YOUR_TOKEN_HERE" \
      -H "Content-Type: application/json" \
      -d '{"product":{"name":"Widget"}}'
    # 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
    # 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 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"}}'
    {
      "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
        }
      }
    }
    {
      "error": "Validation failed",
      "errors": [
        {
          "property": "name",
          "message": "This field is required"
        }
      ]
    }
    {
      "error": "Record not found"
    }
    {
      "error": "Unauthorized"
    }
    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 ") : "";
    }
    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;
    }
    private function parseSort(required string sort) {
        local.allowedFields = ["id", "name", "price", "category", "createdAt", "updatedAt"];
        // ... rest of method
    }

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

    404 Not Found, 422 Unprocessable Entity

    delete

    204 No Content

    404 Not Found

    auth failure

    -

    401 Unauthorized

    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

    Pre-flight Checks: Ensures all services are running before tests

  • Test History: Track test results over time

  • Docker Testing Guide - Detailed Docker testing documentation

    wheels test run
    wheels test coverage
    wheels generate test
    TestBox Documentation
    /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
    // 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");
                });
    
            });
        }
    
    }
    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");
                    });
                });
    
            });
        }
    
    }
    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);
                    });
                });
    
            });
        }
    
    }
    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");
                });
    
            });
        }
    
    }
    // 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);
        }
    
    }
    // 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
                );
            }
        }
    
    }
    # 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 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
    # 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"
    # 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/"
    this.coverage = {
        enabled: true,
        includes: ["models", "controllers"],
        excludes: ["tests", "wheels"],
        outputDir: expandPath("/tests/coverage/"),
        reportFormats: ["html", "json"]
    };
    // 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: {}
        };
    
    }
    # Set test environment
    export WHEELS_ENV=testing
    
    # Set test datasource
    export WHEELS_TEST_DATASOURCE=myapp_test
    
    # Enable verbose output
    export TESTBOX_VERBOSE=true
    tests/
    ├── unit/              # Fast, isolated tests
    │   ├── models/       # One file per model
    │   └── services/     # Service layer tests
    ├── integration/      # Tests with dependencies
    └── e2e/             # End-to-end tests
    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();
            }
        });
    
    });
    // 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
    });
    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
    });
    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
    #!/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
    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");
    });
    # 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
    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);
        });
    
    });
    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");
    });
    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);
    });
    # Start the TestUI and all test containers
    docker compose --profile all up -d
    
    # Access the TestUI
    open http://localhost:3000
    # 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
    # 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
    # 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

    wheels generate migration

    Generate database migration files using templates.

    Synopsis

    CommandBox Parameter Syntax

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

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

    • Param with value: --param=value equals param=value (e.g., --description="User management")

    Recommended: Use named parameters: wheels generate migration name=CreateUsersTable --create=users

    Description

    The wheels generate migration command creates database migration files using pre-built templates. Migrations provide version control for your database schema, allowing you to incrementally modify your database structure and roll back changes when needed. The command intelligently detects migration type from the name pattern and uses appropriate templates with transaction handling and error management.

    Arguments

    Argument
    Description
    Default

    Migration Name Conventions

    • Must start with a letter

    • Can contain letters, numbers, and underscores

    • Use PascalCase for readability

    • Should describe the migration action clearly

    Common Patterns:

    • Create[Table]Table - Creates a new table

    • Add[Column]To[Table] - Adds column(s) to existing table

    • Remove[Column]From[Table] - Removes column(s) from table

    Options

    Option
    Description
    Valid Values
    Default

    Migration Types and Templates

    The command automatically detects the migration type and uses the appropriate template:

    Type
    Template
    Detected Pattern
    Example

    Examples

    Create Table Migration

    Creates migration using create-table.txt template with transaction handling.

    Create Table with Explicit Table Name

    Forces create_table type and uses "users" as table name.

    Add Column Migration

    Creates migration using create-column.txt template.

    Remove Column Migration

    Creates migration using remove-column.txt template.

    Drop Table Migration

    Creates migration using remove-table.txt template.

    With Description

    Adds description as hint to the migration component.

    Blank/Custom Migration

    Creates blank migration for custom migration code.

    Force Overwrite

    Overwrites existing migration file.

    Generated Code Examples

    Create Table Migration (Using Template)

    Add Column Migration (Using Template)

    Blank Migration (Using Template)

    Smart Name Detection

    The command infers table names from migration name patterns:

    Pattern Detection Examples

    Migration Name
    Detected Type
    Inferred Table
    Template Used

    Template System

    All migrations use templates from /cli/src/templates/dbmigrate/:

    Available Templates

    • blank.txt - Empty migration with transaction wrapper

    • create-table.txt - Create table with timestamps

    • remove-table.txt - Drop table with reversible down()

    Template Customization

    You can override default templates by placing custom versions in /app/snippets/dbmigrate/:

    Template Variables

    Templates use placeholder variables that are replaced during generation:

    Variable
    Description
    Example

    Migration Workflow

    1. Generate Migration

    2. Edit Migration File

    Add columns to the migration:

    3. Check Migration Status

    4. Run Migration

    5. Rollback if Needed

    Column Types

    Common column types available in migrations:

    Method
    Database Type
    Example

    Best Practices

    1. Descriptive Names: Use clear, action-oriented migration names

    2. One Change Per Migration: Keep migrations focused on single changes

    3. Always Include down(): Make migrations reversible when possible

    4. Use Transactions: Templates include transactions by default

    Common Patterns

    Create Table with Relationships

    Add Multiple Columns

    Data Migration

    Error Handling

    Migration Already Exists

    Invalid Migration Name

    Migration Fails to Run

    Template Benefits

    Using templates provides several advantages:

    Transaction Safety - All operations wrapped in transactions Error Handling - Automatic try/catch with rollback Consistency - Same structure across all migrations Best Practices - Templates follow Wheels conventions Customizable - Override templates in /app/snippets/ Maintainability - Centralized template updates

    See Also

    • - Run migrations

    • - Check migration status

    • - Alternative table creation

    • - Alternative blank migration

    wheels generate migration name=<migrationName> [options]
    
    # Can also be used as:
    wheels g migration name=<migrationName> [options]
    Rename[OldColumn]To[NewColumn] - Renames a column
  • Change[Column]In[Table] - Modifies column type/properties

  • CreateIndexOn[Table] - Adds an index

  • RemoveIndexFrom[Table] - Removes an index

  • description

    Migration description (added as hint)

    Any descriptive text

    ""

    force

    Overwrite existing migration file

    true, false

    false

    Remove Column

    remove-column.txt

    Remove*From*

    RemoveAgeFromUsers

    Change Column

    change-column.txt

    Change*In*

    ChangeNameInUsers

    Rename Column

    rename-column.txt

    Rename*To*

    RenameFirstnameToFullname

    Add Index

    create-index.txt

    CreateIndexOn*

    CreateIndexOnUsersEmail

    Remove Index

    remove-index.txt

    RemoveIndexFrom*

    RemoveIndexFromUsers

    Blank

    blank.txt

    Any other pattern

    CustomMigration

    RemoveAgeFromUsers

    remove_column

    users

    remove-column.txt

    RenameFirstnameToFullname

    rename_column

    (needs --table)

    rename-column.txt

    ChangeStatusInOrders

    change_column

    orders

    change-column.txt

    CreateIndexOnUsersEmail

    create_index

    users

    create-index.txt

    CustomDataMigration

    blank

    N/A

    blank.txt

    create-column.txt - Add column with options
  • remove-column.txt - Remove column

  • change-column.txt - Modify column type/properties

  • rename-column.txt - Rename column

  • create-index.txt - Add index to table

  • remove-index.txt - Remove index from table

  • |force|

    Force table creation

    false

    |id|

    Auto-create ID column

    true

    |primaryKey|

    Primary key name

    id

    |allowNull|

    Allow NULL values

    true

    |limit|

    Column length limit

    255

    |default|

    Default value

    ""

    |unique|

    Unique index

    false

    t.decimal()

    DECIMAL

    t.decimal(columnNames='price', precision=10, scale=2)

    t.float()

    FLOAT

    t.float(columnNames='rating')

    t.boolean()

    BOOLEAN

    t.boolean(columnNames='active')

    t.date()

    DATE

    t.date(columnNames='birthdate')

    t.datetime()

    DATETIME

    t.datetime(columnNames='registeredAt')

    t.time()

    TIME

    t.time(columnNames='startTime')

    t.binary()

    BLOB

    t.binary(columnNames='avatar')

    t.timestamps()

    DATETIME

    Creates createdAt and updatedAt

    Test Rollbacks: Always test down() method works correctly

  • Index Performance Columns: Add indexes on frequently queried columns

  • Document Complex Migrations: Use description parameter for clarity

  • Avoid Data in Migrations: Keep migrations schema-only when possible

  • wheels generate model - Generate models

  • Database Migrations Guide - Migration best practices

  • Template System Guide - Customize templates

  • name

    Migration name (e.g., CreateUsersTable, AddEmailToUsers)

    Required

    create

    Table name to create (forces create_table type)

    Valid table name

    ""

    table

    Table name to modify (for column operations)

    Valid table name

    ""

    drop

    Table name to drop (forces remove_table type)

    Valid table name

    Create Table

    create-table.txt

    Create*Table

    CreateUsersTable

    Remove Table

    remove-table.txt

    Drop*Table, Remove*Table

    DropUsersTable

    Add Column

    create-column.txt

    Add*To*

    CreateUsersTable

    create_table

    users

    create-table.txt

    DropProductsTable

    remove_table

    products

    remove-table.txt

    AddEmailToUsers

    create_column

    users

    |DBMigrateDescription|

    Migration hint/description

    CreateUsersTable

    |tableName|

    Table name

    users

    |columnName|

    Column name

    email

    |columnType|

    Column type

    string

    t.string()

    VARCHAR(255)

    t.string(columnNames='email')

    t.text()

    TEXT

    t.text(columnNames='bio')

    t.integer()

    INTEGER

    t.integer(columnNames='age')

    t.biginteger()

    BIGINT

    t.biginteger(columnNames='views')

    wheels dbmigrate latest
    wheels dbmigrate info
    wheels dbmigrate create table
    wheels dbmigrate create blank

    ""

    AddEmailToUsers

    create-column.txt

    wheels generate migration name=CreateUsersTable
    wheels generate migration name=CreateUsersTable --create=users
    wheels generate migration name=AddEmailToUsers --table=users
    wheels generate migration name=RemoveAgeFromUsers --table=users
    wheels generate migration name=DropProductsTable
    wheels generate migration name=CreateUsersTable --description="User authentication table"
    wheels generate migration name=MigrateUserData --description="Move user data to new structure"
    wheels generate migration name=CreateUsersTable --force=true
    component extends="wheels.migrator.Migration" hint="CreateUsersTable" {
    
    	function up() {
    		transaction {
    			try {
    				t = createTable(name = 'users', force='false', id='true', primaryKey='id');
    				t.timestamps();
    				t.create();
    			} catch (any e) {
    				local.exception = e;
    			}
    
    			if (StructKeyExists(local, "exception")) {
    				transaction action="rollback";
    				Throw(errorCode = "1", detail = local.exception.detail, message = local.exception.message, type = "any");
    			} else {
    				transaction action="commit";
    			}
    		}
    	}
    
    	function down() {
    		transaction {
    			try {
    				dropTable('users');
    			} catch (any e) {
    				local.exception = e;
    			}
    
    			if (StructKeyExists(local, "exception")) {
    				transaction action="rollback";
    				Throw(errorCode = "1", detail = local.exception.detail, message = local.exception.message, type = "any");
    			} else {
    				transaction action="commit";
    			}
    		}
    	}
    
    }
    component extends="wheels.migrator.Migration" hint="AddEmailToUsers" {
    
    	function up() {
    		transaction {
    			try {
    				addColumn(table='users', columnType='string', columnName='column_name', default='', null='true', limit='255');
    			} catch (any e) {
    				local.exception = e;
    			}
    
    			if (StructKeyExists(local, "exception")) {
    				transaction action="rollback";
    				Throw(errorCode = "1", detail = local.exception.detail, message = local.exception.message, type = "any");
    			} else {
    				transaction action="commit";
    			}
    		}
    	}
    
    	function down() {
    		transaction {
    			try {
    				removeColumn(table='users', columnName='column_name');
    			} catch (any e) {
    				local.exception = e;
    			}
    
    			if (StructKeyExists(local, "exception")) {
    				transaction action="rollback";
    				Throw(errorCode = "1", detail = local.exception.detail, message = local.exception.message, type = "any");
    			} else {
    				transaction action="commit";
    			}
    		}
    	}
    
    }
    component extends="wheels.migrator.Migration" hint="MigrateUserData" {
    
    	function up() {
    		transaction {
    			try {
    				// your code goes here
    			} catch (any e) {
    				local.exception = e;
    			}
    
    			if (StructKeyExists(local, "exception")) {
    				transaction action="rollback";
    				Throw(errorCode = "1", detail = local.exception.detail, message = local.exception.message, type = "any");
    			} else {
    				transaction action="commit";
    			}
    		}
    	}
    
    	function down() {
    		transaction {
    			try {
    				// your code goes here
    			} catch (any e) {
    				local.exception = e;
    			}
    
    			if (StructKeyExists(local, "exception")) {
    				transaction action="rollback";
    				Throw(errorCode = "1", detail = local.exception.detail, message = local.exception.message, type = "any");
    			} else {
    				transaction action="commit";
    			}
    		}
    	}
    
    }
    # Copy template to customize
    cp cli/src/templates/dbmigrate/create-table.txt app/snippets/dbmigrate/create-table.txt
    
    # Edit your custom template
    # Wheels will automatically use your version
    wheels generate migration name=CreateUsersTable
    # File created at: /app/migrator/migrations/20250119123045_CreateUsersTable.cfc
    function up() {
        transaction {
            try {
                t = createTable(name = 'users', force='false', id='true', primaryKey='id');
                t.string(columnNames='firstName,lastName', allowNull=false);
                t.string(columnNames='email', allowNull=false, limit=100);
                t.boolean(columnNames='active', default=true);
                t.timestamps();
                t.create();
    
                // Add index on email
                addIndex(table='users', columnNames='email', unique=true);
            } catch (any e) {
                local.exception = e;
            }
    
            if (StructKeyExists(local, "exception")) {
                transaction action="rollback";
                Throw(errorCode = "1", detail = local.exception.detail, message = local.exception.message, type = "any");
            } else {
                transaction action="commit";
            }
        }
    }
    wheels dbmigrate info
    wheels dbmigrate latest
    wheels dbmigrate down
    function up() {
        transaction {
            try {
                t = createTable(name = 'posts', force='false', id='true');
                t.string(columnNames='title', allowNull=false);
                t.text(columnNames='content');
                t.references(name='userId', references='users');
                t.boolean(columnNames='published', default=false);
                t.timestamps();
                t.create();
    
                addIndex(table='posts', columnNames='userId');
            } catch (any e) {
                local.exception = e;
            }
            // Transaction handling...
        }
    }
    function up() {
        transaction {
            try {
                addColumn(table='users', columnType='string', columnName='phoneNumber', limit=20);
                addColumn(table='users', columnType='boolean', columnName='emailVerified', default=false);
                addColumn(table='users', columnType='datetime', columnName='lastLoginAt');
    
                addIndex(table='users', columnNames='phoneNumber');
            } catch (any e) {
                local.exception = e;
            }
            // Transaction handling...
        }
    }
    function up() {
        transaction {
            try {
                // Add new column
                addColumn(table='users', columnType='string', columnName='fullName');
    
                // Migrate data (use with caution)
                execute("UPDATE users SET fullName = CONCAT(firstName, ' ', lastName)");
    
                // Remove old columns
                removeColumn(table='users', columnName='firstName');
                removeColumn(table='users', columnName='lastName');
            } catch (any e) {
                local.exception = e;
            }
            // Transaction handling...
        }
    }
    wheels generate migration name=CreateUsersTable
    # Error: Migration file already exists. Use force=true to overwrite.
    
    # Solution:
    wheels generate migration name=CreateUsersTable --force=true
    wheels generate migration name="Create Users Table"
    # Error: Invalid migration name. Use only letters, numbers, and underscores.
    
    # Solution:
    wheels generate migration name=CreateUsersTable
    wheels dbmigrate latest
    # Error during migration execution
    
    # Check the migration file for syntax errors
    # Review database logs
    # Test the down() method
    wheels dbmigrate down

    wheels generate controller

    Generate a controller with actions and optional views.

    Synopsis

    CommandBox Parameter Syntax

    • Positional parameters: wheels generate controller Products (controller name)

    • Named parameters: param=value (e.g., name=Products, actions=index,show)

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

    • Params with value: --param=value equals param=value (e.g., --actions=index,show)

    Recommended Format:

    • Positional for name: wheels generate controller Products

    • Flags for options: wheels generate controller Products --crud --force

    Not Allowed:

    • Use --actions (plural) not --action (singular)

    • Don't mix positional and named parameters (causes errors)

    Description

    The wheels generate controller command creates a new controller CFC file with specified actions and optionally generates corresponding view files. It supports both traditional and RESTful controller patterns.

    Arguments

    Argument
    Description
    Default

    Options

    Option
    Description
    Default

    How It Works

    Decision Tree

    Common Use Cases

    What You Want
    Command
    Actions
    Views

    --crud vs --api

    Aspect
    --crud
    --api

    Examples

    Basic controller

    Creates: Products.cfc with index action and index.cfm view

    Controller with custom actions

    Creates: Products.cfc with 3 custom actions and 3 views

    CRUD controller (scaffold-style)

    Creates: Products.cfc with 7 CRUD actions + 5 views (index, show, new, edit, _form)

    API controller (no views)

    Creates: Orders.cfc with 5 API actions, no views

    Controller without views

    Creates: Products.cfc with 7 actions, no views

    Priority override example

    Creates: Products.cfc with only dashboard action (--actions overrides --crud)

    Generated Code

    Basic Controller

    Controller with Description

    RESTful Controller

    API Controller

    View Generation

    Views are automatically generated for non-API controllers:

    index.cfm

    Naming Conventions

    • Controller names: PascalCase, typically plural (Products, Users)

    • Action names: camelCase (index, show, createProduct)

    • File locations:

      • Controllers: /controllers/

    Routes Configuration

    Add routes in /config/routes.cfm:

    Traditional Routes

    RESTful Resources

    Testing

    Generate tests alongside controllers:

    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)

    Common Patterns

    Authentication Filter

    Pagination

    Search

    See Also

    • - Generate models

    • - Generate views

    • - Generate complete CRUD

    • - Generate controller tests

    wheels generate controller name=<controllerName> [options]
    
    #Can also be used as:
    wheels g controller name=<controllerName> [options]

    description

    Controller description comment

    ""

    force

    Overwrite existing files

    false

    Custom actions with views

    --actions=dashboard,export

    2

    2 (dashboard, export)

    Controller only (no views)

    --crud --noViews

    7

    None

    Use Case

    User-facing web apps

    Mobile apps, SPAs, integrations

  • Nested: /controllers/admin/Products.cfc

  • Views: /views/{controller}/

  • Use --actions when you need custom actions (HIGHEST PRIORITY - overrides --crud)

  • Implement proper error handling

  • Add authentication in config() method

  • Use filters for common functionality

  • name

    Name of the controller to create (usually plural)

    Required

    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

    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)

    Purpose

    Traditional web application

    API endpoints

    Actions

    7 (includes new, edit forms)

    5 (no form actions)

    Views

    5 scaffold-style views

    None

    Response

    HTML pages with forms

    JSON/XML data

    wheels generate model
    wheels generate view
    wheels scaffold
    wheels generate test

    1 (index)

    ACTION GENERATION:
    ├─ Has --actions? → Use those actions ONLY (highest priority, overrides --crud)
    ├─ 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
    wheels generate controller Products
    wheels generate controller Products --actions=dashboard,reports,export
    wheels generate controller Products --crud
    wheels generate controller Orders --api
    wheels generate controller Products --crud --noViews
    wheels generate controller Products --crud --actions=dashboard
    component extends="Controller" {
    
      /**
    	* Controller config settings
    	**/
    	function config() {
    
    	}
    
        /**
         * index action
         */
        function index() {
            // TODO: Implement index action
        }
    }
    /**
     * Handles user management operations
     */
    component extends="Controller" {
    
      /**
    	* Controller config settings
    	**/
    	function config() {
    
    	}
    
        /**
         * index action
         */
        function index() {
            // TODO: Implement index action
        }
    }
    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 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";
    	}
    
    }
    <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>
    <cfscript>
    mapper()
        .get(name="products", to="products##index")
        .get(name="product", to="products##show")
        .post(name="products", to="products##create")
        .wildcard()
    .end();
    </cfscript>
    <cfscript>
    mapper()
        .resources("products")
        .wildcard()
    .end();
    </cfscript>
    wheels generate controller name=products --crud
    wheels generate test controller name=products
    function config() {
        filters(through="authenticate", except="index,show");
    }
    
    private function authenticate() {
        if (!session.isLoggedIn) {
            redirectTo(controller="sessions", action="new");
        }
    }
    function index() {
        products = model("Product").findAll(
            page=params.page ?: 1,
            perPage=25,
            order="createdAt DESC"
        );
    }
    function index() {
        if (StructKeyExists(params, "q")) {
            products = model("Product").findAll(
                where="name LIKE :search OR description LIKE :search",
                params={search: "%#params.q#%"}
            );
        } else {
            products = model("Product").findAll();
        }
    }

    wheels docker init

    Initialize Docker configuration for your Wheels application with support for multiple databases, CF engines, production mode, and Nginx reverse proxy.

    Synopsis

    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

    Default Database Versions:

    • MySQL: 8.0

    • PostgreSQL: 15

    • MSSQL: 2019-latest

    Examples

    Basic initialization (MySQL with Lucee 6)

    Initialize with PostgreSQL

    Initialize with specific database version

    Initialize with Adobe ColdFusion

    Initialize with H2 embedded database

    Initialize with SQLite file-based database

    Initialize with Oracle

    Initialize with specific Oracle version

    Custom port

    Force overwrite existing files

    Production configuration

    Production with Nginx reverse proxy

    Development with Nginx on custom port

    Full custom configuration

    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

    Generated Files

    Development Dockerfile

    Production Dockerfile

    docker-compose.yml (Development with MySQL)

    docker-compose.yml (Production with Nginx)

    nginx.conf (Generated with --nginx)

    Database Configurations

    MySQL

    • Image: mysql:8.0 (default)

    • Port: 3306

    • Credentials: wheels/wheels

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

    Oracle

    • Image: gvenzl/oracle-free:latest (default - Oracle Database 23c Free)

    • Port: 1521

    • Credentials: wheels/wheels

    • SID: FREE

    H2

    • Embedded: No separate container needed

    • Extension: Automatically added to Lucee deployments via Dockerfile

    • Connection: Configured in application settings

    • Storage: Within application container filesystem at ./db/

    SQLite

    • Embedded: No separate container needed (file-based)

    • JDBC Driver: org.sqlite.JDBC (included with Lucee/CommandBox by default)

    • Connection: Configured in application settings

    • Storage

    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

    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)

    Comparison Table

    Feature
    Development
    Production

    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

    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

    Production Security Headers (with --production --nginx)

    Production Gzip Compression (with --production --nginx)

    Enabled for:

    • text/plain

    • text/css

    • text/xml

    • text/javascript

    server.json Docker Configuration

    The command automatically updates your server.json with Docker-specific settings:

    Before Docker Init

    After Docker Init

    Why These Changes?

    Setting
    Value
    Reason

    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

    Starting Your Docker Environment

    After running wheels docker init, start your containers:

    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:

    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

    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

    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

    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:

    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

    SQLite database not working

    Problem: SQLite connection errors or database not found

    Solutions:

    • Verify ./db/ directory exists in the container

    • Check file permissions on database file (especially in production mode with UID 1001)

    • Ensure absolute paths are used in datasource configuration

    • SQLite JDBC driver is included by default (no extension installation needed)

    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)

    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

    See Also

    • - Deploy using Docker

    • - General deployment commands

    • - Official CommandBox images

    • - Docker Compose reference

    wheels docker init [options]

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

    Oracle: latest (Oracle Database 23c Free)
  • H2: embedded (no version needed)

  • SQLite: embedded (no version needed)

  • Lists all files that will be replaced

  • Allows cancellation to prevent accidental overwrites

  • 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

    • Based on ortussolutions/commandbox:latest

    • Installs H2 extension for Lucee if H2 database selected

    • SQLite JDBC driver included by default (no extension needed)

    • Exposes application port (custom, from server.json, or defaults to 8080)

  • Generates docker-compose.yml with:

    • Application service with port mapping or internal exposure

    • Database service (mysql, postgres, mssql, or oracle) if selected

    • File-based databases (H2, SQLite) run embedded within app container

    • 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

  • 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

  • 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

  • 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

    • Note: Skipped for H2 and SQLite (file-based databases)

  • 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

  • Root Password: wheels

    Note: Requires EULA acceptance

    Note: Uses lightweight Oracle Free container image

  • Available tags: latest, 23, 23-slim, 23-faststart, 23-slim-faststart

  • JDBC Driver: org.h2.Driver

  • : Within application container filesystem at
    ./db/
  • Database File: ./db/database_name.db

  • Characteristics:

    • Serverless, zero-configuration

    • Single file database (easy to backup)

    • Ideal for development, testing, and prototyping

    • No username/password required

    • Creates auxiliary files during operation (.db-wal, .db-shm, .db-journal)

  • Use Cases: Development, testing, embedded systems, portable applications

  • Limitations:

    • Single writer (not suitable for high-concurrency)

    • Not recommended for production with multiple concurrent users

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

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

  • 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

    Gzip compression: Production mode compresses responses

  • WebSocket support: Upgrade headers configured

  • Request buffering: Better handling of slow clients

  • Health checks: Dedicated /health endpoint

  • 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

  • application/x-javascript
  • application/xml+rss

  • application/json

  • 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/SQLite only)

    h2 or sqlite

    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 and SQLite databases run 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 and SQLite)

  • Stop the conflicting service: lsof -ti:8080 | xargs kill

    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)

  • Adjust file permissions: chmod -R 755 /path/to/app

  • docker-compose exec nginx nginx -t
  • Check nginx logs: docker-compose logs nginx

  • Restart services: docker-compose restart app nginx

  • H2 works with Lucee only (not Adobe ColdFusion)

    Check application logs for SQLite initialization errors

  • Verify database file path in application settings

  • In production, ensure database file is accessible by appuser (UID 1001)

  • 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

  • Rebuild containers: docker-compose up -d --build

    Nginx Documentation - Nginx configuration reference

    --db

    Database system to use

    mysql

    h2, sqlite, 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

    Source Volume Mount

    ✅ Yes

    ❌ No

    Hot Reload

    ✅ Enabled

    ❌ Disabled

    User

    root

    appuser (1001)

    Restart Policy

    none

    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.

    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

    wheels docker deploy
    wheels deploy
    CommandBox Docker Images
    Docker Compose Documentation

    lucee, adobe

    always

    wheels docker init
    wheels docker init --db=postgres
    wheels docker init --db=postgres --dbVersion=13
    wheels docker init --cfengine=adobe --cfVersion=2023
    wheels docker init --db=h2
    wheels docker init --db=sqlite
    wheels docker init --db=oracle
    wheels docker init --db=oracle --dbVersion=23-slim
    wheels docker init --port=3000
    wheels docker init --force
    wheels docker init --production
    wheels docker init --production --nginx
    wheels docker init --nginx --port=8080
    wheels docker init --db=postgres --dbVersion=15 --cfengine=lucee --cfVersion=6 --port=8888 --production --nginx
    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"]
    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"]
    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:
    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:
    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;
            }
        }
    }
    X-Frame-Options: SAMEORIGIN
    X-Content-Type-Options: nosniff
    X-XSS-Protection: 1; mode=block
    Referrer-Policy: no-referrer-when-downgrade
    {
      "name": "myapp",
      "web": {
        "host": "localhost",
        "http": {
          "port": "8080"
        }
      },
      "openBrowser": true
    }
    {
      "name": "myapp",
      "web": {
        "host": "0.0.0.0",
        "http": {
          "port": "8080"
        }
      },
      "openBrowser": false,
      "CFConfigFile": "CFConfig.json",
      "app": {
        "cfengine": "lucee@6"
      }
    }
    # 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
    Health checks configured (30s interval, 10s timeout, 3 retries)
  • Production environment variables set

  • No source volume mounts

  • Optimized image size