Configure a Global cSpell User Dictionary in VS Code

This article shows how to move your custom cSpell words out of VS Code’s settings.json and into a dedicated global dictionary. This keeps your configuration clean while providing a single, reusable place for technical terms, product names, and other words you use across projects.

When using the Code Spell Checker (cSpell) extension in VS Code, custom words can be added directly to settings.json using cSpell.userWords. Over time, however, this list can become large and clutter the VS Code configuration.

A cleaner approach is to maintain custom words in a separate global dictionary file.

Note: These instructions assume you are using macOS.

Create the Dictionary File

Create a global cSpell dictionary in your home directory:

touch ~/.cspell-dictionary.txt

For a user named username, the full path to the file would be:

/Users/username/.cspell-dictionary.txt

Open the file in VS Code:

code ~/.cspell-dictionary.txt

Add custom words to the dictionary with one word per line:

bootcamp
cfadmin
cfconfig
cfcs
cfdocument
cfengine
cfformat
cfinvoke
cflog
cfoutput
cfpm
cfschedule
cfscript
cfset
cfusion
CommandBox
Fixinator
Flexbox
Glyphicons
labwc
Lucee
LUCEEONLY
Mailpit
mailsent
navbars
Ollama
raspi
strikethrough
Udemy
Webroot
WebUI

Unlike settings.json, the dictionary file does not use JSON syntax. Do not include quotes, commas, or brackets.

Configure cSpell

Open your VS Code settings.json and add the following:

"cSpell.dictionaryDefinitions": [
    {
        "name": "my-words",
        "path": "/Users/username/.cspell-dictionary.txt",
        "addWords": true
    }
],
"cSpell.dictionaries": [
    "my-words"
],

This defines a dictionary named my-words and tells cSpell to use it globally.

The addWords setting allows cSpell to add new words to this dictionary when using its Add Word to Dictionary functionality.

Remove cSpell.userWords

If the same words are currently stored in settings.json:

"cSpell.userWords": [
    "CFML",
    "CommandBox",
    "Fixinator",
    "Lucee",
    "Ollama"
],

copy those words into .cspell-dictionary.txt, then remove the cSpell.userWords setting.

Your custom words will now be maintained in the global dictionary instead of settings.json.

Adding New Words

When cSpell flags a legitimate word such as:

Ollama

use the cSpell quick fix in VS Code and add the word to the my-words dictionary.

Because the dictionary was configured with:

"addWords": true

new words can be written to:

~/.cspell-dictionary.txt

instead of continually expanding cSpell.userWords in settings.json.

The dictionary will gradually grow as new project-specific terminology, product names, acronyms, and technical terms are encountered.

Result

The VS Code configuration remains small:

"cSpell.dictionaryDefinitions": [
    {
        "name": "my-words",
        "path": "/Users/username/.cspell-dictionary.txt",
        "addWords": true
    }
],
"cSpell.dictionaries": [
    "my-words"
],

while the potentially much larger collection of custom words lives separately in:

~/.cspell-dictionary.txt

This keeps settings.json cleaner and provides a single global dictionary that can be maintained independently of VS Code’s other settings.

Start/Stop Mailpit with CommandBox Server Start/Stop

I use Mailpit to capture email while developing CFML applications locally. It works great, but I don’t necessarily need it running all the time. Since I already use CommandBox to start and stop my local ColdFusion server, I wanted Mailpit to follow the same lifecycle.

Fortunately, CommandBox server scripts make this straightforward. When the CommandBox server starts, it can start Mailpit. When the server stops, it can stop Mailpit as well.

Prerequisites

This example is for macOS and assumes Mailpit was installed with Homebrew:

brew install mailpit

You can test Mailpit manually with:

brew services start mailpit
brew services stop mailpit

By default, Mailpit provides:

CommandBox server lifecycle scripts

CommandBox supports server-specific scripts in server.json. Among the available lifecycle events are:

  • onServerStart, which runs while the server is starting
  • onServerStop, which runs before the server stops

Add the following top-level scripts object to the CommandBox server configuration:

{
    "scripts": {
        "onServerStart": "!brew services start mailpit",
        "onServerStop": "!brew services stop mailpit"
    }
}

The ! prefix tells CommandBox to execute a native operating-system command.

If CommandBox cannot find Homebrew in its PATH, use the full path to the executable. On an Apple Silicon Mac, that will commonly be /opt/homebrew/bin/brew:

{
    "scripts": {
        "onServerStart": "!/opt/homebrew/bin/brew services start mailpit",
        "onServerStop": "!/opt/homebrew/bin/brew services stop mailpit"
    }
}

On an Intel Mac, Homebrew is commonly located at /usr/local/bin/brew. Run which brew in Terminal to confirm the correct path.

Wait until Mailpit is ready

Starting the Homebrew service does not necessarily mean Mailpit is ready to accept requests at that exact instant. Mailpit provides /readyz for checking its readiness. A successful request returns an HTTP 200 response.

We can make the startup script check that endpoint once per second for up to 20 seconds:

{
    "scripts": {
        "onServerStart": [
            "!brew services start mailpit",
            "!for i in {1..20}; do curl -fsS http://127.0.0.1:8025/readyz && exit 0; sleep 1; done; echo 'Mailpit failed to become ready' >&2; exit 1"
        ],
        "onServerStop": "!brew services stop mailpit"
    }
}

This script:

  1. Starts Mailpit through Homebrew.
  2. Checks the Mailpit readiness endpoint.
  3. Continues as soon as Mailpit responds successfully.
  4. Returns an error if Mailpit does not become ready within 20 seconds.

You can also check Mailpit manually at any time:

curl -fsS http://127.0.0.1:8025/readyz

To inspect the Homebrew service state instead, run:

brew services info mailpit

Show Mailpit’s status on a local home page

My CommandBox server hosts several local applications, so I have a simple home page that links to each application and development resource. I wanted its Mailpit card to show whether Mailpit was online.

When Mailpit is stopped, the card displays a red Offline badge:

Mailpit Offline
Mailpit Offline

When Mailpit is available, it displays a green Online badge:

Mailpit Online
Mailpit Online

First, check the readiness endpoint near the beginning of the CFML page, before the HTML output:

<cfset mailpitOnline = false>

<cftry>
    <cfhttp
        url="http://127.0.0.1:8025/readyz"
        method="GET"
        timeout="2"
        result="mailpitHealth">

    <cfset mailpitOnline = val(mailpitHealth.statusCode) eq 200>

    <cfcatch type="any">
        <cfset mailpitOnline = false>
    </cfcatch>
</cftry>

The default value is false. If Mailpit responds with HTTP status 200, it changes to true. A timeout or connection error is caught so an unavailable Mailpit service does not cause the home page itself to fail.

The two-second timeout also prevents the check from delaying the page for too long.

Next, use that value to set the card’s border and status badge:

<div class="col-6 col-md-4 col-lg-3">
    <a href="http://localhost:8025" class="text-decoration-none" target="_blank">
        <div class="card h-100 shadow-sm <cfif mailpitOnline>border-success<cfelse>border-danger</cfif>">
            <div class="card-body text-center">
                <h5 class="card-title text-secondary">
                    Mailpit
                    <cfif mailpitOnline>
                        <span class="badge text-bg-success align-middle"
                              style="font-size: 0.6rem;">Online</span>
                    <cfelse>
                        <span class="badge text-bg-danger align-middle"
                              style="font-size: 0.6rem;">Offline</span>
                    </cfif>
                </h5>
            </div>
        </div>
    </a>
</div>

This example uses Bootstrap 5 classes for the card, border, and badge styling.

Putting it all together

The final CommandBox configuration is:

"scripts": {
    "onServerStart": [
        "!brew services start mailpit",
        "!for i in {1..20}; do curl -fsS http://127.0.0.1:8025/readyz && exit 0; sleep 1; done; echo 'Mailpit failed to become ready' >&2; exit 1"
    ],
    "onServerStop": "!brew services stop mailpit"
}

