A custom tag is a reusable block of CFML that behaves like a built-in tag — it runs in its own isolated scope (unlike <cfinclude>, which shares the calling page's scope entirely), and it can accept parameters and hand data back, closer to how a function works.
Learning Objectives
After completing this lesson, you'll be able to:
- Create a custom tag and call it with the cf_ prefix.
- Pass data into a custom tag through the Attributes scope, and back out through the Caller scope.
Creating One
A custom tag is just a regular CFML file. Save it, and it becomes callable as a tag by prefixing its filename with cf_.
<cfoutput>#dateFormat(now(), "mmmm d, yyyy")#</cfoutput>
Custom tag filenames must be lowercase on non-Windows systems, even though CFML tag names themselves aren't case-sensitive.
Where the File Has to Live
- The same directory as the page calling it
- The cfusion/CustomTags directory (or a subdirectory of it)
- Any additional directory configured in the ColdFusion Administrator
Passing Data In: the Attributes Scope
<cfoutput>Hello, #attributes.name#!</cfoutput>
Everything passed as a tag attribute lands in the custom tag's own Attributes scope, exactly like Arguments works for a function call.
Passing Data Back: the Caller Scope
To hand a result back to whichever page called the tag, write it into the Caller scope — the calling page's own Variables scope, from the custom tag's perspective.
<cfscript> caller.greetingMessage = "Hello, " & attributes.name & "!"; </cfscript>
Use a distinctive name when writing to Caller, the same reasoning as scoping variables carefully anywhere else — an overly generic name risks silently overwriting something the calling page already had.
cfinclude vs Custom Tags
| cfinclude | Custom Tag | |
|---|---|---|
| Scope | Shares the calling page's scope entirely | Runs in its own isolated scope |
| Passing data in | Not supported — set a variable beforehand | Attributes scope, like real parameters |
| Passing data back | Automatic (shared scope) | Explicit, via the Caller scope |
| Best for | Static or mostly-static shared content | Reusable logic that needs its own inputs/outputs |
Common Beginner Mistakes
Forgetting the cf_ prefix when calling a custom tag
Without it, ColdFusion has no way to know the tag isn't one of its own built-in tags — the cfimport lesson covers an alternative that avoids the prefix requirement entirely.
Reading a value from Attributes that wasn't actually passed
Check structKeyExists(attributes, "name") first if an attribute is optional, the same as checking any other scope before reading from it.
Assuming a custom tag can read the calling page's variables directly
It can't — that's the whole point of the isolated scope. Anything the tag needs has to come in through Attributes explicitly.
Best Practices
- Reach for a custom tag (or a CFC) instead of cfinclude whenever the reusable block genuinely needs its own inputs or should be protected from the calling page's variables.
- Use a distinctive Caller variable name to avoid silently overwriting something in the calling page.
Interview Questions
What's the difference between how cfinclude and a custom tag handle scope?
cfinclude shares the calling page's Variables scope entirely. A custom tag runs in its own isolated scope, receiving data only through Attributes and returning data only through Caller.
How do you pass data into a custom tag?
As tag attributes, which the tag reads from its own Attributes scope — the same pattern as a function's Arguments scope.
Summary
In this lesson, you created and called a custom tag, and passed data in through Attributes and back out through Caller.
What's Next?
The next lesson covers cfimport — calling a whole directory of custom tags under a prefix of your own choosing, instead of the cf_ convention.