contentbox-boxlang-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 (BoxLang)
Build custom modules for ContentBox CMS using BoxLang. 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.bx
Every module requires a ModuleConfig.bx at its root:
// modules_app/contentbox-custom/_modules/myModule/ModuleConfig.bx
// Module Properties
this.title = "My Module"
this.author = "Your Name"
this.webURL = "https://example.com"
this.version = "1.0.0"
this.description = "Description of my module"
this.viewParentLookup = true
this.layoutParentLookup = true
this.entryPoint = "mymodule"
this.modelNamespace = "mymodule"
this.cfmapping = "mymodule"
this.dependencies = [ "contentbox" ]
function configure(){
// Module Settings
settings = {
mySetting : "defaultValue"
}
// Layout Settings
layoutSettings = {
defaultLayout : "layout.bx"
}
// Custom Interception Points
interceptorSettings = {
customInterceptionPoints : [
"onMyModuleEvent"
]
}
// Interceptors
interceptors = [
{
class : "mymodule.interceptors.MyInterceptor",
name : "MyInterceptor@mymodule"
}
]
// Routes
routes = [
{ pattern : "/mymodule/:action", handler : "home" }
]
// i18n
cbi18n = {
resourceBundles : {
"mymodule" : "#moduleMapping#/i18n/mymodule"
}
}
}
function onLoad(){
// Post-load initialization
// Register services, widgets, etc.
}
function onUnload(){
// Cleanup on module unload
}
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.bx โ Module configuration
โโโ box.json โ ForgeBox/CommandBox metadata
โโโ handlers/ โ ColdBox event handlers
โ โโโ Home.bx
โโโ models/ โ Business logic and entities
โ โโโ services/
โ โ โโโ MyService.bx
โ โโโ entities/
โ โโโ MyEntity.bx
โโโ views/ โ View templates
โ โโโ home/
โ โโโ index.bx
โโโ layouts/ โ Layout templates
โ โโโ layout.bx
โโโ interceptors/ โ ColdBox interceptors
โ โโโ MyInterceptor.bx
โโโ widgets/ โ ContentBox widgets
โ โโโ MyWidget.bx
โโโ modules/ โ Nested sub-modules
โ โโโ subModule/
โโโ config/ โ Configuration files
โ โโโ Router.bx
โโโ i18n/ โ Resource bundles
โ โโโ mymodule.properties
โโโ migrations/ โ Database migrations
โ โโโ 001_create_my_table.bx
โโโ includes/ โ Static assets, help files
โโโ css/
โโโ js/
โโโ help/
Creating an Admin Module
To add functionality to the ContentBox admin:
// ModuleConfig.bx with admin integration
this.title = "My Admin Module"
this.entryPoint = "cbadmin/myModule"
this.modelNamespace = "myModule"
this.dependencies = [ "contentbox", "contentbox-admin" ]
function configure(){
settings = {}
interceptorSettings = {
customInterceptionPoints : []
}
routes = [
{ pattern : "/cbadmin/myModule/:action", handler : "myModule" }
]
}
function onLoad(){
// Register with admin menu via interception
}
Admin Handler Pattern
// handlers/MyModule.bx
component extends="contentbox.modules.contentbox-admin.handlers.baseHandler" singleton {
function index(){
prc.pageTitle = "My Module"
setView( "myModule/index" )
}
}
Registering Admin Menu Items
Listen to cbadmin_onAdminMenuLoad to add menu items:
// interceptors/AdminMenu.bx
component {
function configure(){
}
function onAdminMenuLoad( event, data, buffer, rc, prc ){
// Add menu item to data.menuItems
arrayAppend( data.menuItems, {
name : "My Module",
link : event.buildLink( "cbadmin/myModule" ),
icon : "star",
permission : "MYMODULE_ACCESS"
} )
}
}
Registering 2FA Providers
Register custom two-factor authentication providers in onLoad():
function onLoad(){
twoFactorService = wirebox.getInstance( "twoFactorService@contentbox" )
twoFactorService.registerProvider(
wirebox.getInstance( "MyTwoFactorProvider@myModule" )
)
}
Registering Content Helpers
Inject mixins into all content objects:
// In core ModuleConfig.bx configure()
settings.contentHelpers = [
"myModule.models.mixins.MyContentMixin"
]
Database Migrations
Use cfmigrations for schema changes:
// migrations/001_create_my_table.bx
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" )
}
Run migrations:
box run-script contentbox:migrate
box run-script contentbox:migrate:up
ORM Entities
Create persistent entities with ContentBox conventions:
// models/entities/MyEntity.bx
component persistent="true" entityname="cbMyEntity" table="cb_my_table" extends="contentbox.models.BaseEntity" {
property name="myEntityID" fieldtype="id" generator="uuid" ormtype="string"
property name="name" ormtype="string" length="255" notnull="true"
property name="description" ormtype="text"
}
Module Dependencies
Declare dependencies in ModuleConfig.bx:
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
property name="entryService" inject="entryService@contentbox"
property name="pageService" inject="pageService@contentbox"
property name="authorService" inject="authorService@contentbox"
property name="categoryService" inject="categoryService@contentbox"
property name="menuService" inject="menuService@contentbox"
property name="settingService" inject="settingService@contentbox"
property name="securityService" inject="securityService@contentbox"
property name="widgetService" inject="widgetService@contentbox"
property name="themeService" inject="themeService@contentbox"
property 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 BoxLang engine. For CFML-specific syntax (Lucee 5+, Adobe ColdFusion 2018+), see the CFML variant of this skill.
Key BoxLang advantages:
- Cleaner script syntax without
<cfcomponent>/<cffunction>tags - No parentheses needed for zero-argument function calls
#{...}#for inline expression output in.bxtemplates- Modern syntax:
?:null coalescing,?.safe navigation - Native support for modern data structures