Ideally, when customizing Discourse via themes/plugins, you should use CSS, the JavaScript Plugin API, or plugin outlets. If none of these work for your use-case, feel free to open a PR to Discourse core or start a dev topic here on Meta. We’re always happy to discuss adding new outlets/APIs to make customization easier.
If you’ve exhausted all other options, you may need to resort to template overrides. This technique allows you to override the entire template of any Ember Component or Route from your theme/plugin.
This is not a recommended way of customizing Discourse. Day-to-day changes in Discourse core will conflict with your template override eventually, potentially causing catastrophic errors when rendering the forum.
If you decide to take this approach, make sure you have sufficient automated testing and QA processes to detect regressions. If you distribute a theme/plugin with template overrides, please ensure forum admins are aware of the stability risks your theme/plugin carries.
![]()
![]()
October 2023 Update: For new features, Discourse is increasingly moving towards using components authored using Ember’s
.gjs
file format. Templates for these components are defined inline, and cannot be overridden by themes/plugins.Going forward, all template customizations should be done using Plugin Outlets
I understand this will break in the near future, show me the docs anyway
Overriding Component Templates
To override an Ember Component template (i.e. anything under components/*
in Discourse core), you should create an identically-named .hbs
in your theme/plugin. For example, to override the template for the badge-button
component in Discourse core, you would create a template file in your theme/plugin at this location:
{theme}/javascripts/discourse/templates/components/badge-button.hbs
{plugin}/assets/javascripts/discourse/templates/components/badge-button.hbs
The override must always be nested inside the /templates
directory, even if the core component has a ‘colocated’ template.
Overriding Route Templates
Overriding route templates (i.e. all the non-component templates under templates/*
) works in the same way as components. Create an identically named template in your theme/plugin. For example, to override discovery.hbs
in core, you would create a file like
{theme}/javascripts/discourse/templates/discovery.hbs
{plugin}/assets/javascripts/discourse/templates/discovery.hbs
Overriding ‘Raw’ Templates (.hbr
)
Discourse’s “raw” template system will soon be replaced by regular Ember components. But in the meantime, overriding raw templates works in the same way as Ember templates. For example, to override topic-list-item.hbr
in core, you could create a file like:
{theme}/javascripts/discourse/templates/list/topic-list-item.hbr
{plugin}/assets/javascripts/discourse/templates/list/topic-list-item.hbr
Interaction between multiple themes / plugins
If multiple installed themes/plugins override the same template, the ‘winner’ is the one with the lowest-numbered ranking in this list:
- Theme overrides (highest theme ‘id’ wins)
- Plugin overrides (latest alphabetical plugin name wins)
- Core
This precedence also means that you can override plugin templates from themes. Technically you can also override theme templates from other themes, and plugin templates from other plugins, but the behaviour can be surprising because of the dependence on plugin-name and theme-id.
How does this work?
Discourse assembles and prioritises templates in the DiscourseTemplateMap class. For colocated component templates, that information is used during app initialization to replace the core template associations. For all other templates, the map is used by the resolver at runtime to fetch the correct template.
This document is version controlled - suggest changes on github.
Last edited by @JammyDodger 2024-07-26T11:34:34Z
Check document
Perform check on document: