Skip to content

Latest commit

 

History

History
358 lines (284 loc) · 9.9 KB

File metadata and controls

358 lines (284 loc) · 9.9 KB
name contentbox-boxlang-module-development
description Use this skill when building ContentBox modules, including ModuleConfig conventions, admin integration, interceptors, routes, migrations, ORM entities, dependency registration, and module lifecycle hooks.
applyTo **/*.{bx,bxm,cfc,cfm,cfml}

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

  1. Use modules_app/contentbox-custom/_modules/ for user-owned modules
  2. Declare dependencies in this.dependencies
  3. Use namespace injection@moduleName for DI
  4. Follow ColdBox conventions — handlers, models, views, layouts
  5. Use migrations for database changes — never modify schema directly
  6. Extend base classesBaseEntity, baseHandler, BaseWidget
  7. Register interceptors for admin menu, content lifecycle, etc.
  8. Use provider: injection to avoid circular dependencies
  9. Include box.json for ForgeBox distribution
  10. 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 .bx templates
  • Modern syntax: ?: null coalescing, ?. safe navigation
  • Native support for modern data structures