Now Mailpit starts along with the CommandBox server, its readiness is verified, and the local home page indicates whether it is online. When the CommandBox server is stopped normally, Mailpit is stopped as well.

A few things to keep in mind

  • The stop hook runs when the server is stopped through CommandBox. It cannot run if CommandBox is force-terminated or the computer shuts down unexpectedly.
  • Homebrew services are user-wide. If several CommandBox servers or other applications share the same Mailpit instance, stopping one server could stop Mailpit while something else is using it.
  • If one CommandBox server hosts several applications, put the scripts in that server’s shared configuration. Mailpit will then follow the lifecycle of the entire server rather than any single application.
  • The status badge reflects Mailpit’s state when the CFML page is rendered. Refresh the page to update it.

That’s it. Mailpit is available when the local development server is running and gets out of the way when development is finished.

Add items (like Mailpit or tailing logs) to the CommandBox Tray

I’ve always been a fan of the tray that’s available when CommandBox launches. On the Mac, it appears as a menu bar icon in the top menu (first icon below).

CommandBox menu bar icon
CommandBox menu bar icon

It has some very convenient built-in options:

Servername
├── Stop Server
├── Restart Server
├── Open...
│   ├── Webroot
│   ├── Server Home
│   ├── Site Home
│   └── Server Admin
├── Info
│   ├── Engine: adobe 2023.0.17+330864
│   ├── Webroot: /Path/to/website/
│   ├── URL: https://127.0.0.1:8443
│   ├── PID: 99999
│   └── Heap: Not set

I frequently need to access Mailpit and various log files as I work through the development process on a current project. This got me wondering if I could add some of my own frequently accessed items to the tray. It turns out you can add quite a few different things.

Enable the tray

First, enable the tray in the server.json file for the site.

"trayEnable": true,

Next, let’s start simple and add a single item to open Mailpit in a browser. The trayOptions setting accepts an array of objects. Each object should contain a label, an action, and one of [url, path, command]. The label is the text that appears in the tray. The action tells CommandBox what to perform (openbrowseropenfilesystemrunAsync, etc.). The [url, path, command] values provide the URL of the site, the filesystem path, or the command to execute, respectively.

Setting the tray options

"trayOptions": [
    {
        "label": "Mailpit Web Interface",
        "action": "openbrowser",
        "url": "http://localhost:8025"
    }
]

Let’s dive a little deeper with some other options.

Add a “divider” of blank space between CommandBox’s built-in options and the ones we are adding.

{
    "label": " ",
    "disabled": true
}

Open a folder on the filesystem.

{
    "label": "Open Log Folder",
    "action": "openfilesystem",
    "path": "${serverinfo.serverHomeDirectory}/WEB-INF/cfusion/logs"
}

Tail the last 100 lines of mailsent.log (macOS only as written, but adaptable for Linux or Windows).

{
    "label": "Tail mailsent.log",
    "action": "runAsync",
    "command": "tmpfile=$(mktemp -t tail-mailsent).command; printf '#!/bin/zsh\nprintf \"\\e]0;Tail mailsent.log\\a\"\nclear\ntail -n 100 -f \"%s\"\n' '${serverinfo.serverHomeDirectory}/WEB-INF/cfusion/logs/mailsent.log' > \"$tmpfile\"; chmod +x \"$tmpfile\"; open -a Terminal \"$tmpfile\""
}

Explanation of the tail command

Add some icons using emojis.

🛠 Development Tools
📬 Mailpit Web Interface
📂 Logs

Putting it all together

