Articles

cfSlack: A ColdFusion Component for the Slack Web API

cfSlack - Verified on seven CFML engines. Adobe ColdFusion 2016-2025 and Lucee 5 & 6.
August 27, 2026

I wanted to deploy notifications and error alerts from a few CFML apps and post straight into Slack instead of fighting spam filters or getting buried in an email inbox that nobody checks. Searching for "ColdFusion Slack API" mostly turns up coldfumonkeh/cfslack, which hasn't been updated since 2017 and predates most of the current Web API. So I built one, wrapping 50 methods across chat, conversations, users, views, and DND, tested on seven engines from Adobe ColdFusion 2016 through 2025 and Lucee 5 & 6.

Slack's Block Kit payloads are JSON, and text values like "90210" have to stay a string. Adobe ColdFusion's native serializeJSON() looks at that value, decides it's numeric, and writes the bare number 90210 instead. Lucee gets it right. Slack either rejects the payload or renders it wrong, and nothing about the failure points anywhere near serializeJSON(). cfSlack never calls the native function on an outbound payload. It has its own small JSON builder that quotes every scalar except real booleans, with an escape hatch to plug in a proper JSONUtil component if you already have one and need actual JSON numbers somewhere.

That boolean exception turned into its own rabbit hole. A bare true literal reflects as three different Java classes depending on engine: java.lang.String on ColdFusion 2016, coldfusion.runtime.CFBoolean on ColdFusion 2018 through 2025, and java.lang.Boolean on Lucee. The builder only treats a value as a real boolean when its class is exactly java.lang.Boolean, so a bare true inside a Block Kit payload comes out as the unquoted JSON boolean on Lucee and as the quoted string "true" on every Adobe version. Same code, two different but both valid payloads, depending on where it runs. javacast( "boolean", x ) sidesteps the whole thing; it reflects as java.lang.Boolean everywhere, which is why it's in the docs as the rule for anything that has to be a real boolean.

There's also a Slack-side gotcha that has nothing to do with CFML: chat.postMessage returns not_in_channel the first time a bot posts somewhere it hasn't joined, even with the chat:write scope granted. The bot has to actually be in the channel, or the app needs chat:write.public for public channels. Took me longer to track down than it should have.

Quick start:

slack = new cfSlack( botToken = "xoxb-your-bot-token" );

posted = slack.chatPostMessage(
	channel = "C0123456789",
	text    = "Hello from cfSlack"
);

if ( !posted.ok ) {
	writeOutput( "Post failed: " & posted.error );
}

Every method returns a struct and nothing throws on an API-level failure; you check ok. Anything the library doesn't wrap yet is still reachable through the public callAPI() method, so a missing endpoint is a one-line call away, not a fork.

Source Code

https://github.com/JamoCA/cf-Slack

MIT licensed. The coldfumonkeh library was read during design to build a coverage checklist, so this wouldn't miss an endpoint the older one already handled, but no code from it was carried over.