๐Ÿ”ท Core

contentbox-cfml-widget-development

Use this skill when building ContentBox widgets, including BaseWidget patterns, render logic, dependency injection, editor metadata annotations, caching choices, and safe output/rendering practices.

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

ContentBox Widget Development (CFML)

Build custom widgets for ContentBox CMS using CFML. Widgets are reusable, self-contained components that render dynamic content anywhere in pages, entries, sidebars, or layouts.

Widget Architecture

Widgets are singleton CFCs that extend contentbox.models.ui.BaseWidget. They are discovered automatically from four locations (in priority order):

  1. Active theme widgets: themes/{activeTheme}/widgets/
  2. Custom widgets: modules_app/contentbox-custom/_widgets/
  3. Core widgets: modules/contentbox/widgets/
  4. Module widgets: Any registered module's widgets/ folder

Theme widgets override core/custom widgets of the same name.

BaseWidget Properties

All widgets inherit from BaseWidget which provides:

Widget Metadata Properties

PropertyTypeDescription
namestringWidget display name
versionstringWidget version
descriptionstringWidget description
authorstringAuthor name
authorURLstringAuthor website
forgeBoxSlugstringForgeBox package slug
categorystringWidget category (e.g., "Content", "Blog", "Navigation")
iconstringIcon name (used in admin UI)

Auto-Injected Services

Every widget receives these services via WireBox DI:

PropertyDSLDescription
siteServicesiteService@contentboxMulti-site management
categoryServicecategoryService@contentboxCategory CRUD
entryServiceentryService@contentboxBlog entry CRUD
pageServicepageService@contentboxPage CRUD
contentServicecontentService@contentboxUnified content service
contentVersionServicecontentVersionService@contentboxContent versioning
authorServiceauthorService@contentboxAuthor management
commentServicecommentService@contentboxComment management
contentStoreServicecontentStoreService@contentboxContentStore (key-value)
menuServicemenuService@contentboxMenu management
cbCBHelper@contentboxCBHelper for theme/UI operations
securityServicesecurityService@contentboxAuthentication/authorization
htmlHTMLHelper@coldboxHTML helper utilities
controllercoldboxColdBox controller
loglogbox:logger:{this}Logger instance

Creating a Widget

Basic Widget

<!--- modules_app/contentbox-custom/_widgets/HelloWorld.cfc --->
<cfcomponent extends="contentbox.models.ui.BaseWidget" singleton>

	<cffunction name="init" access="public" returntype="HelloWorld" output="false">
		<cfset setName( "Hello World" )>
		<cfset setVersion( "1.0.0" )>
		<cfset setDescription( "Displays a hello world message" )>
		<cfset setAuthor( "Your Name" )>
		<cfset setAuthorURL( "https://example.com" )>
		<cfset setIcon( "smile" )>
		<cfset setCategory( "General" )>
		<cfreturn this>
	</cffunction>

	<cffunction name="renderIt" access="public" returntype="any" output="false">
		<cfargument name="greeting" type="string" default="Hello" hint="The greeting text">
		<cfargument name="name" type="string" default="World" hint="The name to greet">

		<cfreturn "<p class='hello'>#arguments.greeting#, #arguments.name#!</p>">
	</cffunction>

</cfcomponent>

Script Syntax Widget

<!--- modules_app/contentbox-custom/_widgets/RecentEntries.cfc --->
component extends="contentbox.models.ui.BaseWidget" singleton {

	RecentEntries function init(){
		setName( "Recent Entries" );
		setVersion( "1.0" );
		setDescription( "Shows the most recent blog entries" );
		setAuthor( "Your Name" );
		setIcon( "list" );
		setCategory( "Blog" );
		return this;
	}

	/**
	 * Show recent blog entries
	 *
	 * @max         The number of entries to show (default: 5)
	 * @max.options 3,5,10,15,20
	 * @title       Optional title displayed as heading
	 * @titleLevel  Heading level (h1-h6), default: h2
	 * @category    Filter by category slug
	 * @category.multiOptionsUDF getAllCategories
	 * @sortOrder   Sort order for entries
	 * @sortOrder.options Most Recent,Most Popular,Most Commented
	 */
	any function renderIt(
		numeric max       = 5,
		title             = "",
		string titleLevel = "2",
		string category   = "",
		string sortOrder  = "Most Recent"
	){
		// Determine sort order
		switch( arguments.sortOrder ){
			case "Most Popular":
				arguments.sortOrder = "hits DESC";
				break;
			case "Most Commented":
				arguments.sortOrder = "numberOfComments DESC";
				break;
			default:
				arguments.sortOrder = "publishedDate DESC";
		}

		// Fetch entries
		var entries = entryService.findPublishedContent(
			max       : arguments.max,
			category  : arguments.category,
			sortOrder : arguments.sortOrder,
			siteID    : getSite().getSiteID()
		);

		// Build output
		var output = "";
		saveContent variable="output" {
			if( len( arguments.title ) ){
				writeOutput( "<h#arguments.titleLevel#>#arguments.title#</h#arguments.titleLevel#>" );
			}
			writeOutput( "<ul class='recent-entries'>" );
			for( var i = 1; i <= entries.recordCount; i++ ){
				var entry = entries;
				entries.absoluteRow = i;
				writeOutput( "<li>" );
				writeOutput( "<a href='#cb.entryURL( entry )#'>#entry.getTitle()#</a>" );
				writeOutput( " <small>#dateFormat( entry.getPublishedDate(), 'mmm d, yyyy' )#</small>" );
				writeOutput( "</li>" );
			}
			writeOutput( "</ul>" );
		}

		return output;
	}

	/**
	 * Get all categories for the select box
	 * @cbignore
	 */
	array function getAllCategories(){
		return categoryService.getAllSlugs();
	}

}

