ColdFusion on Wheels Blog

CFWheels Fully Embraces ForgeBox Packages

As you may know, many years ago CFWheels embraced the distribution of Plugins via ForgeBox packages instead of maintaining our own directory. But the framework itself remains illusive. There was some work done in the last few months to put up packages for the framework but those packages were being maintained by hand which made them a show stopper for a long term solution.

Well, thanks to a new CI workflow based on GitHub Actions we now have the building and publishing of the packages fully automated. Giving credit where credit is due, the new workflow borrows heavily from the ColdBox workflow. It used GitHub Actions, Ant, and CommandBox to automate the process.

So what does all this mean for you, let’s cut to the chase. This means you can now install a fresh copy of the framework using the following command:

box install cfwheels-base-template

This will pull down a copy of the latest stable release of the template files and then pull down a copy of the latest stable release of the framework via package dependencies. In fact the CI workflow mentioned about publishes two packages cfwheels which is the core framework directory and cfwheels-base-template which is all the other files you need to scaffold the framework.

We’ve even backfilled all the prior released versions of the framework all the way back to version 1.0.0. So you can install a particular version of the framework using the following command:

box install [email protected]

In addition you can install the bleeding edge which includes all the work in process towards the next major release using:

box install [email protected]

And if you ever just need to get a copy of the latest framework files simply use the following command:

box install cfwheels

All this means that upgrading to a newer version of the framework should be much easier going forward. Frankly you should just need to modify the version of the dependency in the box.json file and issue a box update command. But we’ll document that more fully when we make our next release.

For now please feel free to play with all this package goodness and let us know if we fumbled anything.

CFWheels Joins Open Source Collective

We are happy to announce that CFWheels has joined Open Source Collective. According to their website, Open Collective enables all kinds of groups to raise, manage, and spend money transparently. We’re also in good company in the collective. Other projects hosted by the Open Source Collective include Lucee, WebPack, PHP Foundation, Vue, LinuxServer, ESLint, Bower, Svelte, and the list literally goes on and on.

So what does this mean for CFWheels. Well, it allows us to finally be able to accept donations from our community. Many of you have offered your donations to us in the past but we really had no good way to do accept them legally. Plus we felt strongly that as an open source project we needed to adopt an open and transparent accounting practices. As a member of the collective, you’ll be able to donate and see every dollar we raise and what it is spent on. Creating a sustainable ecosystem is important for the long term viability of the CFWheels project. So how do you donate, visit Open Source Collective directly or any of our GitHub projects and look for the Sponsor this project link in the right side bar.

We’ve already received our first monthly donation and we are truly grateful. These funds will allow us to offer bounties for small issues or bugs, commission larger works, and pay for marketing, logo, or branding services.

Getting the Example App Up and Running

Have you looked at the CFWheels Example App lately? If you’re wondering, wait, there is an example app?, you’re not alone. Tom has done a great job putting the CFWheels Example App together but historically getting it up and running was somewhat difficult. It requires a database to be setup, a datasource to be defined, and migrations to be run.

With the help of our new templating structure and some improvements to the CFWheels-CLI commands, getting the Example application is a piece of cake. All you need to do is issue three commands inside a CommandBox shell and the app magically opens up in your browser.

So lets get started:

wheels g app name=example datasourceName=exampleh2 template=cfwheels-template-example-app --setupH2
package install
server start

So what do those commands do for us. The first line is the longhand way to install a fresh CFWheels app using one of the published templates from This command gives the application a name, sets up a datasource, and configures it to use the built in H2 database in the Lucee CF Engine. (Are you wondering, wait, Lucee has a built in database engine?) The next line installs all our development and production dependencies into our application directory. Lastly we start the Lucee server and the application launches in our default browser.

You’ll initially see the installation verification screen.

Once you click on the Reload button, the application will reload and launch the Example App.

At this point you can login using one of the default user ID listed below.

user: (Pending Verification)

All of them have the password set to Password123!

I hope you enjoy playing with this Example App and it serves as a learning tool as it was intended. Please share your experience in the comments below.

EDIT: The Lucee server that starts up will have cfwheels set as its admin password.

TodoMVC Implementation with CFWheels and HTMX

Recently I’ve been playing around with HTMX and really starting to love it. So what is HTMX? From their website:

htmx gives you access to  AJAXCSS Transitions,  WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext

Introduction to htmx

And also:

Why should only <a> and <form> be able to make HTTP requests?
Why should only click & submit events trigger them?
Why should only GET & POST methods be available?
Why should you only be able to replace the entire screen?

By removing these arbitrary constraints, htmx completes HTML as a hypertext

Motivation behind htmx

So what does this all mean? Well, in its simplest form, it means being able to build modern web applications with the UX that users have come to expect, with the HTML, CSS, and the backend technology of your choice which in our case is CFML and CFWheels.

So I decide to see if I could build the TodoMVC project using no hand written JavaScript and only relying on HTML, CSS, and CFWheels. I downloaded the template project and took a look at the application specs to get an idea of what to implement.

Here is the video of the running app:

So if you want to run the app locally, you’ll need to have Commandbox installed and the CFWHeels CLI commands for CommandBox installed as well. With those two items taken care of, launch a CommandBox and issue the following commands.

wheels g app name=todo datasourceName=todo template=cfwheels-todomvc-htmx --setupH2
package install
server start

Let’s look at those lines and talk about what they do. The first line wheels g app will download the template app from and create a CFWheels application and name it todo. It also create a H2 database and configures the datasource for you. The next line will install all the dependencies of our app. These include, a few CommandBox modules to make development easier, the CFWheels core framework directory and place it into the wheels folder, and install the H2 drivers into our Lucee server for out application. The last line will start our Lucee server. I’ve also added a setting to automatically run the Database migrations on application startup so the database schema is created.

You can checkout the code on GitHub. Let me know what you think.

EDIT: The Lucee server that starts up will have cfwheels set as its admin password.

Changing of the Guards at CFWheels

Hello everyone,

I was waiting to have more of my thoughts and plans ironed out before posting this message, but due to the intense interest from the community I’ve decided to post what I have, however premature.

As many of you know we have had some changes in the CFWheels core team. Several of the core team members, have decided to reduce their level of administrative involvement in the project and have stepped down from the core team. I have volunteered to take the reins of the project and we are in the middle of passing the baton as it were.

So what does this mean for the CFWheels project? 

Well, the fact that there is a transition in place at all, means the project will continue to live on. The CFWheels project has been around since 2005 and during that time different individuals have held the reins and guided the project along. This time is no different. I hope to be as worthy of a steward as those that have come before me.

So what are my thoughts for the future of the project?

From an administrative perspective I want to see what structure to adopt. Whether it is the core team structure we have had in the past or perhaps a more advisory committee structure would be better. We need to take stock of all the code in flight at the moment and try to get a roadmap sketched out. Setting up a funding structure for the project vis-á-vis,,, or And finally looking at the legal structure of the project and if there is a need to formalize that by creating a LLC or 501.C corporation. 

At this point I have more questions than answers but I welcome your feedback and look forward to your support. 

Starting a New CFWheels Project with the CLI

If you’re not using CommandBox for your CFWheels development, you’re in for a treat. This is a quick post to show case how easy it is to start a new CFWheels project with the help of the the wheels command for CommandBox.

If you have CommandBox installed and have the cfwheels-cli module installed, then simply launch CommandBox by typing box and then issue the command wheels generate app myApp . This simple command will take care of the following:

  • Create a folder in the current working directory for the project and name if myApp
  • Copy the cfwheels-template-base package from ForgeBox and expand it in the root of the myApp folder
  • Copy the cfwheels-core package from ForgeBox and expand it into the wheels folder in the root of the myApp folder
  • This command also takes care of naming your app myApp or whatever name you pass in

You may be asking yourself, what are all these packages you’re talking about? Well, we are starting to use the package management system provided by ForgeBox and CommandBox to make distribution of sample apps easier as well as installing and updating projects based on CFWheels. More to come on these topics later but this is just to whet your appetite.

CFWheels 2.2 Released

It’s been a while coming. Can I blame the pandemic? Lots of nice little tweaks and fixes in this version. Please see the changelog for all details.

Download zip

If updating from CFWheels 2.1.x:

It should be an easy upgrade , just swap out the wheels folder.

If updating from CFWheels 2.0.x:

  • replace your wheels folder from the one in the download, and
  • outside the wheels folder, ensure you’ve got a file at events/onabort.cfm and create it if needed.
  • rename any instances of findLast() to findLastOne() (this has been changed due to Lucee 5.3 having a new inbuilt function called findLast() which clashes with the wheels internals)

