Every Discord bot eventually gets the same feature request:
"Can I customize the welcome message?"
Seems simple enough. An admin wants to write something like:
Welcome {user} to {server}! You're member #{count}.
and your bot fills in the blanks.
The first version I built looked exactly like you'd expect:
const msg = template
.replaceAll("{user}", `<@${member.id}>`)
.replaceAll("{server}", guild.name)
.replaceAll("{count}", String(guild.memberCount));
Honestly, it felt done.
Then reality showed up.
A server owner wanted premium members to get a different welcome message. Another wanted to display the user's roles. Someone else wanted conditional content. Then eventually a user managed to get @everyone into a piece of user-generated content, and suddenly my bot was helping ping thousands of people.
That was the moment I realized something important:
replaceAll() is a string replacement tool. A templating system is a parser.
Those sound similar until you start adding features. Once users can write their own templates, you're no longer replacing text — you're interpreting a language. That means you need things like validation, conditionals, loops, nesting, escaping, and error handling.
After solving this problem repeatedly across Discord projects, I ended up building a small library called tagparse.
It's not trying to replace Handlebars, Mustache, or EJS. It's designed specifically for chat applications where users expect syntax like {user} and where mistakes can accidentally ping entire servers.
This post walks through building a proper welcome-message system using tagparse, including:
- Variables
- Conditionals
- Loops
- Mention-safe escaping
- Template validation
By the end you'll have a welcome-message system that's significantly safer than a chain of replaceAll() calls and only slightly more code.
Full disclosure: I wrote tagparse. This article is about the problem that motivated it, and the library happens to be the solution I ended up building.
Why Not Just Use Handlebars?
Whenever I show this library to someone, the first question is usually:
"Why not just use Handlebars?"
That's a reasonable question.
Handlebars and Mustache are mature, battle-tested tools. If you're rendering HTML, I'd probably recommend them before anything else.
The issue is that Discord bots aren't HTML applications. The things bot developers care about are different:
- User mentions
- Role mentions
- Channel references
- Timestamp formatting
- Preventing
@everyoneabuse - Dashboard-friendly syntax
Most Discord users already expect placeholders to look like:
{user}
{server}
{count}
not:
{{user}}
{{server}}
{{count}}
Matching what users already know turns out to matter more than I originally thought.
The second issue is safety. Traditional template engines understand HTML injection because that's the environment they were built for. They don't understand Discord mention injection.
To Handlebars, @everyone is harmless text. To a Discord bot, it can be a support ticket waiting to happen.
That's the gap tagparse tries to fill.
Top comments (0)