๐Ÿ”ท Core

contentbox-cfml-admin-extension

Use this skill when extending the ContentBox admin UI with custom menus, views, interception points, editor integrations, workflows, and secure admin-only module behaviors.

$ npx skills add coldbox/skills/contentbox-cfml/admin-extension
$ coldbox ai skills install coldbox/skills/contentbox-cfml/admin-extension
๐Ÿ”— https://skills.boxlang.io/skills/raw/coldbox/skills/contentbox-cfml~admin-extension

ContentBox Admin Extension (CFML)

Extend the ContentBox admin interface using CFML. Add custom panels, modify existing screens, inject HTML into admin layouts, hook into content lifecycle events, and register new admin functionality.

Admin Module Overview

The admin module lives at modules/contentbox/modules/contentbox-admin/ with entry point cbadmin. It provides:

  • 30+ handlers for managing content, authors, settings, security, etc.
  • Admin layouts with consistent navigation and UI
  • Interceptors for request processing, 2FA enforcement, menu management
  • Interception points for extending every part of the admin UI

Admin Extension Points

1. Layout HTML Injection

Inject custom HTML into the admin layout at specific points:

Interception PointLocation
cbadmin_beforeHeadEndBefore </head> tag
cbadmin_afterBodyStartAfter <body> tag
cbadmin_beforeBodyEndBefore </body> tag
cbadmin_footerAdmin footer area
cbadmin_beforeContentBefore main content area
cbadmin_afterContentAfter main content area
cbadmin_onTagLineTag line area
cbadmin_onTopBarTop navigation bar

Example: Inject Custom CSS/JS

<!--- interceptors/AdminAssets.cfc --->
<cfcomponent>

	<cffunction name="configure">
	</cffunction>

	<cffunction name="onCbadmin_beforeHeadEnd" 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">

		<cfset buffer.append( '<link rel="stylesheet" href="/modules/mymodule/includes/css/admin.css">' )>
	</cffunction>

	<cffunction name="onCbadmin_beforeBodyEnd" 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">

		<cfset buffer.append( '<script src="/modules/mymodule/includes/js/admin.js"></script>' )>
	</cffunction>

</cfcomponent>

2. Content Editor Extension

Extend the content editor (entry/page editing screens):

Interception PointLocation
cbadmin_contentEditorSidebarSidebar area
cbadmin_contentEditorSidebarAccordionSidebar accordion sections
cbadmin_contentEditorSidebarFooterBottom of sidebar
cbadmin_contentEditorFooterEditor footer
cbadmin_contentEditorInBodyInside editor body
cbadmin_contentEditorNavEditor navigation
cbadmin_contentEditorNavContentEditor nav content

Example: Add Custom Sidebar Panel

<!--- interceptors/EditorSidebar.cfc --->
<cfcomponent>

	<cffunction name="configure">
	</cffunction>

	<cffunction name="onCbadmin_contentEditorSidebarAccordion" 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">

		<cfset var content = "">
		<cfset saveContent variable="content">
			<div class="accordion-section">
				<h4>My Custom Panel</h4>
				<div class="accordion-content">
					<!--- Custom fields or content --->
					<p>Custom panel content here</p>
				</div>
			</div>
		</cfset>
		<cfset buffer.append( content )>
	</cffunction>

</cfcomponent>

3. Content Lifecycle Events

Hook into content save, remove, and status change events:

Entry Events

PointWhen
cbadmin_preEntrySaveBefore entry is saved
cbadmin_postEntrySaveAfter entry is saved
cbadmin_preEntryRemoveBefore entry is deleted
cbadmin_postEntryRemoveAfter entry is deleted
cbadmin_onEntryStatusUpdateWhen entry status changes

Page Events

PointWhen
cbadmin_prePageSaveBefore page is saved
cbadmin_postPageSaveAfter page is saved
cbadmin_prePageRemoveBefore page is deleted
cbadmin_postPageRemoveAfter page is deleted
cbadmin_onPageStatusUpdateWhen page status changes

Example: Post-Save Hook

<!--- interceptors/EntryAudit.cfc --->
<cfcomponent>

	<cffunction name="configure">
	</cffunction>

	<cffunction name="onCbadmin_postEntrySave" 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">

		<!--- data.entry contains the saved entry --->
		<cfset var entry = data.entry>
		<cfset var log = wirebox.getInstance( "logbox:logger:EntryAudit" )>
		<cfset log.info( "Entry saved: #entry.getTitle()# by #entry.getAuthor().getUsername()#" )>

		<!--- Could also trigger external API, send notifications, etc. --->
	</cffunction>

</cfcomponent>

4. Author Extension

Extend author management screens:

