Creating a custom Discourse theme

This topic is a linked part of a larger work: “Discourse Admin Manual


1. Introduction

2. Creating and editing a Discourse local theme

3. Creating and editing a Discourse remote theme

4. Enabling your custom Discourse theme

5. CSS formatting tricks for Discourse

1. Introduction

Discourse themes are the simplest way of customizing the look & feel of a Discourse platform. You will get exposed to them as (1) a moderator or admin wanting to change how something looks, to change a main menu entry or to do something special (like hiding an element) and (2) as a client with an Edgeryders “whitelabel solution” on an own domain, wanting to set up your very own design.

There are two types of Discourse themes: normal themes (just “themes”, here also “local themes”) and remote themes. Normal themes live as settings in the Discourse database, while remote themes live as files in a git repository and can be automatically fetched and updated from there by Discourse. In the first use case from above, you mostly work on a normal theme, in the second one exclusively with a remote theme.

In both cases, it is relevant to know that a theme is a relatively limited way to customize Discourse. In technical terms, the only thing a theme can do is overwrite and add to the CSS and HTML code of the “one and only” internal Discourse theme. This internal Discourse theme is invisible to admins, you can only see it in the actual source code.

Also note that the two pre-installed, local themes that come with every Discourse installation (“Default” and “Dark”) do just the same: adding to and overwriting CSS and HTML of the internal Discourse theme. They are nothing special, and they can even be deleted. “Default” is actually a local theme that adds nothing to the internal Discourse theme (before you edited it …), and “Dark” is one that only modifies the color scheme (using “Simple Dark”).

There is, so far, no official way to “start a theme from scratch”, or to modify the internal Discourse theme, except by editing the Discourse core source code. Plugins can do some of that, and handlebars templates can also do some of that. Fortunately, handlebars templates can be included into any Discourse theme (example, instructions).

2. Creating and editing a Discourse local theme

2.1. How to add an asset to the Discourse local theme

This documents how to add images and files for use in the Discourse template / theme.

The difficulty is that all uploaded files need to be referenced in a topic for Discourse to consider them “in use” – otherwise, Discourse might delete them after a time to save space. So this topic also serves to reference all these assets – means: Do not delete this topic, it stores the theme graphics! To protect this topic against unauthorized changes, post your assets in comments here and do not make these comments into wikis – then, only staff members can edit them.

  1. Create a new reply to this topic.

  2. In your reply, upload the image you wish to use for a logo, favicon or other website asset. (Use the upload toolbar icon in the post editor, or drag-and-drop or paste images.)

  3. Submit your reply to post it.

  4. Right click the image in your new post to get the path to the uploaded image, or click the edit icon to edit your post and retrieve the path to the image. Copy that image path.

  5. Paste this image path into the corresponding URL field for a required website asset, or a similar URL field somewhere else in the Discourse settings.

If you need to enable different file type uploads, edit authorized_extensions in the file settings.

3. Creating and editing a Discourse remote theme

3.1. What is a Discourse remote theme, what can it do?

Discourse remote themes are a better / more structured way to share the look & feel customizations that can be made in the Discourse admin interface at “Admin → Customize → Themes” and “Admin → Customize → Colors”. These settings could already be exported into a *.dcstyle.json file each, but these files were not properly editable as a file, and thus not properly manageable with a version control system for collaborative development.

Discourse remte themes provide exactly the customization options known from “Admin → Customize → Themes” and “Admin → Customize → Colors” in the Discourse admin backend, and nothing more.

For all this, see the official Discourse how-to for remote themes, and esp. this comment there.

3.2. File structure

The following files of a Discourse remote theme will be evaluated, if present [source]. Only about.json is required, the others are optional and correspond to the different form fields when editing a theme under “Admin → Customize → Themes”.





In addition to these files, all files referenced in the about.json file as theme assets are also evaluated [source].

3.3. Format of about.json

You can see an example with all the possible options in the specification code:

4. Enabling your custom Discourse theme

4.1. Making the theme user selectable

This is the simplest version. It’s a setting for that theme in the admin backend. This setting is not imported and exported in .dcstyle.json files, and also cannot be contained in remote themes, probably for security reasons.

4.2. Overriding the default theme with custom code

This technique requires changes to Discourse core code. As a result, you can set the default theme however you want, including dependent on the domain a user is currently using to view your Discourse (multisite) installation.

For that last case, the simplest alternative is to evaluate the standard “Host:” HTTP Request Header field, telling you the domain the visitor is on. This field is not normally supplied to Discourse as it usually sits behind a reverse proxy. However, you can enable it. In case you use Apache 2, add this option to your virtual host configuration section:

ProxyPreserveHost On

How to make the required code changes in Discourse: [TODO]

4.3. Overriding theme_key

This allows you to choose a default theme for all users. It is useful in cases where you cannot simply use the “Theme is enabled by default” option in the theme settings in Discourse, for example when you have a secondary domain that should show (parts of) your Discourse installation but with a different default theme.

  1. Make the theme user selectable. It will work only then, as the mechanism is imitating a user action for theme selection by setting the relevant cookie.

  2. Look up the theme key.

    1. Go to “Account preferences → Interface” of your own Discourse account, choose the theme of which you want to know the theme_key in the dropdown and disable (!) the “Make this my default theme on all my devices” checkbox. Theme choice by cookie is only done on a per-device basis, while theme choice for all devices is simply saved to a user’s account.

    2. Open the web developer tools of your browser and look up the value of the theme_key cookie. That’s the theme’s key.

  3. Set theme_key with a cookie. In your proxy configuration that forwards traffic from your domain to Discourse, configure that a cookie with the theme key is set. In case you use the Apache 2 mod_proxy mechanism for proxying, you can use a mod_rewrite rule like the following to set the cookie:

     RewriteRule "^(.*)$" "-" [CO=theme_key:10577b5d-17b2-4030-bf45-7a8654064efe\]

    Note how the % character is escaped as \%, or it will not work. The meaning of this part %2C6 of the the theme key is not yet known. This part is not contained in the theme key mentioned in a .dcstyle.json file exported by Discourse for the theme, and it does not change when renaming the theme inside Discourse. %2C is probably URL encoding for the character ,.

Remaining issues:

  • Works only for logged-in users, so far. Code changes to Discourse core are required to fix this, so this is not a nice solution after all.

  • Shows up in the user theme selection form. Means, in the form at /u/{username}/preferences/interface. This means that there is clientside JavaScript code connected to this form that both sets and evaluates this setting based on the theme_key cookie. As it shows up as a change the user has done to their form, which they did not, it’s confusing and not a nice solution …

5. CSS formatting tricks for Discourse

Discourse has very (!) limited capabilities for using HTML in the editor. It sanitizes away all class and id attributes of tags, for example. There are multiple ways to deal with these limitations, as follows:

5.1. Whitelisting tags and attributes

5.2. Substituting for CSS selectors for id and class

There are cases where you want to CSS format HTML elements not by tag name but by a property that you can influence as user while writing content. Normally you would add a class or id attribute to the element, but these are sanitized away by Discourse. However, there is a hack around this (and it does not need whitelisting elements): use an ` " element and the CSS sibling selector.

Example: You want to select the div that is the parent of a table element (and there are still no parent selectors in CSS …).

You can use this HTML code:

  <a name="table-calendar"> </a>

Together with this SCSS code:

article .contents .cooked {
  a[name*="table-"] + div {
    overflow-y: auto; /* or however you want to format the div */

Note that you may have to put the HTML code all in one line to prevent Discourse from editing <br/> tags that would break the sibling selector match. Also note, the outer <div> element is just to prevent Discourse from putting your <a> element into an auto-generated <p> element, which would also break the sibling selector match.

The instructions in this topic about “Adding a website asset to the theme” should probably be simplified, as there is (since recently?) a mechanism to store assets directly in the themes in the admin backend.

See: “Customize → Themes → e.g. Default → Uploads”. The feature is described there as “You can upload assets associated with your theme such as fonts and images”. So no need anymore to “stored” assets in comments to this topic.

But it remains to be tested if the mechanism is as assumed here.

Edit: This new mechanism was introduced in 2017-05 and is documented here.