contentbox-cfml-module-development
Use this skill when building ContentBox modules, including ModuleConfig conventions, admin integration, interceptors, routes, migrations, ORM entities, dependency registration, and module lifecycle hooks.
ContentBox Module Development (CFML)
Build custom modules for ContentBox CMS using CFML. Modules are ColdBox modules that extend ContentBox functionality โ adding new admin panels, API endpoints, widgets, interceptors, or content features.
Module Architecture
ContentBox uses a layered module architecture:
| Layer | Path | Purpose |
|---|---|---|
| Core | modules/contentbox/ | ContentBox engine (do not modify) |
| Sub-modules | modules/contentbox/modules/ | Admin, API, UI, deps |
| Custom | modules_app/contentbox-custom/ | User-owned customizations |
| Standalone | modules/ | Independent ColdBox modules |
Module Locations for Custom Code
| Type | Location |
|---|---|
| Custom modules | modules_app/contentbox-custom/_modules/ |
| Custom widgets | modules_app/contentbox-custom/_widgets/ |
| Custom themes | modules_app/contentbox-custom/_themes/ |
| Custom content | modules_app/contentbox-custom/_content/ |
| Standalone modules | modules/{moduleName}/ |
ModuleConfig.cfc
Every module requires a ModuleConfig.cfc at its root:
<!--- modules_app/contentbox-custom/_modules/myModule/ModuleConfig.cfc --->
<cfcomponent>
<!--- Module Properties --->
<cfset this.title = "My Module">
<cfset this.author = "Your Name">
<cfset this.webURL = "https://example.com">
<cfset this.version = "1.0.0">
<cfset this.description = "Description of my module">
<cfset this.viewParentLookup = true>
<cfset this.layoutParentLookup = true>
<cfset this.entryPoint = "mymodule">
<cfset this.modelNamespace = "mymodule">
<cfset this.cfmapping = "mymodule">
<cfset this.dependencies = [ "contentbox" ]>
<cffunction name="configure" access="public" returntype="void">
<!--- Module Settings --->
<cfset settings = {
mySetting : "defaultValue"
}>
<!--- Layout Settings --->
<cfset layoutSettings = {
defaultLayout : "layout.cfm"
}>
<!--- Custom Interception Points --->
<cfset interceptorSettings = {
customInterceptionPoints : [
"onMyModuleEvent"
]
}>
<!--- Interceptors --->
<cfset interceptors = [
{
class : "mymodule.interceptors.MyInterceptor",
name : "MyInterceptor@mymodule"
}
]>
<!--- Routes --->
<cfset routes = [
{ pattern = "/mymodule/:action", handler = "home" }
]>
<!--- i18n --->
<cfset cbi18n = {
resourceBundles : {
"mymodule" : "#moduleMapping#/i18n/mymodule"
}
}>
</cffunction>
<cffunction name="onLoad" access="public" returntype="void">
<!--- Post-load initialization --->
<!--- Register services, widgets, etc. --->
</cffunction>
<cffunction name="onUnload" access="public" returntype="void">
<!--- Cleanup on module unload --->
</cffunction>
</cfcomponent>
ModuleConfig Properties
| Property | Description |
|---|---|
title | Module display name |
author | Author name |
webURL | Author/project website |
version | Module version |
description | Module description |
viewParentLookup | Inherit views from parent modules (true/false) |
layoutParentLookup | Inherit layouts from parent modules (true/false) |
entryPoint | SES URL prefix for module routes |
modelNamespace | WireBox DI namespace (e.g., @mymodule) |
cfmapping | ColdFusion mapping for the module |
dependencies | Array of required module names |
ModuleConfig Methods
| Method | When Called | Purpose |
|---|---|---|
configure() | Module registration | Define settings, routes, interceptors |
onLoad() | After module loaded | Post-load initialization |
onUnload() | Module unloaded | Cleanup resources |
onMissingMethod() | Missing method calls | Dynamic method handling |
Module Directory Structure
myModule/
โโโ ModuleConfig.cfc โ Module configuration
โโโ box.json โ ForgeBox/CommandBox metadata
โโโ handlers/ โ ColdBox event handlers
โ โโโ Home.cfc
โโโ models/ โ Business logic and entities
โ โโโ services/
โ โ โโโ MyService.cfc
โ โโโ entities/
โ โโโ MyEntity.cfc
โโโ views/ โ View templates
โ โโโ home/
โ โโโ index.cfm
โโโ layouts/ โ Layout templates
โ โโโ layout.cfm
โโโ interceptors/ โ ColdBox interceptors
โ โโโ MyInterceptor.cfc
โโโ widgets/ โ ContentBox widgets
โ โโโ MyWidget.cfc
โโโ modules/ โ Nested sub-modules
โ โโโ subModule/
โโโ config/ โ Configuration files
โ โโโ Router.cfc
โโโ i18n/ โ Resource bundles
โ โโโ mymodule.properties
โโโ migrations/ โ Database migrations
โ โโโ 001_create_my_table.cfm
โโโ includes/ โ Static assets, help files
โโโ css/
โโโ js/
โโโ help/
Creating an Admin Module
To add functionality to the ContentBox admin:
<!--- ModuleConfig.cfc with admin integration --->
<cfcomponent>
<cfset this.title = "My Admin Module">
<cfset this.entryPoint = "cbadmin/myModule">
<cfset this.modelNamespace = "myModule">
<cfset this.dependencies = [ "contentbox", "contentbox-admin" ]>
<cffunction name="configure">
<cfset settings = {}>
<cfset interceptorSettings = {
customInterceptionPoints : []
}>
<cfset routes = [
{ pattern = "/cbadmin/myModule/:action", handler = "myModule" }
]>
</cffunction>
<cffunction name="onLoad">
<!--- Register with admin menu via interception --->
</cffunction>
</cfcomponent>
Admin Handler Pattern
<!--- handlers/MyModule.cfc --->
<cfcomponent extends="contentbox.modules.contentbox-admin.handlers.baseHandler" singleton>
<cffunction name="index" access="public" returntype="void">
<cfset prc.pageTitle = "My Module">
<cfset setView( "myModule/index" )>
</cffunction>
</cfcomponent>
Registering Admin Menu Items
Listen to cbadmin_onAdminMenuLoad to add menu items:
<!--- interceptors/AdminMenu.cfc --->
<cfcomponent>
<cffunction name="configure">
</cffunction>
<cffunction name="onAdminMenuLoad" access="public" returntype="void">
<cfargument name="event" type="any">
<cfargument name="data" type="struct">
<cfargument name="buffer" type="any">
<cfargument name="rc" type="struct">
<cfargument name="prc" type="struct">
<!--- Add menu item to data.menuItems --->
<cfset arrayAppend( data.menuItems, {
name : "My Module",
link : event.buildLink( "cbadmin/myModule" ),
icon : "star",
permission : "MYMODULE_ACCESS"
} )>
</cffunction>
</cfcomponent>
Registering 2FA Providers
Register custom two-factor authentication providers in onLoad():
<cffunction name="onLoad">
<cfset var twoFactorService = wirebox.getInstance( "twoFactorService@contentbox" )>
<cfset twoFactorService.registerProvider(
wirebox.getInstance( "MyTwoFactorProvider@myModule" )
)>
</cffunction>
Registering Content Helpers
Inject mixins into all content objects:
<!--- In core ModuleConfig.cfc configure() --->
<cfset settings.contentHelpers = [
"myModule.models.mixins.MyContentMixin"
]>
Database Migrations
Use cfmigrations for schema changes:
<!--- migrations/001_create_my_table.cfm --->
<cfscript>
function up( schema, qb ){
schema.create( "my_table", function( table ){
table.increments( "id" ).primary();
table.string( "name" ).nullable( false );
table.timestamps();
} );
}
function down( schema, qb ){
schema.drop( "my_table" );
}
</cfscript>
Run migrations:
box run-script contentbox:migrate
box run-script contentbox:migrate:up
ORM Entities
Create persistent entities with ContentBox conventions:
<!--- models/entities/MyEntity.cfc --->
<cfcomponent persistent="true" entityname="cbMyEntity" table="cb_my_table" extends="contentbox.models.BaseEntity">
<cfproperty name="myEntityID" fieldtype="id" generator="uuid" ormtype="string">
<cfproperty name="name" ormtype="string" length="255" notnull="true">
<cfproperty name="description" ormtype="text">
</cfcomponent>
Module Dependencies
Declare dependencies in ModuleConfig.cfc:
<cfset this.dependencies = [
"contentbox", // Core ContentBox
"contentbox-admin", // Admin module
"contentbox-api", // API module
"cbsecurity", // Security module
"cborm" // ORM module
]>
Accessing ContentBox Services
<!--- Inject ContentBox services --->
<cfproperty name="entryService" inject="entryService@contentbox">
<cfproperty name="pageService" inject="pageService@contentbox">
<cfproperty name="authorService" inject="authorService@contentbox">
<cfproperty name="categoryService" inject="categoryService@contentbox">
<cfproperty name="menuService" inject="menuService@contentbox">
<cfproperty name="settingService" inject="settingService@contentbox">
<cfproperty name="securityService" inject="securityService@contentbox">
<cfproperty name="widgetService" inject="widgetService@contentbox">
<cfproperty name="themeService" inject="themeService@contentbox">
<cfproperty name="cb" inject="CBHelper@contentbox">
Interception Points
Core ContentBox Interception Points
| Point | When Fired |
|---|---|
cb_onContentRendering | Before content is rendered |
cb_onContentStoreRendering | Before ContentStore content is rendered |
Admin Interception Points
See the admin extension skill for the full list of cbadmin_* interception points.
Best Practices
- Use
modules_app/contentbox-custom/_modules/for user-owned modules - Declare dependencies in
this.dependencies - Use namespace injection โ
@moduleNamefor DI - Follow ColdBox conventions โ handlers, models, views, layouts
- Use migrations for database changes โ never modify schema directly
- Extend base classes โ
BaseEntity,baseHandler,BaseWidget - Register interceptors for admin menu, content lifecycle, etc.
- Use
provider:injection to avoid circular dependencies - Include
box.jsonfor ForgeBox distribution - Test with all three engines โ Lucee, Adobe CF, BoxLang
Engine Compatibility
This skill targets CFML engines (Lucee 5+, Adobe ColdFusion 2018+). For BoxLang-specific syntax and features, see the BoxLang variant of this skill.