๐Ÿ”ท Core

contentbox-boxlang-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-boxlang/widget-development
$ coldbox ai skills install coldbox/skills/contentbox-boxlang/widget-development
๐Ÿ”— https://skills.boxlang.io/skills/raw/coldbox/skills/contentbox-boxlang~widget-development

ContentBox Widget Development (BoxLang)

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

Widget Architecture

Widgets are singleton components 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.bx
component extends="contentbox.models.ui.BaseWidget" singleton {

	function init(){
		setName( "Hello World" )
		setVersion( "1.0.0" )
		setDescription( "Displays a hello world message" )
		setAuthor( "Your Name" )
		setAuthorURL( "https://example.com" )
		setIcon( "smile" )
		setCategory( "General" )
	}

	/**
	 * Render the hello world widget
	 *
	 * @greeting The greeting text
	 * @name The name to greet
	 */
	any function renderIt( string greeting = "Hello", string name = "World" ){
		return "<p class='hello'>#arguments.greeting#, #arguments.name#!</p>"
	}

}

Advanced Widget with Content Queries

// modules_app/contentbox-custom/_widgets/RecentEntries.bx
component extends="contentbox.models.ui.BaseWidget" singleton {

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

	/**
	 * 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
		entries = entryService.findPublishedContent(
			max       : arguments.max,
			category  : arguments.category,
			sortOrder : arguments.sortOrder,
			siteID    : getSite().getSiteID()
		)

		// Build output
		output = ""
		if( len( arguments.title ) ){
			output &= "<h#{arguments.titleLevel}#>#arguments.title#</h#{arguments.titleLevel}#>"
		}
		output &= "<ul class='recent-entries'>"
		for( entry in entries ){
			output &= "<li>"
			output &= "<a href='#{cb.entryURL( entry )}#'>#{entry.getTitle()}#</a>"
			output &= " <small>#{dateFormat( entry.getPublishedDate(), 'mmm d, yyyy' )}#</small>"
			output &= "</li>"
		}
		output &= "</ul>"

		return output
	}

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

}

Widget with View Rendering

// modules_app/contentbox-custom/_widgets/FeaturedContent.bx
component extends="contentbox.models.ui.BaseWidget" singleton {

	function init(){
		setName( "Featured Content" )
		setVersion( "1.0" )
		setDescription( "Displays featured content with a custom view" )
		setAuthor( "Your Name" )
		setIcon( "star" )
		setCategory( "Content" )
	}

	/**
	 * Render featured content
	 *
	 * @slug The content slug to feature
	 * @slug.optionsUDF getContentSlugs
	 * @showImage Whether to show the featured image
	 */
	any function renderIt( string slug = "", boolean showImage = true ){
		if( !len( arguments.slug ) ){
			return "<p>No content slug provided.</p>"
		}

		// Fetch the content
		content = contentService.findBySlug( arguments.slug )
		if( isNull( content ) ){
			return "<p>Content not found.</p>"
		}

		// Render via a view template
		return renderView(
			view    : "widgets/featuredContent",
			args    : {
				content   : content,
				showImage : arguments.showImage
			},
			module  : "contentbox-custom"
		)
	}

	/**
	 * @cbignore
	 */
	array function getContentSlugs(){
		return contentService.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
	site = getSite()
	siteId = site.getSiteID()

	// Use siteId for content queries
	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
widget = widgetService.getWidget( "RecentEntries" )
html   = widget.renderIt( max : 5, title : "Recent" )

Widget Service API

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

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

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

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

// Get a specific widget instance
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 string concatenation โ€” 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 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