As always, a huge thanks to all contributors – stay safe out there!

CFWheels 2.1 Released

Today sees the release of CFWheels 2.1. Only a couple of bug fixes since the beta, so please refer to the changelog for a list of all changes.

Download now (zip)

If updating from CFWheels 2.0.x:

  • replace your wheels folder from the one in the download, and
  • outside the wheels folder, ensure you’ve got a file at events/onabort.cfm and create it if needed.
  • rename any instances of findLast() to findLastOne() (this has been changed due to Lucee 5.3 having a new inbuilt function called findLast() which clashes with the wheels internals)

Happy Easter, and stay safe!

Debugging plugin performance in CFWheels 2.x with FusionReactor

The Issue

Shortly after the release of CFWheels 2.0, we started to get reports of slower running requests under certain conditions. For instance, a page which might have had 1000 calls to `linkTo()` could take anything from 1-2ms to 5-6ms a call, which, after 1000 iterations, is one hell of a performance bottle neck. In 1.x, the same call would be between 0-1ms, usually with a total execution time of sub 200ms. 

This behaviour was something which could be proven by a developer, but not everyone was seeing the same results: what was the difference? Plugins (or rather, plugins which override or extend a core function, like linkTo()). To make matters worse, the performance degradation was doubled for each plugin, so you might get 1-2ms for 1 plugin, 2-4 ms for adding another plugin and so on.

So what was causing this?

Enter FusionReactor

We approached FusionReactor, who were kind enough to give us a temporary licence to help debug the issue (it’s great when companies support open-source!). So next up were some tests to help diagnose the issue.

Installing FusionReactor was really simple. As we use CommandBox locally, we could just utilise the existing module via install commandbox-fusionreactor to bootstrap FusionReactor onto our local running servers, which gave us access to the FR instance, already plumbed in. As we were looking for a specific line of code, we also installed the FusionReactor Lucee Plugin and configured it track CFML line execution times using the CF line performance explorer.

This was instantly illuminating, and tracked the problem to our new pluginrunner() method. When we released CFWheels 2.0, there was a fairly heft rewrite of the plugins system. It was designed to be able to allow for plugins to be chained, and execute in a specific order, so you could hypothetically have the result from one plugin overriding the previous one in the chain.

The way it did this was by creating a “stack” of plugins in an array, working out where it was in that stack, and executing the next plugin in the stack till it reached the end. It did that via a combination of callStackGet() and getFunctionCalledName() function to do the comparison.

As you can see from the screenshot below, the line debugger clearly highlights this. This app had four plugins, two of which extended core functions.

Example of FR Lucee 4 Line Debugger

callStackGet() gets invoked 2364 times in this example, but appeared performant, only causing 10ms execution time. getFunctionCalledName() is called the same number of times, but has a total execution time of 2242ms(!). We had our potential culprit. Either way, it was looking like the combination of calling the stack and trying to find the calling function name which was causing so much pain. I suspect it’s to do with how Java deals with this: I think it might be calling a full stack trace and writing it to disk on each call – at least that was the hint from FusionReactor’s thread profiler (I’m sure those who have a better understanding of Java’s underlying functions will chip in).

After some deliberation, we decided to revert this behaviour in CFWheels 2.1 back to how it used to work in 1.x, as the vast majority weren’t using it, but were being affected by it. We’d seen no plugins in the wild which used this behaviour either, which was largely undocumented.

Obviously thanks to FusionReactor for helping us out – hopefully this gives some insight into just one of the ways FusionReactor can be used. Maybe one day I’ll understand Java stack traces – maybe.

CFWheels 2.1.0-beta Now Available

It’s been far too long in the making, but the beta for 2.1 has now arrived! Please do check it out: this should be considered an essential upgrade for anyone on 2.x. A huge thanks to all have contributed!

Make sure to check the “Potenitally Breaking Changes” section below, and please report any bugs.

What’s New:


Probably the most obvious change in 2.1 beta is the new internal user interface. Previously, Wheels internal pages like test outputs and routing tables could accidentally be broken by your app, as they extended your Controller.cfc by default. Now, they’re completely isolated, and have been significantly beefed up to show everything that you might want to look at as a developer. 

The new GUI has it’s own dedicated internal routes which can be accessed directly at /wheels/info (assuming you’ve got URL rewriting on) or via the usual links in the debug footer. 

It’s made up of six main sections:

  • General Wheels info – which displays all the settings for your development environment, such as datasources and other core configuration;   
  • a new routing table – which includes a handy route tester as well as a quick search;   
  • improved test outputs so you can more easily access unit tests for your app, core tests if you’re on the master branch, and plugin tests if they’re available;   
  • a new database migration interface, which allows for SQL previews, so you can actually check your migration is going to do what you think it’s going to do before executing;   
  • a more comprehensive documentation output, which includes your own autogenerated application docs as well as the internal wheels function references,   
  • and a better plugins list, showing all the information we know about any installed plugins. 

This GUI is only available in development mode by default, but can be enabled in other modes – this isn’t recommended, but it can be done if you really need to check the configuration or try and track something specific down. You can enable it via set(enablePublicComponent=true) in your environment specific configuration files. 

Improved CORS handling

Next up is some much needed and improved CORS handling. CORS, or Cross-Origin Resource Sharing, will be very familiar to any of you who have a javascript component or fully fledged progressive web app which runs on one domain, but needs to get information from another. A full explanation of CORS is probably beyond the scope of this post, but if you’re thinking of running your own API on a standalone domain where other applications talk to it, you’ll need to be able to handle CORS.

In Wheels 2.0, you could handle CORS requests, but they could only be configured in a very broad way. For instance, the CORS Allow Methods just returned all methods by default in an OPTIONS request, which kinda defeated the whole point.

The CORS Headers –  Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Credentials can now be directly set by their respective functions. 

However, for most people, a new helper configuration, accessControlAllowMethodsByRoute() will be the single most useful function to set: it allows for automatic matching of available methods for a route and sets the CORS Header Access-Control-Allow-Methods automatically; so now when your OPTIONS request hits your wheels app, it will actually respond with the available methods which are allowed for that resource endpoint. This makes it much easier to diagnose why certain requests might not get through, and means javascript libraries such as axios can respond more appropriately to hitting the wrong URL with the wrong HTTP method.

Improvements to mapper()

Redirects can now be put directly in the mapper() routing call, so you can quickly add a simple redirect for a route if you need.

mapFormat can now be set as false, which bypasses the additional ‘.[format]’ routes which were put in by default for each resource. So if you don’t use them, you can now just turn them off, and make your routing table a lot cleaner. This can be set either globally on the mapper() call itself, or on a per resource basis when using resources()

params._json if request is Array

Previously, if you POSTed or PUT/PATCHed json to an endpoint with an array as it’s root element, it would just get ignored, and you’d not be able to access it in the params struct. This has now been changed and if the incoming payload is a json array, it will be available at params._json which matches rails conventions.

New FlashAppend Behaviour

You can now change the default flash behaviour to append to an existing key, rather than directly replacing it. To turn on this behaviour, add set(flashAppend=true) to your /config/settings.cfm file. This allows you to more easily collect flash notifications during a request:

Plugin performance

The plugins system has been reverted back to 1.x behaviour, as it was simply non-performant; more on this in a future post.

Full Changelog:

Potentially breaking changes

  • The new CFWheels internal GUI is more isolated and runs in it’s own component: previously this was extending the developers main Controller.cfc which caused multiple issues. The migrator, test runner and routing GUIs have therefore all been re-written.
  • The plugins system behaviour no longer chains multiple functions of the same name as this was a major performance hit. It’s recommended that plugin authors check their plugins to run on 2.1
  • HTTP Verb/Method switching is now no longer allowed via GET requests and must be performed via POST (i.e via _method)

Model Enhancements

  • Migrator now automatically manages the timestamp columns on addRecord() and updateRecord() calls – #852 [Charley Contreras]
  • Migrator correctly honors CFWheels Timestamp configuration settings (setUpdatedAtOnCreate, softDeleteProperty, timeStampMode, timeStampOnCreateProperty, timeStampOnUpdateProperty) – #852 [Charley Contreras]
  • MSSQL now uses NVARCHAR(max) instead of TEXT #896 [Reuben Brown]
  • Allow createdAt and updatedAt to be explicitly assigned using the allowExplicitTimestamps=true argument – #887 – [Adam Chapman]

Controller Enhancements

  • New set(flashAppend=true) option allows for appending of a Flash key instead of replacing – #855 – [Tom King]
  • flashMessages() now checks for an array of strings or just a string and outputs appropriately – #855 – [Tom King]
  • flashInsert() can now accept a one dimensional array – #855 – [Tom King]