CommandBox tray open
CommandBox tray open
"trayEnable": true,
"trayOptions": [
    {
        "label": " ",
        "disabled": true
    },
    {
        "label": "🛠 Development Tools",
        "items": [
            {
                "label": "📬 Mailpit Web Interface",
                "action": "openbrowser",
                "url": "http://localhost:8025"
            },
            {
                "label": "📂 Logs",
                "items": [
                    {
                        "label": "Open Log Folder",
                        "action": "openfilesystem",
                        "path": "${serverinfo.serverHomeDirectory}/WEB-INF/cfusion/logs"
                    },
                    {
                        "label": "Tail application.log",
                        "action": "runAsync",
                        "command": "tmpfile=$(mktemp -t tail-application).command; printf '#!/bin/zsh\nclear\ntail -n 100 -f \"%s\"\n' '${serverinfo.serverHomeDirectory}/WEB-INF/cfusion/logs/application.log' > \"$tmpfile\"; chmod +x \"$tmpfile\"; open -a Terminal \"$tmpfile\""
                    },
                    {
                        "label": "Tail exception.log",
                        "action": "runAsync",
                        "command": "tmpfile=$(mktemp -t tail-exception).command; printf '#!/bin/zsh\nclear\ntail -n 100 -f \"%s\"\n' '${serverinfo.serverHomeDirectory}/WEB-INF/cfusion/logs/exception.log' > \"$tmpfile\"; chmod +x \"$tmpfile\"; open -a Terminal \"$tmpfile\""
                    },
                    {
                        "label": "Tail mailsent.log",
                        "action": "runAsync",
                        "command": "tmpfile=$(mktemp -t tail-mailsent).command; printf '#!/bin/zsh\nprintf \"\\e]0;Tail mailsent.log\\a\"\nclear\ntail -n 100 -f \"%s\"\n' '${serverinfo.serverHomeDirectory}/WEB-INF/cfusion/logs/mailsent.log' > \"$tmpfile\"; chmod +x \"$tmpfile\"; open -a Terminal \"$tmpfile\""
                    }
                ]
            }
        ]
    }
]

Explanation of the tail command

This command creates a temporary shell script, makes it executable, and opens it in Terminal. Reminder: this version is macOS only as written because it uses zshmktemp, and open -a Terminal.

"command": "tmpfile=$(mktemp -t tail-mailsent).command; printf '#!/bin/zsh\nprintf \"\\e]0;Tail mailsent.log\\a\"\nclear\ntail -n 100 -f \"%s\"\n' '${serverinfo.serverHomeDirectory}/WEB-INF/cfusion/logs/mailsent.log' > \"$tmpfile\"; chmod +x \"$tmpfile\"; open -a Terminal \"$tmpfile\""

What it does

  • mktemp -t tail-mailsent
    • Creates a uniquely named temporary file.
  • printf '#!/bin/zsh ...'
    • Writes a small shell script into the temporary file.
  • printf "\e]0;Tail mailsent.log\a"
    • Sets the Terminal window title.
  • clear
    • Clears the Terminal window before output begins.
  • tail -n 100 -f
    • Displays the last 100 lines of the log file and continues following new entries in real time.
  • chmod +x
    • Makes the temporary script executable.
  • open -a Terminal
    • Opens the script in the macOS Terminal application.

Next Steps

There are all kinds of other tasks that could be added to the tray such as:

  • Ping a server
  • Flushing DNS
  • Open the hosts file to edit
  • Open the site’s Git repo

Good luck!

Mailpit + ColdFusion Local Email Testing

Overview

This article documents how to configure Adobe ColdFusion to use Mailpit for local email testing.

Mailpit captures outbound email locally so messages can be reviewed in a browser without sending real email.


Export Existing Mail Settings (Optional)

Before making changes, export the current ColdFusion mail configuration so it can be restored later if needed.

cfconfig export from=server.local to=mailSettings.json includeList=mailservers**

Example exported configuration:

{
    "mailServers": [
        {
            "tls": true,
            "password": "SUPERSECRET",
            "port": 587,
            "username": "USERNAME",
            "ssl": false,
            "smtp": "smtp.mailgun.org"
        }
    ]
}

Install Mailpit

Install Mailpit using Homebrew.

brew install mailpit

Start Mailpit

Start the Mailpit service.

brew services start mailpit

Configure ColdFusion to Use Mailpit