renderIt() Method

The renderIt() method is the entry point for every widget. It:

  • Must be implemented by every concrete widget
  • Can accept any number of arguments
  • Arguments become configurable in the admin widget editor
  • Must return a string (the rendered HTML)

Argument Annotations for Admin UI

Use metadata in function comments to control how arguments appear in the admin widget editor:

AnnotationDescriptionExample
@hintHelp text for the argument@hint The menu slug
@labelCustom label in the editor@label Search Term
@optionsComma-separated select options@options red,blue,green
@optionsUDFUDF name that returns options@optionsUDF getCategories
@multiOptionsUDFUDF for multi-select options@multiOptionsUDF getAllCategories
@defaultValueDefault value in editor@defaultValue 5

@cbignore Annotation

Mark helper methods with @cbignore to exclude them from the widget editor:

/**
 * Internal helper โ€” not shown in widget editor
 * @cbignore
 */
array function getInternalList(){
	return [ "a", "b", "c" ];
}

Using getSite()

The getSite() method (inherited from BaseWidget) detects the current context:

function renderIt(){
	// In admin: returns the current working site being edited
	// In UI: returns the site for the current request
	var site = getSite();
	var siteId = site.getSiteID();

	// Use siteId for content queries
	var entries = entryService.findPublishedContent( siteID : siteId );
}

Rendering Widgets

In Content (via Admin Editor)

Widgets are inserted into entry/page content using the widget shortcode:

{widget:WidgetName arg1="value" arg2="value"}

The WidgetRenderer@contentbox interceptor processes these at render time.

In Theme Views

<!--- Render widget with arguments --->
#cb.widget( "RecentEntries", { max = 10, title = "Latest Posts" } )#

<!--- Render widget without arguments --->
#cb.widget( "SearchForm" )#

<!--- Render widget with dynamic arguments --->
#cb.widget( "Menu", { slug = "footer" } )#

In Handlers

property name="widgetService" inject="widgetService@contentbox";

// Get widget instance and render
var widget = widgetService.getWidget( "RecentEntries" );
var html   = widget.renderIt( max = 5, title = "Recent" );

Widget Service API

property name="widgetService" inject="widgetService@contentbox";

// Get all widgets as a query
var widgets = widgetService.getWidgets();

// Get widget names as an array
var names = widgetService.getWidgetsList();

// Get distinct widget categories
var categories = widgetService.getWidgetCategories();

// Get a specific widget instance
var widget = widgetService.getWidget( "Menu" );

// Force reload widget registry
widgetService.getWidgets( reload = true );

Widget Locations

LocationPathOverride Priority
Active themethemes/{theme}/widgets/Highest (overrides all)
Custommodules_app/contentbox-custom/_widgets/2nd
Coremodules/contentbox/widgets/3rd
Module{module}/widgets/Lowest

Core Widgets Reference

ContentBox ships with these built-in widgets:

WidgetCategoryDescription
ArchivesBlogMonthly archive links
CategoriesBlogCategory listing
CommentFormBlogComment submission form
ContentStoreContentKey-value content blocks
EntryIncludeContentInclude entry content by slug
MenuNavigationRender ContentBox menus
MetaSEOMeta tag generation
PageIncludeContentInclude page content by slug
RSSBlogRSS feed links
RecentCommentsBlogRecent comments listing
RecentEntriesBlogRecent blog entries
RecentPagesPagesRecent pages listing
RelatedContentContentRelated entries/pages
RelocateUtilityPage redirect widget
RenderviewUtilityRender a ColdBox view
SearchFormSearchSearch input form
SubPageMenuNavigationSub-page navigation menu
ViewletUtilityExecute a ColdBox event
cbUtilityGeneric CBHelper access

Best Practices

  1. Always extend BaseWidget โ€” provides DI and utility methods
  2. Use singleton scope โ€” widgets are instantiated once and cached
  3. Implement renderIt() โ€” the only required method
  4. Use getSite() โ€” for multi-site aware content queries
  5. Annotate arguments โ€” for rich admin editor experience
  6. Use @cbignore โ€” for internal helper methods
  7. Return strings โ€” renderIt() must return rendered HTML
  8. Use saveContent โ€” for building complex output
  9. Leverage injected services โ€” no need to manually wire dependencies
  10. Categorize widgets โ€” set category for admin organization
  11. Provide icons โ€” use icon names for admin UI display
  12. Test in both contexts โ€” admin editor and public UI rendering

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.

Key CFML considerations:

  • Use <cfcomponent> tag syntax or script syntax with component keyword
  • Use <cffunction> for tag-based or function for script-based
  • Use saveContent for building output strings
  • Use structKeyExists() for safe struct access
  • Use isDefined() or isNull() for null checks