boxlang-core-dev-component-development — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited boxlang-core-dev-component-development (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
BoxLang components (also called "custom tags") are reusable markup-oriented units invoked with tag-like syntax in .bxm templates. Unlike classes, components are designed for output generation and template composition. They live in the components/ directory of a module and are registered globally when the module loads.
All built-in and custom components in BoxLang use the bx: prefix. Never use `cf`-prefixed functions or tags — those are CFML, not BoxLang.
// CORRECT BoxLang component syntax
bx:header name="Content-Type" value="application/json";
bx:location url="/dashboard" addToken=false;
bx:abort;
bx:include template="partials/nav.bxm";
// Paired component with body
bx:transaction {
// body
}
// WRONG \u2014 these are CFML, not BoxLang
cfheader( name="Content-Type", value="application/json" ) // \u274c CFML function
<cfabort> // \u274c CFML tag
<cflocation url="/dashboard"> // \u274c CFML tagSelf-closing components end with a semicolon to terminate the invocation:
// Self-closing \u2014 semicolon required in script
bx:header name="X-Custom" value="hello";
bx:setting showdebugoutput=false;
// Paired \u2014 body in braces (no trailing semicolon on the closing brace)
bx:savecontent variable="local.output" {
writeOutput( "captured content" )
}In a class or component body, bx:property declarations also end with a semicolon:
class {
bx:property name="title" type="string" default="";
bx:property name="count" type="numeric" default=0;
bx:property name="active" type="boolean" default=true;
}Component (.bx in components/) | Class (.bx anywhere) | |
|---|---|---|
| Invocation | <bx:myTag attr="val"> | new MyClass() |
| Purpose | Output/template composition | Logic, services, models |
| Body | Can have child content | N/A |
| Use in | .bxm templates | Anywhere |
// components/Alert.bx
// Invoked as: <bx:alert type="warning" message="Something happened!" />
class {
// Declare accepted attributes
property name="type" type="string" default="info"
property name="message" type="string" required="true"
property name="closable" type="boolean" default="true"
/**
* Called when the opening tag is encountered.
* Return false to suppress body execution.
*/
boolean function onStartTag( struct attributes, struct caller ) {
// Normalize attributes
if ( !listFindNoCase("info,success,warning,danger", attributes.type) ) {
attributes.type = "info"
}
return true // true = execute body (if any)
}
/**
* Called after the body content (for paired tags) or after the tag (for self-closing).
* Use this to render output.
*/
void function onEndTag( struct attributes, struct caller, string generatedContent ) {
// Render the component HTML
writeOutput( '<div class="alert alert-#attributes.type#">' )
if ( attributes.closable ) {
writeOutput( '<button type="button" class="btn-close" data-bs-dismiss="alert"></button>' )
}
writeOutput( attributes.message )
// Include any child body content
if ( len( trim(generatedContent) ) ) {
writeOutput( generatedContent )
}
writeOutput( '</div>' )
}
}// Simple self-closing
<bx:alert type="success" message="Record saved successfully!" />
// With body content
<bx:alert type="warning">
<strong>Please note:</strong> Your session will expire in 5 minutes.
</bx:alert>// components/DataTable.bx
class {
property name="query" required="true"
property name="columns" type="array" default="#[]#"
property name="cssClass" type="string" default="table"
property name="caption" type="string" default=""
boolean function onStartTag( struct attributes, struct caller ) {
return true
}
void function onEndTag( struct attributes, struct caller, string generatedContent ) {
var qry = attributes.query
var cols = attributes.columns.len() ? attributes.columns : listToArray( qry.columnList )
var cssClass = attributes.cssClass
savecontent variable="local.tableHtml" {
writeOutput( '<table class="#cssClass#">' )
if ( len(attributes.caption) ) {
writeOutput( '<caption>#encodeForHTML(attributes.caption)#</caption>' )
}
// Header row
writeOutput( '<thead><tr>' )
cols.each( (col) -> writeOutput('<th>#encodeForHTML(col)#</th>') )
writeOutput( '</tr></thead>' )
// Data rows
writeOutput( '<tbody>' )
for ( var i = 1; i <= qry.recordCount; i++ ) {
writeOutput( '<tr>' )
cols.each( (col) -> {
var cellVal = qry[col][i] ?: ""
writeOutput( '<td>#encodeForHTML(cellVal.toString())#</td>' )
})
writeOutput( '</tr>' )
}
writeOutput( '</tbody></table>' )
}
writeOutput( local.tableHtml )
}
}// Usage
<bx:dataTable
query="#userQuery#"
columns="#['name','email','status']#"
cssClass="table table-striped"
caption="Active Users"
/>// components/Cache.bx
// Similar to <bx:cache> — caches child content
class {
property name="key" required="true"
property name="timespan" required="true"
property name="cacheName" default="default"
variables.cachedContent = ""
variables.useCache = false
boolean function onStartTag( struct attributes, struct caller ) {
// Check if we have a cached version
var cached = cacheGet( attributes.key, false, attributes.cacheName )
if ( !isNull(cached) ) {
writeOutput( cached )
variables.useCache = true
return false // false = skip executing the body
}
return true // true = execute body and capture it in generatedContent
}
void function onEndTag( struct attributes, struct caller, string generatedContent ) {
if ( !variables.useCache ) {
// Cache the generated body content
cachePut(
attributes.key,
generatedContent,
attributes.timespan,
"",
attributes.cacheName
)
writeOutput( generatedContent )
}
}
}The caller argument gives access to the calling template's scope:
void function onEndTag( struct attributes, struct caller, string generatedContent ) {
// Read a variable from the calling template
var userId = caller.userId ?: ""
// Set a variable in the calling template
caller.componentResult = processData( attributes.data )
}boolean function onStartTag( struct attributes, struct caller ) {
// Required attribute check
if ( !structKeyExists(attributes, "query") || isNull(attributes.query) ) {
throw(
message = "The 'query' attribute is required for <bx:dataTable>",
type = "MyModule.MissingAttributeError"
)
}
// Type coercion
if ( structKeyExists(attributes, "maxRows") ) {
attributes.maxRows = val( attributes.maxRows )
if ( attributes.maxRows < 1 ) attributes.maxRows = 100
}
return true
}In ModuleConfig.bx:
class {
// Register the module's components/ directory
this.componentPaths = [
"#moduleRecord.physicalPath#/components"
]
// Or register a specific namespace
this.componentNamespace = "mymodule"
// Usage: <bx:mymodule:alert type="info" message="Hello!" />
}After registration, components are available globally in all templates:
// Available after module loads (no imports needed):
<bx:alert type="info" message="Hello from my module!" />
<bx:dataTable query="#qry#" />
<bx:cache key="homePage" timespan="#createTimeSpan(0,1,0,0)#">
<!-- expensive content here -->
</bx:cache>BoxLang handles both automatically:
// Self-closing — onStartTag + onEndTag called with empty generatedContent
<bx:myTag attr="val" />
// Paired — body is executed and passed as generatedContent to onEndTag
<bx:myTag attr="val">
body content here
</bx:myTag>// Capture component output into a variable instead of writing to response
savecontent variable="myOutput" {
// <bx:myTag ...> goes here in markup files
// In script: invoke the component
include template="components/Alert.bx"
attributes={ type: "info", message: "Test" }
}
writeOutput( myOutput )// tests/specs/AlertComponentTest.bx
class extends="testbox.system.BaseSpec" {
function run() {
describe( "Alert Component", function() {
it( "should render an info alert", function() {
savecontent variable="local.output" {
include template="#expandPath('./../../components/Alert.bx')#"
attributes={ type: "info", message: "Test message" }
}
expect( local.output ).toInclude( 'class="alert alert-info"' )
expect( local.output ).toInclude( "Test message" )
})
it( "should default to info type when invalid type given", function() {
savecontent variable="local.output" {
include template="#expandPath('./../../components/Alert.bx')#"
attributes={ type: "invalid", message: "Hello" }
}
expect( local.output ).toInclude( 'alert-info' )
})
})
}
}BoxLang components map to CFML custom tags (<cf_myTag>) when the bx-compat-cfml module is enabled. Existing CFML custom tags work without modification.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.