Method 1: Configure with CommandBox

Create a file named mailSettingsMailpit.json.

{
    "mailServers": [
        {
            "tls": false,
            "password": "",
            "port": 1025,
            "username": "",
            "ssl": false,
            "smtp": "127.0.0.1"
        }
    ]
}

Import the Mailpit configuration into ColdFusion.

cfconfig import from=mailSettingsMailpit.json to=server.local

Method 2: Configure in ColdFusion Administrator

Navigate to:

ColdFusion Administrator → Server Settings → Mail

Configure the following values:

Mail Server: 127.0.0.1
Server Port: 1025
Username: blank
Password: blank
Use TLS: unchecked
Use SSL: unchecked

Test Email Delivery

Create a .cfm test page.

<cfscript>
    recipientCount = 5;

    for (i = 1; i <= recipientCount; i++) {

        recipient = "foo#i#@bar.com";

        cfmail(
            to = recipient,
            from = "[email protected]",
            subject = "Mailpit Test",
            type = "html"
        ) {

            writeOutput("
                <h1>Hello from ColdFusion</h1>
                <p>
                    It is #dateFormat(now(), 'mm/dd/yyyy')# at
                    #dateTimeFormat(now(), 'hh:mm:ss tt')#.
                    This should show up in Mailpit.
                </p>
            ");
        }

        sleep(3000);

        writeOutput("<p>Mail sent.</p>");
    }

    writeOutput('<p><a href="#cgi.script_name#">Run again</a></p>');
</cfscript>

Verify Email Delivery

Open the Mailpit web interface:

http://localhost:8025

The test emails should appear in the Mailpit inbox.

Mailpit inbox
Mailpit inbox

Restore Original Mail Settings (Optional)

Restore the original ColdFusion mail configuration.

cfconfig import from=mailSettings.json to=server.local

Restart ColdFusion after restoring the configuration if required.


Stop Mailpit

Stop the Mailpit service.

brew services stop mailpit

ColdFusion Hash() Defaults Changed — Here’s How to Fix It With Regex

Starting with ColdFusion 2021 Update 14 and ColdFusion 2023 Update 8, the default hashing algorithm changed from CFMX_COMPAT to SHA-256.

Any code relying on Hash(value) without explicitly specifying the algorithm can:

  • Behave differently after an upgrade
  • Break verification logic
  • Trigger security scanner findings (Fixinator)

Fixinator provides the following warning:

Use of a weak hashing algorithm such as MD5 (the algorithm used by CFMX_COMPAT). This can also be a compatibility issue (after CF2023 update 8 and CF2021 update 14) if the hash algorithm is not specified. The default has changed from CFMX_COMPAT to SHA-256 in those releases.
In CFML, Hash() can appear in two contexts:
  1. Output expressions: #Hash(value)#
  2. Script/logic: Hash(value)

Any global refactor must account for both forms. It took me a few iterations to get what I needed.

This article demonstrates how to perform a global search and replace using REGEX in VS Code.

Note(s):

  • In the VS Code search panel REGEX is enabled with the .* icon to the right of the search input.
  • The ColdFusion app I was working with used only Hash() and not hash().
  • You could use a case insensitive search with the REGEX from Iteration 3 with “Preserve Case” for the replace input to account for Hash() vs hash() if necessary.
  • Your mileage may vary on this solution.

WARNING

PLEASE PREVIEW THE RESULTS OF YOUR SEARCHES BEFORE DOING THE REPLACE.


Iteration 1

This was my first attempt.

Search: #Hash\(\s*([^)]*?)\s*\)#
Replace: #Hash($1, "SHA-256", "UTF-8")#
Bad Match: <a href="edit.cfm?newsID=#qData.newsID#&verifyID=#Hash(qData.newsID, ">Edit</a>
Bad Result: <a href="edit.cfm?newsID=#qData.newsID#&verifyID=#Hash(qData.newsID, ">Edit</a>
Why it’s bad: Caused incorrect code if the algorithm argument already existed.

Iteration 2

Based on the failure of the first attempt I made the following second attempt.

Search: #Hash\(\s*([^,\)]+)\s*\)#
Replace: #Hash($1, "SHA-256", "UTF-8")#
Missed Match:

<cfif Hash(URL.newsID) EQ URL.newsID>
...
</cfif>

Why it’s bad: No match when there were no pound signs (ie Script/logic not output)

Iteration 3

Third time is a charm!

Search: \bHash\(\s*([^,\)\r\n]+?)\s*\)
Replace: Hash($1, "SHA-256", "UTF-8")

cfhash search and replace regex
cfhash search and replace with regex vscode screenshot

Conclusion

YAY! This result yielded 206 corrections throughout the app that would have taken a long time to correct without a REGEX search and replace. This legacy app is 20+ years old so the first goal was compatibility. In a follow up article I’ll look at improving security with HMAC.

Note

This post was amended from “the default hashing algorithm changed from MD5 to SHA-256” to “the default hashing algorithm changed from CFMX_COMPAT to SHA-256”. Even though they do the same thing the default was technically CFMX_COMPAT.

Find a column in a table by column name (MSSQL)

A SQL script was provided for me to run on behalf of a workgroup yesterday. The script was supposed to run a simple update on some problematic records. The database is one that is in use by many different clients with varying degrees of customization. The script failed with this error message:

Msg 207, Level 16, State 1, Line 1 Invalid column name 'incidentId'.

It’s a pretty straightforward error. The column ‘incidentId’ doesn’t exist in the table that the script was trying to update.

Since I am unfamiliar with the database I wanted to provide the workgroup any potentially useful information they could take to the developer such as tables which did contain the column or something close. Below are the queries I used.

Option 1
-- Find all tables containing the column incidentId
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = 'incidentId';

Option 2
-- Find all tables and columns with a column name that contains 'incident'
SELECT TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME LIKE '%incident%'
ORDER BY COLUMN_NAME;

Turned out the update script just had the wrong column name.  🙁

ColdFusion 2021 Update 19 – Administrator Not Installed and toInstallBundles.txt (Access is Denied) Issue

I administer two servers running ColdFusion 2021. One of them is on a VPS. The other is on a VM in a highly restricted internal environment. For both servers the following manual install method is used for updates.

  1. Stop the ColdFusion service.
  2. Run the command prompt as administrator then execute:
    C:\ColdFusion2021\jre\bin\java -jar C:\Path\To\hotfix\hotfix-019-330379.jar
  3. Follow the GUI installer prompts until the install is completed.
  4. Restart the ColdFusion service.
  5. Login to the CFAdmin and verify the version on the System Information page.

Everything went fine for the VPS. When I tried to login to the CFAdmin on the internal server after the upgrade, however, I was greeted with the following error:

Update 19 install administrator error
Update 19 install administrator error

This error message occurs when you attempt to execute functionality that depends on a package that has not been installed. Kind of odd in this case since the administrator package was previously installed, but since I’ve seen this once before it didn’t seem that concerning. I launched the ColdFusion Package Manager by navigating to C:\ColdFusion2021\bin\ and executing cfpm.bat. Then I ran install administrator as suggested in the error message. I received the following error(s) in the cfpm prompt.

Update 19 cfpm install administrator error 1
Update 19 cfpm install administrator error 1

I’ve actually seen this error before on the internal server:
The packages repository https://www.adobe.com/go/coldfusion-packages is not accessible.
The second part of the error contained the tip I used to resolve it previously:
You can only load the packages that are available locally in the C:\ColdFusion2021\bundles directory.
As I did previously I went to the C:\ColdFusion2021\bundles directory on our other server and copied the administrator package files and placed them in the same location on the internal server:

  • administrator-2021.0.19.330379.jar
  • administrator-2021.0.19.330379.zip

I ran install administrator again. The package seemed to install, however, the error regarding toInstallBundles.txt remained.

Update 19 cfpm install administrator error 2
Update 19 cfpm install administrator error 2

I was still unable to access CFAdmin with the same The administrator is not installed error so I navigated to the C:\ColdFusion2021\cfusion\lib\toInstallBundles.txt file and checked the properties and it seemed as though the account running ColdFusion had read/write access to it. I moved the file to the desktop and then moved it back.

I ran install administrator again and this time there were no errors and CFAdmin was installed and accessible.

Any thoughts on a better way to handle/prevent these errors in future upgrades?

Cleaner code with CFML Formatter (VSCode extension) + cfformat (CommandBox module)

I don’t work on a lot of non-CFML development but I had a couple of PHP projects and a JavaScript project I was working on last month. In an effort to tidy up my code in those projects I started using Prettier. I even wrote a post on Prettier and how I would be including it in future projects. Well, this weekend I was working on a CFML project and came across Mark Drew’s incredible CFML formatter extension for Visual Studio Code. Per the documentation:

CFML formatter is a Visual Studio Code extension that provides formatting for CFML files using Lucee Server and CFFormat

CFML formatter and cfformat are two great tools you can use to:

  1. Set and implement coding standards for yourself and/or your team.
  2. Format code in real time as you work in Visual Studio Code.
  3. Scan, review, and even format code issues manually or in an automated manner by watching directories.

Installation

CFML formatter

Install the CFML formatter VSCode extension from the Extensions view in Visual Studio Code.

cfformat

Install the cfformat CommandBox module by launching CommandBox and running the following command.

box install commandbox-cfformat

A few things you can do with CFML formatter

Format code on save

This will format your code using CFML formatter every time you save a file in Visual Studio Code (you can define the rules using a .cfformat.json file which you can also share with your team!).

To configure Format code on save:

  • Open Settings by pressing Cmd+, for Mac (or CTRL+, for Windows/Linux).
  • Type format in the search box and enable the option Format On Save.
Format on save in VSCode
Format on save in VSCode

Format code using right click

This will format your code using CFML formatter when you right click in the Visual Studio Code editor and choose Format Document

More info on CFML formatter

You should also check out CFRules.

Read the full CFML formatter documentation at Visual Studio Marketplace.

A few things you can do with cfformat

Run a wizard

cfformat settings wizard

The wizard will:

  1. Walk you through all settings.
  2. Display the options AND what those options will do to an example of code.
  3. Indicate the default setting.
  4. Generate a .cfformat.json file for you based on your choices.

This wizard option is incredible!

View existing settings

cfformat settings show

cfformat settings show command in CommandBox
cfformat settings show command in CommandBox

These settings can be stored in a .cfformat.json file in your Visual Studio Code project. They will then govern Format code on save and the Format Document on right click action.

Read the full settings reference at: https://github.com/jcberquist/commandbox-cfformat/blob/master/reference.md.

Learn more about a setting

cfformat settings info tab_indent

cfformat settings show info command in CommandBox
cfformat settings show info command in CommandBox

Checking Tag structure

This option looks for:

tags that are unbalanced or incorrectly structured

Check a file:

cfformat tag-check about.cfm

cfformat tag-check file command in CommandBox
cfformat tag-check file command in CommandBox

Check a directory:

cfformat tag-check

cfformat tag-check command in CommandBox
cfformat tag-check command in CommandBox

Not only will cfformat tag-check locate cfml tag issues it will also locate html tag issues as shown above!

More info on cfformat

Check out some other really cool stuff like Watching Directories and Ignoring Code Sections.

Read the full cfformat documentation at FORGEBOX.

In Summary

My workflow is going to be:

  1. Generate a .cfformat.json file in my CFML project using the cfformat CommandBox module
  2. Let CFML formatter format my code on save in Visual Studio Code (governed by the .cfformat.json file)
  3. Periodically I will manually run cfformat tag-check on my project using the cfformat CommandBox module.

ckEditor, jQuery Validation Plugin, Bootstrap 5 Implementation

I recently had a project at work with a very specific desired front-end stack and functional requirements. After building out the project I came up with a demo to show how I was able to put it all together.

This demo implements multiple instances of ckEditor in conjunction with the jQuery Validation Plugin and Bootstrap 5. There are commented out settings to implement ckEditor’s subscription version.

See the Pen ckEditor-jQuery-Validation-Plugin-Bootstrap-5-Implementation by Chris Simmons (@csimmons_dev) on CodePen.

ColdFusion Scheduled Tasks Failing with 403 Forbidden Error (Cloudflare Issue)

My company recently experienced an issue where all of the scheduled tasks in CFADMIN were failing. The first step I took to troubleshoot the issue was to check the scheduler.log log file. Each task had 2 lines in the log file. The first line indicated that the task had been triggered. The second line indicated an error of 403 Forbidden.

"Information","DefaultQuartzScheduler_Worker-3","11/08/24","07:00:00","","Task DEFAULT.NIGHTLY CLEANUP JOB triggered."
"Error","DefaultQuartzScheduler_Worker-3","11/08/24","07:00:00","","403 Forbidden"

Since no permissions had been changed on the server this was a perplexing error. The next step that I took was to execute one of the task URLs from a web browser on the sever. The task completed successfully. This led me to try to obtain more information about CFADMIN running the task so I enabled Save output to file under the Publish section of the Scheduled Task and specified a file to output the result.

CFADMIN Scheduled Task Log to File
CFADMIN Scheduled Task Log to File

Once this setting was in place I executed the task again from CFADMIN > Server Settings > Scheduled Tasks and checked the log file. The log file contained the text error code: 1010.

CFADMIN Scheduled Task Log to File result
CFADMIN Scheduled Task Log to File result

Researching error code: 1010 led me to several articles regarding Cloudfare blocking access to a site based on the browser signature.

This narrowed the issue to either an issue with Cloudfare or the task not running correctly when executed by ColdFusion. I decided to try execute the URL from a ColdFusion cfhttp call using the following basic script.

The task completed successfully when called from cfhttp. Below is a dump of the result:

Dump of cfhttp
Dump of cfhttp

The issue therefore seemed to be narrowed to the fact that Cloudfare was rejecting calls to URLs from the CFADMIN (apparently based on an issue with the browser signature). The browser signature is examined at Cloudfare by a Browser Integrity Check (BIC) as a component of a WAF.

A WAF or web application firewall helps protect web applications by filtering and monitoring HTTP traffic between a web application and the Internet. It typically protects web applications from attacks such as cross-site forgery, cross-site-scripting (XSS), file inclusion, and SQL injection, among others.

Read more about Cloudfare’s WAF.

You can create a custom WAF rule to turn off the Browser Integrity Check (BIC). First, use the Go to navigation to search for WAF and choose Security | WAF | Custom Rules:

WAF Go to
WAF Go to

Next, click the Create rule button to begin. Our solution will use the following settings to disable the BIC on requests only from our server IP to scripts residing in a certain directory:

  • For the `Field` select `IP Source Address`, for `Operator` select `equals` and enter the IP address of your server as the `Value` (this will allow the rule to only apply to requests from your server).
  • Click the `And` button to add another row.
  • For the `Field` select `URI Path`, for `Operator` select `wildcard` and and enter the directory of your scheduled tasks as the `Value` (this will allow a single rule to apply to multiple task scripts in the directory). Notice the directory uses a wildcard at the end `/jobs/*`.
  • For the `Choose action` select `skip`.
  • Select `On` for `Log matching requests`.
  • Under `WAF components to skip` check `Browser Integrity Check` (you may need to click the `More components to skip` link to locate it).
  • Click the `Deploy` button to enable the rule immediately.
WAF Create rule
WAF Create rule

You can view the logging of the Firewall events. First, use the Go to navigation to search for Events and choose Security | Events:

Events Go to
Events Go to

As you can see the previously blocked requests via CFADMIN are now allowed at Cloudfare via a Skip action using Custom rules.

Firewall events
Firewall events