Bug Fixes

  • Allow uppercase table names containing reserved substrings like OR and AND – #765 [Dmitry Yakhnov, Adam Chapman]
  • Calculated properties can now override an existing property – #764 [Adam Chapman, Andy Bellenie]
  • Filters are now correctly called if there is more than one after filter – #853 [Brandon Shea, Tom King, Adam Chapman]
  • Minor fix for duplicate debug output in the test suite – #176 [Adam Chapman, Tom King]
  • Convert handle to a valid variable name so it doesn’t break when using dot notation – #846 [Per Djurner]
  • The validatesUniquenessOf() check now handles cases when duplicates already exist – #480 [Randall Meeker, Per Djurner]
  • validatesConfirmationOf() now has a caseSensitive argument to optionally perform a case sensitive comparison – #918 [Tom King]
  • sendFile() no longer expands an already expanded directory on ACF2016 – #873 [David Paul Belanger, Tom King, strubenstein]
  • Automatic database migrations onApplicationStart now correctly reference appropriate Application scope – #870 [Tom King]
  • usesLayout() now can be called more than once and properly respects the order called – #891 [David Paul Belanger]
  • Migrator MSSQL adapter now respects Time and Timestamp Column Types – #906 [Reuben Brown]
  • Automatic migrations fail on application start – #913 [Adam Chapman]
  • Default cacheFileChecking to true in development mode – [Adam Chapman, Steve Harvey]
  • Migrator columnNames list values are now trimmed – #919 [Adam Chapman]
  • Fixes bug when httpRequestData content is a JSON array – #926 [Adam Chapman]
  • When httpRequestData content is a JSON array, contents are now automatically added to params._json – #939 [Tom King]
  • Fixes bug where Migrator $execute() always appends semi-colon – #924 [Adam Chapman]
  • Fixes bug where model createdAt property is changed upon update – #927 [Brandon Shea, Adam Chapman]
  • Fixed silent application.wheels scope exception hampering autoMigrateDatabase – #957 [Adam Chapman, Tom King]


  • Added the ability to pass &lock=false in the URL for when reload requests won’t work due to locking – [Per Djurner]
  • Basic 302 redirects now available in mapper via redirect argument for GET/PUT/PATCH/POST/DELETE – #847 – [Tom King]
  • .[format] based routes can now be turned off in resources() and resource() via mapFormat=false – #899 – [Tom King]
  • mapFormat can now be set as a default in mapper() for all child resources() and resource() calls – #899 – [Tom King]
  • HEAD requests are now aliased to GET requests #860 – [Tom King]
  • Added the includeFilters argument to the processRequest function for skipping execution of filters during controller unit tests – [Adam Chapman]
  • Added the useIndex argument to finders for adding table index hints #864 – [Adam Chapman]
  • HTTP Verb/Method switching is now no longer allowed via GET requests and must be performed via POST #886 – [Tom King]
  • CORS Header Access-Control-Allow-Origin can now be set either via a simple value or list in accessControlAllowOrigin() #888 [Tom King]
  • CORS Header Access-Control-Allow-Methods can now be set via accessControlAllowMethods(value) #888 [Tom King]
  • CORS Header Access-Control-Allow-Credentials can now be turned on via accessControlAllowCredentials(true)#888 [Tom King]
  • accessControlAllowMethodsByRoute() now allows for automatic matching of available methods for a route and sets CORS Header Access-Control-Allow-Methods appropriately #888 [Tom King]
  • CORS Header can now be set via accessControlAllowHeaders(value) #888 [Tom King]
  • Performance Improvement: Scanning of Models and Controllers #917 [Adam Chapman]
  • Added the authenticityToken() function for returning the raw CSRF authenticity token #925 [Adam Chapman]
  • Adds enablePublicComponentenableMigratorComponent,enablePluginsComponent environment settings to completely disable those features #926 [Tom King]
  • New CFWheels Internal GUI #931 [Tom King]
  • pluginRunner() now removed in favour of 1.x plugin behaviour for performance purposes #916 [Core Team]
  • Adds validateTestPackageMetaData environment setting for skipping test package validation on large test suites #950 [Adam Chapman]
  • Added aliases for migrator.TableDefinition functions to allow singular variant of the columnNames property #922 [Sébastien FOCK CHOW THO]
  • onAbort is now supported via events/onabort.cfm #962 [Brian Ramsey]