Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Steve 'Cutter' Blades
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Steve 'Cutter' Blades ( @cutterbl )

Adding turbo-cfml To My ColdFusion + Hotwire Demos Project

Published in ,

Out of the box, Hotwire Turbo doesn't work with ColdFusion / CFML because it doesn't recognize .cfm as a valid HTML file extension. To "fix this", I forked the Turbo project and created turbo-cfml, which does nothing but add cfm|cfml|cfc to the library's regular expression pattern matching. This morning, I then added a "hello world" demo, using turbo-cfml to my ColdFusion + Basecamp Hotwire Demos project.

In previous demos, I had to rely on the the path_info behavior of the server; and, route all ColdFusion requests through one root hotwire.cfm template, using the path info to fake an .html page. For example, my "about" page was located at:


This URL will make a request to the ColdFusion template, hotwire.cfm, and pass /about.html as the cgi.path_info. This URL architecture tricks the Turbo library into thinking that this URL is pointing to an HTML file (not to a CFML file).

In my ColdFusion framework component, I was then overriding the request processing in my onRequest() event handler:

component {

	* I process the requested script.
	public void function onRequest( required string scriptName ) {

		if ( cgi.path_info.len() ) {

			var turboScriptName = cgi.path_info
				// Replace the ".htm" file-extension with ".cfm".
				.reReplaceNoCase( "\.html?$", ".cfm" )
				// Strip off the leading slash.
				.right( -1 )

			include "./#turboScriptName#";

		} else {

			include scriptName;




With this logic in place, a request to the ColdFusion server for:


... ends up executing the script:


This is viable; but, it's a total pain in the bum! And, raises the barrier to entry for anyone building ColdFusion websites. Which is why I forked the Turbo library to add CFML file extensions.

Aside: Lucee CFMLL will call the onRequestStart() and onRequest() event handlers even if there's no physical CFML template. As such, my demos project doesn't actually have a hotwire.cfm template anywhere - I'm just using that template name as a hook into the request processing.

With that said, I wanted to add a "hello world" demo to my demos project using my new turbo-cfml library. This way, I'd have a copy-paste basis for future demos.

Getting this to work took a bit of trial and error, stemming from the fact that I'm hosting turbo-cfml on GitHub, not on It turns out, in order to install dependencies from GitHub, the installation container needs to have git installed.

I didn't notice this in my prior exploration because I was running npm install directly on my host computer, which always has git installed. But, my ColdFusion + Hotwire project is running inside Docker, including the npm install. So, when I went to install turbo-cfml, I was getting the following error:

1016 verbose cwd /app/turbo-cfml
1017 verbose Linux 5.15.49-linuxkit
1018 verbose node v22.0.0
1019 verbose npm  v10.5.1
1020 error code ENOENT
1021 error syscall spawn git
1022 error path git
1023 error errno -2
1024 error enoent An unknown git error occurred
1025 error enoent This is related to npm not being able to find a file.

To me, this error meant nothing. But, I pasted it into ChatGPT; which was able to tell me that git needed to be made available. So, I updated my Dockerfile to include git and a more recent version of nodejs (which I believe was causing a separate installation issue for morphdom):

FROM ortussolutions/commandbox

RUN curl -fsSL | bash - \
	&& apt-get install -y \
		git \
		nodejs \

I then created a demo with the following package.json dependencies (truncated code):

	"name": "demo",
	"dependencies": {
		"@hotwired/stimulus": "3.2.2",
		"@parcel/transformer-less": "2.12.0",
		"parcel": "2.12.0",
		"turbo-cfml": "github:bennadel/turbo-cfml#cfml-8.0.4-2"

At first, npm was telling me that the version #cfml-8.0.4-2 didn't exist. I believe this was a confusion on my part. I had thought that providing a tag name would work. But, it seems that only a branch name will work. So, I created a branch with the same name (cfml-8.0.4-2) and pushed it up to my turbo-cfml repository. At that point, the installation was successful.

Aside: I think some of my confusion came from the fact that the package-lock.json file was hiding some issues in my previous exploration.

With those development assets in place, I then created a small ColdFusion site with three pages. And, to the point of this post, the navigation links for this site all use plain .cfm URLs:

<h1> Turbo CFML Testing </h1>
	<a href="./index.cfm">Home</a> |
	<a href="./about.cfm">About</a> |
	<a href="./contact.cfm">Contact</a>

Now, when I click around in this ColdFusion site using turbo-cfml instead of @hotwired/turbo, the Turbo framework happily navigates across CFML pages:

User clicking around a ColdFusion website. The network activity shows fetch operations triggered for each navigation.

As you can see, I can navigate across the ColdFusion pages. And, by looking at the network activity, we can tell that these navigation events are being handled by Hotwire Turbo because:

  1. They are being requested via the fetch API.

  2. The initiator of the network request is turbo-cfml.

At this point, I now have a proven mechanism for using Hotwire Turbo in ColdFusion without any URL rewriting shenanigans.

Want to use code from this post? Check out the license.

Reader Comments

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel