| name | coldbox-module-development |
|---|---|
| description | Use this skill when creating reusable ColdBox modules, writing ModuleConfig.cfc, defining module-specific routes, models, settings, and interceptors, packaging modules for ForgeBox, or building internal application sub-systems as modules. |
Use this skill when creating ColdBox modules — reusable, self-contained sub-applications with their own routes, handlers, services, and configuration.
Examples use BoxLang (.bx) syntax by default. Adapt for your target language:
| Concept | BoxLang (.bx) |
CFML (.cfc) |
|---|---|---|
| Class declaration | class [extends="..."] { |
component [extends="..."] { |
| DI annotation | @inject above property name="svc"; |
property name="svc" inject="svc"; |
| View templates | .bxm suffix |
.cfm / .cfml suffix |
| Tag prefix | <bx:if>, <bx:output>, <bx:set> |
<cfif>, <cfoutput>, <cfset> |
CFML Compat Mode: With BoxLang + CFML Compat module,
.bxand.cfcfiles coexist freely. BoxLang-native classes useclass {}(.bxfiles); CFML-compat classes usecomponent {}(.cfcfiles).
ColdBox modules:
- Live in
modules/(external) ormodules_app/(internal) - Have a
ModuleConfig.cfcfor registration and configuration - Have a
box.jsonfor package metadata - Can declare routes, settings, interceptors, and DI bindings
- Can be packaged and distributed on ForgeBox
- Are isolated via their own classloader mappings
modules_app/
myModule/
box.json # Package metadata
ModuleConfig.cfc # Module registration
config/
Router.cfc # Module routes
WireBox.cfc # DI bindings (optional)
handlers/
Dashboard.cfc
models/
WidgetService.cfc
views/
dashboard/
index.cfm
layouts/
Module.cfm
interceptors/
ModuleInterceptor.cfc
class ModuleConfig {
// ======================================================
// Module Properties
// ======================================================
property name="title" default="My Module";
property name="description" default="A ColdBox module";
property name="version" default="1.0.0";
property name="author" default="Your Name";
property name="webURL" default="https://example.com";
property name="entryPoint" default="mymodule";
property name="cfmapping" default="myModule";
property name="autoMapModels" default="true";
// ======================================================
// Module Configuration
// ======================================================
function configure() {
// Module settings (configurable from host app)
settings = {
apiKey : "",
timeout : 30,
debug : false
}
// Optional layout settings
layoutSettings = {
defaultLayout : "Module"
}
// Module-specific interceptors
interceptors = [
{
class : "#moduleMapping#.interceptors.ModuleInterceptor",
name : "MyModuleInterceptor"
}
]
// Module-specific CacheBox caches
cachebox = {}
// WireBox binder config path
wirebox = {
binder : "#moduleMapping#.config.WireBox"
}
}
// ======================================================
// Lifecycle Hooks
// ======================================================
function onLoad() {
// Runs when module is loaded (after configure())
var settings = controller.getModuleSettings( "myModule" )
log.info( "myModule loaded with settings: #settings.toString()#" )
}
function onUnload() {
// Runs when module is unloaded
log.info( "myModule unloaded" )
}
}
CFML (.cfc):
component {
// ======================================================
// Module Properties
// ======================================================
property name="title" default="My Module";
property name="description" default="A ColdBox module";
property name="version" default="1.0.0";
property name="author" default="Your Name";
property name="webURL" default="https://example.com";
property name="entryPoint" default="mymodule";
property name="cfmapping" default="myModule";
property name="autoMapModels" default="true";
// ======================================================
// Module Configuration
// ======================================================
function configure() {
// Module settings (configurable from host app)
settings = {
apiKey : "",
timeout : 30,
debug : false
}
// Optional layout settings
layoutSettings = {
defaultLayout : "Module"
}
// Module-specific interceptors
interceptors = [
{
class : "#moduleMapping#.interceptors.ModuleInterceptor",
name : "MyModuleInterceptor"
}
]
// Module-specific CacheBox caches
cachebox = {}
// WireBox binder config path
wirebox = {
binder : "#moduleMapping#.config.WireBox"
}
}
// ======================================================
// Lifecycle Hooks
// ======================================================
function onLoad() {
// Runs when module is loaded (after configure())
var settings = controller.getModuleSettings( "myModule" )
log.info( "myModule loaded with settings: #settings.toString()#" )
}
function onUnload() {
// Runs when module is unloaded
log.info( "myModule unloaded" )
}
}// modules_app/myModule/config/Router.cfc
class Router extends coldbox.system.web.routing.Router {
function configure() {
// All routes here are prefixed with entryPoint
// e.g., /mymodule/...
route( "/", "dashboard.index" )
route( "/dashboard", "dashboard.index" )
// Resource routes
resources( "widgets" )
// API routes
group( pattern = "/api" ) {
resources( "items" )
}
// Wildcard fallback
route( "/:handler/:action?" )
}
}
CFML (.cfc):
// modules_app/myModule/config/Router.cfc
component extends="coldbox.system.web.routing.Router" {
function configure() {
// All routes here are prefixed with entryPoint
// e.g., /mymodule/...
route( "/", "dashboard.index" )
route( "/dashboard", "dashboard.index" )
// Resource routes
resources( "widgets" )
// API routes
group( pattern = "/api" ) {
resources( "items" )
}
// Wildcard fallback
route( "/:handler/:action?" )
}
}class Dashboard extends coldbox.system.EventHandler {
@inject
property name="widgetService";
// Get module settings in handler
property name="settings" inject="coldbox:moduleSettings:myModule";
function index( event, rc, prc ) {
prc.widgets = widgetService.list()
prc.pageTitle = "Dashboard"
prc.apiKey = settings.apiKey
event.setView( "dashboard/index" )
}
}
CFML (.cfc):
component extends="coldbox.system.EventHandler" {
property name="widgetService" inject="widgetService";
// Get module settings in handler
property name="settings" inject="coldbox:moduleSettings:myModule";
function index( event, rc, prc ) {
prc.widgets = widgetService.list()
prc.pageTitle = "Dashboard"
prc.apiKey = settings.apiKey
event.setView( "dashboard/index" )
}
}class WidgetService {
// Injected module settings
@inject( "coldbox:moduleSettings:myModule" )
property name="settings";
@inject
property name="wirebox";
function list() {
return queryExecute(
"SELECT * FROM widgets ORDER BY created_at DESC",
{},
{ datasource: settings.datasource ?: "myapp" }
)
}
function getById( id ) {
var result = queryExecute(
"SELECT * FROM widgets WHERE id = :id",
{ id: { value: id, cfsqltype: "integer" } },
{ datasource: settings.datasource ?: "myapp" }
)
return result.recordcount ? result : javaCast( "null", "" )
}
}
CFML (.cfc):
component {
// Injected module settings
property name="settings" inject="coldbox:moduleSettings:myModule";
property name="wirebox" inject="wirebox";
function list() {
return queryExecute(
"SELECT * FROM widgets ORDER BY created_at DESC",
{},
{ datasource: settings.datasource ?: "myapp" }
)
}
function getById( id ) {
var result = queryExecute(
"SELECT * FROM widgets WHERE id = :id",
{ id: { value: id, cfsqltype: "integer" } },
{ datasource: settings.datasource ?: "myapp" }
)
return result.recordcount ? result : javaCast( "null", "" )
}
}{
"name" : "my-module",
"version" : "1.0.0",
"author" : "Your Name <you@example.com>",
"description" : "A ColdBox module",
"homepage" : "https://github.com/yourorg/my-module",
"license" : "Apache-2.0",
"slug" : "my-module",
"type" : "modules",
"keywords" : [ "coldbox", "module" ],
"dependencies": {
"coldbox" : "^7.0.0"
},
"devDependencies": {
"testbox" : "^5.0.0"
}
}Use module inception when one ColdBox module depends on another ColdBox module and should install or bootstrap it automatically.
{
"name" : "content-module",
"type" : "modules",
"dependencies" : {
"cbvalidation" : "^4.0.0",
"cbauth" : "^2.0.0"
}
}Keep inception boundaries clean:
- Declare module dependencies in
box.json, not in ad hoc runtime checks - Let each module own its config, routes, interceptors, and binder registrations
- Avoid hard-coding host-app paths when a dependent module should remain portable
// config/ColdBox.cfc → moduleSettings
moduleSettings = {
myModule : {
apiKey : getSystemSetting( "MY_MODULE_API_KEY", "" ),
timeout : 60,
debug : ( server.system.environment.APP_ENV ?: "production" ) == "development"
}
}
// modules_app/myModule/interceptors/ModuleInterceptor.cfc
class ModuleInterceptor extends coldbox.system.Interceptor {
property name="settings" inject="coldbox:moduleSettings:myModule";
function configure() {
log.info( "MyModule interceptor configured" )
}
function preProcess( event, rc, prc, interceptData ) {
if( settings.debug ){
log.debug( "Module request: #event.getCurrentEvent()#" )
}
}
}
CFML (.cfc):
// modules_app/myModule/interceptors/ModuleInterceptor.cfc
component extends="coldbox.system.Interceptor" {
property name="settings" inject="coldbox:moduleSettings:myModule";
function configure() {
log.info( "MyModule interceptor configured" )
}
function preProcess( event, rc, prc, interceptData ) {
if( settings.debug ){
log.debug( "Module request: #event.getCurrentEvent()#" )
}
}
}// modules_app/myModule/ModuleConfig.cfc
class ModuleConfig {
function configure() {
settings = {
cfmapping : "/mymodule"
}
}
}
Use module helpers and CF mappings intentionally:
- Add module-level or handler-level helper templates for reusable view logic that belongs to the module
- Expose a dedicated CF mapping when external code needs stable access to module assets or classes
- Keep helpers presentation-focused; do not move service/business logic into helper files
// modules_app/myModule/config/WireBox.cfc
class WireBox extends coldbox.system.ioc.config.Binder {
function configure() {
// Map an interface to an implementation
mapPath( "#moduleMapping#.models.WidgetService" )
.asSingleton()
// Map with custom ID
map( "widgetRepository" )
.to( "#moduleMapping#.models.WidgetRepository" )
.asSingleton()
// Map with constructor args
map( "myModuleClient" )
.to( "#moduleMapping#.models.APIClient" )
.asSingleton()
.initWith( apiKey = "@settings.apiKey@" )
}
}
CFML (.cfc):
// modules_app/myModule/config/WireBox.cfc
component extends="coldbox.system.ioc.config.Binder" {
function configure() {
// Map an interface to an implementation
mapPath( "#moduleMapping#.models.WidgetService" )
.asSingleton()
// Map with custom ID
map( "widgetRepository" )
.to( "#moduleMapping#.models.WidgetRepository" )
.asSingleton()
// Map with constructor args
map( "myModuleClient" )
.to( "#moduleMapping#.models.APIClient" )
.asSingleton()
.initWith( apiKey = "@settings.apiKey@" )
}
}- Use
moduleMapping(injected by ColdBox) for fully-qualified class paths - Access module settings via
cold box:moduleSettings:moduleNameinjection DSL - Use
onLoad()andonUnload()for setup/teardown initialization - Set
autoMapModels = trueso models auto-register with WireBox - Keep modules self-contained — don't depend on host app models
- Test modules independently with their own TestBox specs
- Publish finished modules to ForgeBox with semantic versioning