PointLocation
cbadmin_UserPreferencePanelUser preferences panel
cbadmin_onAuthorEditorNavAuthor editor navigation
cbadmin_onAuthorEditorContentAuthor editor content area
cbadmin_onAuthorEditorSidebarAuthor editor sidebar
cbadmin_onAuthorEditorActionsAuthor editor action buttons
cbadmin_onNewAuthorFormNew author form
cbadmin_onNewAuthorActionsNew author form actions
cbadmin_preNewAuthorSaveBefore new author saved
cbadmin_postNewAuthorSaveAfter new author saved
cbadmin_onAuthorPasswordChangeWhen author password changes
cbadmin_preAuthorPreferencesSaveBefore preferences saved
cbadmin_postAuthorPreferencesSaveAfter preferences saved

Example: Add Custom Author Field

<!--- interceptors/AuthorExtension.cfc --->
<cfcomponent>

	<cffunction name="configure">
	</cffunction>

	<cffunction name="onCbadmin_onAuthorEditorSidebar" 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">

		<cfset var content = "">
		<cfset saveContent variable="content">
			<div class="form-group">
				<label>Department</label>
				<input type="text" name="department"
					value="#prc.author.getDepartment() ?: ''#"
					class="form-control">
			</div>
		</cfset>
		<cfset buffer.append( content )>
	</cffunction>

	<cffunction name="onCbadmin_postAuthorSave" 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">

		<!--- Save custom field --->
		<cfif structKeyExists( rc, "department" )>
			<cfset data.author.setDepartment( rc.department )>
			<cfset authorService = wirebox.getInstance( "authorService@contentbox" )>
			<cfset authorService.save( data.author )>
		</cfif>
	</cffunction>

</cfcomponent>

5. Dashboard Extension

Add content to the admin dashboard:

PointLocation
cbadmin_onDashboardDashboard main area
cbadmin_preDashboardContentBefore dashboard content
cbadmin_postDashboardContentAfter dashboard content
cbadmin_preDashboardSideBarBefore dashboard sidebar
cbadmin_postDashboardSideBarAfter dashboard sidebar
cbadmin_onDashboardTabNavDashboard tab navigation
cbadmin_preDashboardTabContentBefore tab content
cbadmin_postDashboardTabContentAfter tab content

6. Admin Menu Registration

Register custom menu items via cbadmin_onAdminMenuLoad:

<!--- interceptors/AdminMenu.cfc --->
<cfcomponent>

	<cffunction name="configure">
	</cffunction>

	<cffunction name="onCbadmin_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">

		<!--- data.menuItems is the array of menu items --->
		<cfset arrayAppend( data.menuItems, {
			name       : "My Module",
			link       : event.buildLink( "cbadmin/myModule" ),
			icon       : "star",
			permission : "MYMODULE_ACCESS",
			order      : 50
		} )>
	</cffunction>

</cfcomponent>

Registering Admin Interceptors

Register your interceptors in your module's ModuleConfig.cfc:

<cfcomponent>

	<cffunction name="configure">
		<cfset interceptors = [
			{
				class : "mymodule.interceptors.AdminAssets",
				name  : "AdminAssets@mymodule"
			},
			{
				class : "mymodule.interceptors.EditorSidebar",
				name  : "EditorSidebar@mymodule"
			},
			{
				class : "mymodule.interceptors.EntryAudit",
				name  : "EntryAudit@mymodule"
			}
		]>
	</cffunction>

</cfcomponent>

Creating Custom Admin Handlers

Extend baseHandler for admin handlers:

<!--- 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>

	<cffunction name="settings" access="public" returntype="void">
		<cfset prc.pageTitle = "My Module Settings">
		<cfset prc.settings = settingService.getSettings()>
		<cfset setView( "myModule/settings" )>
	</cffunction>

</cfcomponent>

Admin Routes

Define routes in your module's ModuleConfig.cfc:

<cfset routes = [
	{ pattern = "/cbadmin/myModule", handler = "myModule", action = "index" },
	{ pattern = "/cbadmin/myModule/settings", handler = "myModule", action = "settings" },
	{ pattern = "/cbadmin/myModule/:action", handler = "myModule" }
]>

Admin Security

Admin handlers inherit security from the admin firewall. Configure permissions:

<!--- In your module's ModuleConfig.cfc --->
<cfset settings.cbsecurity = {
	firewall : {
		rules : {
			provider : {
				source     : "model",
				properties : {
					model  : "securityRuleService@contentbox",
					method : "getSecurityRules"
				}
			}
		}
	}
}>

Best Practices

  1. Use interception points โ€” don't modify core admin files
  2. Register interceptors in your module's ModuleConfig.cfc
  3. Extend baseHandler for admin handlers
  4. Use prc scope for passing data to admin views
  5. Check permissions before rendering admin content
  6. Use buffer.append() for HTML injection interceptors
  7. Follow admin UI patterns โ€” match existing styling and structure
  8. Test with all engines โ€” Lucee, Adobe CF, BoxLang
  9. Use provider: injection to avoid circular dependencies
  10. Document custom interception points in your module

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.