Custom Templates
Custom Templates lets a board reshape how the stock forum surfaces render: the post author rail, topic rows, forum rows, profile cards, every post on the topic view, and in Advanced mode entire pages like the board index, the topic list and the topic view. Templates are written in the same {{ }} dialect widgets already use, edited in the Admin Control Panel, and they are free on every v2 plan. Customizations are stored separately from the stock designs, so a platform update never overwrites them.
Start here: your first customization
You do not need a coding background. A template is just the HTML of one part of your board with placeholders where the real information goes, and the safest way to learn it is to change one small thing.
- Open Admin > Appearance > Custom Templates on your board.
- Pick a surface. The Post rail is a good first one: it is the member box that sits beside every post.
- The editor opens with the current design already filled in. Find the word
Postsand change it toMessages. - Press Save, then open any topic on your board. The rail now says Messages.
- Changed your mind? Press Revert to stock and the built-in design takes over again. Your saved version stays restorable, so nothing is ever lost.
That is the whole loop: open, tweak, save, look. Everything else on this page is the same idea with more options.
The words on this page
| Word | What it means |
|---|---|
| Surface | A part of your board you can reshape: the post rail, a topic row, a whole page. |
| Template | The blueprint for a surface. HTML plus placeholders. |
| Variable | A placeholder like @{{ user.username }}. When the page renders, it becomes the real thing: the member's name, a post count, an avatar. |
| Partial | A smaller template reused inside pages, like the topic row inside the topic list. |
| Stock | The built-in design. It is what renders until you customize, and what everything falls back to. |
| Outdated | A platform update improved the stock design of something you customized. Yours keeps rendering untouched; the ACP just offers you a look at what changed. |
The template language
Templates are HTML plus variables and a small set of blocks. Variables use dot paths and unknown variables render as an empty string, never an error.
<div class="post-rail">
<img src="{{ user.avatar_url }}" alt="{{ user.username }}">
<b>{{ user.username }}</b>
{{#if user.trust_level}}
<span class="level">{{ user.trust_level }}</span>
{{else}}
<span class="level">New member</span>
{{/if}}
{{#each user.achievements}}
<img src="{{ this.icon }}" title="{{ this.name }}">
{{/each}}
Posts: {{ user.num_posts | abbreviate }}
</div>
| Syntax | What it does |
|---|---|
{{ user.username }} | Prints a variable, HTML-escaped. Dot paths walk nested data. |
{{{ reply.content }}} | Prints raw HTML. Use only for variables documented as HTML, like formatted post content. |
{{#if var}} ... {{else}} ... {{/if}} | Conditional. Empty strings, empty lists, zero and false are all falsy. |
{{#unless var}} ... {{/unless}} | Inverted conditional, with the same {{else}} support. |
{{#each items}} ... {{/each}} | Loops a list. Inside the block, this is the current item ({{ this.name }}) and {{ @index }} is the position. |
{{ var | filter }} | Filters transform output: number (1,234), abbreviate (1.2K), limit:40 (truncate), upper, lower, default:'fallback'. |
{{> topic_row }} | Includes another surface's template, customized or stock, whichever the board has. Inside {{#each}} it renders the current item, so a page template can loop its rows through the row template. |
Hard limits keep rendering safe: templates max 64KB, output max 256KB, blocks nest at most 16 levels and each loop renders at most 100 items. A template that exceeds a limit or fails to parse falls back to the stock design.
Surfaces
Generated from the platform registry| Surface | What it replaces | Advanced |
|---|---|---|
post_rail |
The member column beside every post. The character-sheet classic. | No |
topic_row |
Every row on the forum index and topic lists. | No |
profile |
The identity card at the top of member profiles. | No |
forum_row |
Each forum entry on the board index. | Yes |
reply |
Every post on the topic view, replacing the card or rail design. | Yes |
category |
Category sections wrapping groups of forums. | Yes |
board_index |
The whole category and forum listing on the home page. | Yes |
topic_list |
The full list of topics inside a forum, pinned included. | Yes |
topic_view |
The whole reply stream on the topic page, composed from the reply template. | Yes |
Normal surfaces are always editable. Advanced surfaces, including the whole-page templates for the board index and the topic list, sit behind an explicit Advanced mode toggle: deeper customizations are more likely to need a review after a platform update, so they carry a warning and rely on the same Outdated review flow, automatic fallback and restorable versions.
Pages compose from partials through includes. The stock board index template loops categories through {{> category }}, which loops its forums through {{> forum_row }}; the stock topic list includes {{> topic_row }} per topic; the stock topic view includes {{> reply }} per post. Customize just a partial and every page picks it up, including customized pages that kept the include. A page template that drops the include and writes its own row markup takes full control of that page; the partial keeps working everywhere else.
Variables per surface
Generated from the platform registryEach surface exposes a documented set of variables. Names starting with this. are fields of the current {{#each}} item on that surface, for example each topic inside topics on the topic list.
| Surface | Variables |
|---|---|
| Post rail |
user.username
user.avatar_url
user.group_name
user.group_image_url
user.custom_title
user.rank
user.num_posts
user.num_topics
user.reputation
user.joined
user.joined_long
user.location
user.is_online
user.last_active
user.profile_url
user.trust_level
user.credits
user.is_staff
user.custom_fields
user.achievements
|
| Topic row |
topic.title
topic.url
topic.unread_url
topic.is_read
topic.is_pinned
topic.is_locked
topic.is_hot
topic.is_solved
topic.is_open
topic.replies_count
topic.views_count
topic.views_short
topic.created_at
topic.created_at_full
topic.label
topic.labels
topic.participants
topic.author.username
topic.author.avatar_url
topic.author.profile_url
topic.last_post.author
topic.last_post.avatar_url
topic.last_post.date
topic.last_post.url
topic.forum.name
topic.forum.url
topic.checkbox
|
| Forum row |
forum.name
forum.description
forum.url
forum.topics_count
forum.replies_count
forum.viewers_count
forum.is_read
forum.marker_icon
forum.marker_image_url
forum.last_post.author
forum.last_post.avatar_url
forum.last_post.date
forum.subforums
|
| Reply |
reply.number
reply.content
reply.body
reply.actions
reply.meta
reply.reactions
reply.permalink
reply.created_at
reply.created_at_full
reply.is_op
reply.is_best
reply.attachments
user.username
user.avatar_url
user.group_name
user.num_posts
user.trust_level
user.credits
|
| Category |
category.name
category.description
category.forums
|
| Board index |
board.name
board.description
board.url
stats.topics
stats.replies
stats.members
stats.online
stats.latest_member
stats.latest_member_url
categories
this.name
this.url
this.forums
this.topics_count
this.replies_count
this.last_post.author
this.last_post.date
this.widgets_after
widgets.above_board
widgets.sidebar
widgets.below_board
widgets.footer
|
| Topic list |
forum.name
forum.description
forum.url
forum.topics_count
pinned
topics
pagination.current_page
pagination.last_page
pagination.next_url
pagination.prev_url
this.title
this.url
this.is_read
this.is_solved
this.labels
this.participants
this.replies_count
this.views_count
this.views_short
this.created_at
this.author.username
this.author.avatar_url
this.last_post.author
this.last_post.avatar_url
this.last_post.date
this.last_post.url
this.checkbox
widgets.above_board
widgets.sidebar
widgets.below_board
widgets.footer
|
| Topic view |
topic.title
topic.url
topic.replies_count
topic.views_count
topic.is_locked
topic.is_solved
replies
pagination.current_page
pagination.last_page
pagination.next_url
pagination.prev_url
this.reply.number
this.reply.content
this.reply.body
this.reply.actions
this.reply.meta
this.reply.reactions
this.reply.permalink
this.reply.created_at
this.reply.is_op
this.reply.is_best
this.user.username
this.user.avatar_url
this.user.group_name
this.user.num_posts
widgets.above_board
widgets.sidebar
widgets.below_board
widgets.footer
|
Outlet snippets
Besides replacing a surface, boards can inject small snippets at named outlets without touching the stock design, for example a trust badge under every avatar. Snippets are managed on the same ACP page, can be reordered and toggled individually, and share the variables of their surface.
Generated from the platform registry| Outlet | Position |
|---|---|
post-rail:below-avatar |
Post Rail, Below Avatar |
post-rail:after-group |
Post Rail, After Group |
post-rail:after-meta |
Post Rail, After Meta |
topic-row:after-title |
Topic Row, After Title |
topic-row:after-meta |
Topic Row, After Meta |
topic-row:after-stats |
Topic Row, After Stats |
profile-card:after-name |
Profile Card, After Name |
profile-card:after-stats |
Profile Card, After Stats |
profile-card:footer |
Profile Card, Footer |
Copy-paste recipes
Each recipe is one small change. Open the surface, find the line from the starter design, and swap or add as shown. Save, refresh your board, done.
Show where a member is from
Surface: Post rail. Find this pair in the starter:
<dt>Reputation</dt>
<dd>{{ user.reputation }}</dd>
Add this right below it. The {{#if}} wrapper keeps the row hidden for members who left their location empty:
{{#if user.location}}
<dt>From</dt>
<dd>{{ user.location }}</dd>
{{/if}}
Bigger, bolder topic titles
Surface: Topic row. Find:
<a href="{{ topic.url }}">{{ topic.title }}</a>
Replace with:
<a href="{{ topic.url }}" style="font-size:1.05rem;font-weight:700">{{ topic.title }}</a>
A level badge under every avatar
No template editing at all: this one is an outlet snippet, a small addition that leaves the design untouched. On the Custom Templates page choose Add snippet on the Post Rail outlets, pick the post-rail:below-avatar outlet and paste:
{{#if user.trust_level}}
<div style="text-align:center;font-size:.72rem;font-weight:700">{{ user.trust_level }}</div>
{{/if}}
The safety model
- Validated twice: the editor validates while you type and again on save. A template with syntax errors cannot be saved.
- Silent fallback: if a stored template ever fails at render time, members see the stock design and the admin gets a dismissible ACP notice naming the surface and the error.
- Sanitized on save:
<script>and<style>tags are stripped and the HTML is sanitized. JavaScript belongs in script-injection extensions, CSS in Theme Studio. - Escaped by default:
{{ var }}output is HTML-escaped; raw output requires the explicit triple-brace form. - Update-safe: when a platform update changes a stock design you customized, your template keeps rendering untouched. The ACP flags it as Outdated with a line-by-line view of what changed, and you merge what you want, revert to the new stock, or mark it up to date as is.
- Reversible: every save snapshots a version that can be restored, each surface has its own on/off toggle, and Revert to stock never deletes saved versions.
html_template packages, so everything on this page carries over to the Widgets guide and back.The official cookbook
Six official add-on packs ship in every board's extension directory (ACP, Extensions, Browse) and install in one click, custom profile fields included. Every snippet inside them is reproduced here to copy, paste and remix in your own outlets.
Character Sheets
Provisions 3 profile fields: Character name, Class, Alignment.
Character sheet · outlet post-rail:after-meta
{{#if user.custom_fields}}<div style="margin-top: 0.55rem; padding-top: 0.45rem; border-top: 1px solid rgba(128,128,128,0.25); font-size: 0.74rem;">{{#each user.custom_fields}}<div style="padding: 0.1rem 0;"><small>{{ this.label }}</small><br><b>{{ this.value }}</b></div>{{/each}}</div>{{/if}}
Gamer Tags
Provisions 4 profile fields: Steam, Discord, PSN, Xbox.
Gamer tags · outlet post-rail:after-meta
{{#if user.custom_fields}}<div style="margin-top: 0.55rem; padding-top: 0.45rem; border-top: 1px solid rgba(128,128,128,0.25); font-size: 0.72rem;">{{#each user.custom_fields}}<div style="padding: 0.08rem 0;"><small>{{ this.label }}:</small> <b>{{ this.value }}</b></div>{{/each}}</div>{{/if}}
Post Rail Flair
Trust level badge · outlet post-rail:after-group
{{#if user.trust_level}}<div style="font-size: 0.7rem; margin-top: 0.2rem;">{{ user.trust_level }}</div>{{/if}}
Credits line · outlet post-rail:after-meta
{{#if user.credits}}<div style="font-size: 0.72rem; margin-top: 0.2rem;"><b>{{ user.credits }}</b> credits</div>{{/if}}
Profile Showcase
Standing line · outlet profile-card:after-stats
<div style="font-size: 0.78rem; margin-top: 0.35rem;">{{#if user.trust_level}}{{ user.trust_level }} · {{/if}}{{#if user.is_online}}Online now{{else}}Last seen {{ user.last_active }}{{/if}}</div>
Member since footer · outlet profile-card:footer
<div style="font-size: 0.74rem; margin-top: 0.4rem;">Member since {{ user.joined_long }}{{#if user.location}} · {{ user.location }}{{/if}}</div>
Thread Meta
Active line · outlet topic-row:after-stats
{{#if topic.last_post.date}}<span style="font-size: 0.72rem;">Active {{ topic.last_post.date }}</span>{{/if}}
Exact start date · outlet topic-row:after-meta
<span style="font-size: 0.7rem;">Started {{ topic.created_at_full }}</span>