Theme-Component v Plugin: What's the difference

Can someone clarify the difference between these three Discourse concepts: theme-component, plugin, and pluginAPI.

Especially between theme-component and plugin. If I want to customize my forum, how do I know which to build?

(sorry if I missed this distinction in the intro, but I’m not seeing it there)


I am not a beginner with Discourse but also am no expert.

  1. Theme-component uses HTM,CSS,JavaScript to enhance a base theme.
    I note base theme because it is typically called theme and sometimes people don’t note the difference and you have to infer it. A theme and/or theme component can be installed by an admin without taking the site down and if you are a Discourse customer, you can also add these. (list) Also see: Beginner’s guide to using Discourse Themes

  2. A plugin uses Ruby and can do just about anything possible. If you are a customer of Discourse you have limited sets of plugins that are allowed to be activated, however if you are self-hosting then you can add all you want, but be warned that I see lots of post where custom plugins break the site during an upgrade. These also do not require a restart when activated; I suspect a restart might be required when first installing. Others can elaborate as the only experience I have with plugins is to activate them from the admin menus. (list) Also see: Beginner’s Guide to Creating Discourse Plugins - Part 1

  3. I have not developed a Plugin so my guess is that you are refering to Discourse API Ruby Gem. See: Using the Discourse API Ruby Gem

  4. There is also the API which are webhooks and typically used with curl or another programming language. This is nice because it frees you from using Ruby.

  5. While I have not dabbled in this either, you could program at the PostgreSQL database level but I would not recommend it unless you are very skilled and very confident in your abilities.



Bonus round if you want to go all-in as a Discourse developer

See: How to start building stuff for Discourse if you’re newbie (like myself)


To add onto @EricGT’s answer which does a good job of explaining already –

  • A theme/theme component is essentially a way to modify any part of the Discourse front end EmberJS app. This can be as simple as customizing HTML or CSS, or as complex as adding new functionality. Themes are much more graceful if something breaks, meaning your entire site won’t necessarily go down if something doesn’t work.
  • A plugin primarily affects the Rails server-side app, but also includes all the power of a theme and affecting the EmberJS app, though much more complex. Plugin failures tend to not be so graceful so if you can build something in a theme, start there. However a plugin is required if you need a custom route or to store data.
  • The pluginAPI is an API on the client side that themes/theme components can use to more easily modify specific parts of the Discourse client.

The best place to start in customizing your site is with a theme. Here are some resources:

Designer’s Guide to Discourse Themes
Developer’s guide to Discourse Themes
Beginners’ guide to using Theme Creator and Theme CLI to start building a Discourse theme


Thanks, guys. This is helpful. I think the key distinction I am picking up:
–if you want to change something that is only about what’s on the front end, build a theme.
–if you want to change something that requires interaction with the backend, build a plugin.

Does that sound right?

Here’s a concrete example I have in mind that I’m trying to work out: I want to make it so that any category moderator can pin topics in that category. The broad strokes, I think, are:

  1. Evaluate whether the user is a moderator of the category (this requires going to the backend for information about the user and category)

  2. If user is moderator, show the pin button (this is front end)

  3. If user clicks the pin button, push that topic to the top (actually not sure where this happens in discourse code–maybe also front end and backend?)

Here, because I need to converse with the backend (probably in (1), maybe also in (3)?), i’d have to use a plugin. Does that sound right?


The superficial part might be, but it will involve the backend because it relates to rights and security. You can’t give the front end discretion on what privileges someone has.

1 Like

Do you mean that programmatically answering “Is this user a moderator of this category” is something that will involve multiple files across front and back end? Hmm…

1 Like

In a theme, you should be able to know if a user is moderator and also make backend calls if the discourse API exposes an endpoint that you can call from the front-end (after all the theme can use javascript), so you probably don’t need a plugin for [1]. You would need only if you need to change the backend behaviour or expose an API.

But you probably need for [3], because, like @merefield said, it relates to rights and security (if the backend blocks a moderator to pin a topic, you would have to change it to start allowing).

Like I said above, it probably won’t require a plugin just to make that validation (to know if the user is a moderator or not), but it will probably require because of the action of allowing him to pin the category. If the discourse has an option to allow any moderator to pin a topic (I don’t know if it has), then you wouldn’t need a plugin (but you probably wouldn’t need a theme too, unless the pin button is not shown to moderators, and you use a theme just to show it to moderators and call the endpoint in javascript when he clicks the pin button).


That’s very helpful regarding themes v plugins and also regarding the specific example I mentioned. Thanks.

So far my approach to entering changes has been to sort through the specifics of the Discourse code (where is this object defined, what controls this template, where is the logic that handles this action, etc…). But that has been slow going.

I’m thinking that probably a more efficient route would be to focus on the API. That way, I don’t need to sort through all the details of the mature Discourse code, and can also focus on building a theme instead of a plugin–or perhaps just entering changes in the “customize” dashboard.

Basically, figuring out how the API works seems a lot more manageable than figuring out the Discourse code base.

To stick with the example I mentioned: If user is a moderator of a category, allow that user to pin topics on the category page.

Could I do this without a plugin? Let me see if I can sketch it out:

1. Is a user a moderator of a category?

I currently don’t see an anything in the api that would tell me if a user is a moderator of a category. I’d expect it to be found in a GET call for retrieving a category, but I don’t see such a call there. Or maybe in the GET call for retrieving a user, but there I don’t see a listing of categories the user is a moderator of.

Could I add these in?

Or, alternatively, maybe I could create a custom_field on the user or the category to identify being a moderator, and then make an API call to that custom field when a category page loads.

2. If user is a moderator, show the pin button.

If I can answer (1), then I assume I can just add this button with front end javascript+css, showing it if a user is a moderator.

3. User (who is moderator) clicks button, and that pins the topic

On the API, topics do seem to have a “pinned” trait (a boolean). I assume this correlates to whether they are pinned in their category, as it looks like each topic only has one category.

So here, when the moderator hits the “pin” button, I could probably update the “pinned” status of the topic to True. If that doesn’t work, custom fields could also be a solution here too (although I am not seeing how to add custom fields to a topic).

So with that, or something like it, it seems like I could accomplish this task with the API that, were I to do it with a plugin, would require a lot of sorting through the files of the Discourse code base.

Does that sound right?

1 Like

Have you found: How to reverse engineer the Discourse API

1 Like

I have seen that previously, but I’m taking a closer look now. Thanks for the reminder.

Im trying to sort through:
–where in the API I would get the info about who the moderator of a given category is (I’m not seeing it in the info returned about categories or users–obviously it has to be somewhere)

–can you use the API to add new fields to an entry? For example, Could I use the API to add a custom field to a topic? (I don’t think topics generally come with custom fields)

1 Like

I would look in the database.

The database has a posts table and you could add a field there, but then you will be off the ranch and get no support.

1 Like

Thanks–this is all very helpful.

What database do you mean?

To confirm, my idea here would be to use the API to do some of this stuff that might otherwise require a plugin. So, for example, my site itself would be making a call to the API to determine if a user (or group, as case may be) is a moderator of a category. The call would be hardcoded into the customize panel, or in a theme or plugin.

To make these kinds of calls, I’ll need to be authenticated, with I believe requires creating an API key from the admin panel. The admin panel API creation process requires a “Description” and a “User Level”–I’m not sure how that applies here. In my case, I want my app to make the API call. It’s not being made for a particular user. So I may be misunderstanding.

Do you know what User Level is appropriate for that kind of API call or what I should enter there?

1 Like

When Discourse is installed per the standard it is in a Docker container. Persistence of data is achieved using a PostgreSQL database.

See: Data Explorer Plugin

If you have admin rights you can grab one of the backups, it is SQL compressed into a tar.gz file. You can use the SQL to reload the data into another PostgreSQL database and do even more.


I have never created a theme, plugin, or used the API from Ruby or used the API with any other programming language. I do have admin access on a production site which is how I accessed the backup. I also program in Prolog which is how I am accessing the data and using it to parse the posts with DCGs. If you know BNF, DCGs are not much removed but you have to understand syntactic unification and backward chaining for the more sophisticated parts.

1 Like

Thanks. I’ll pursue this item separately.

1 Like

No, as access rights for each action are enforced by the server.

You’ll want to look at overriding the functions in lib/guardian/ and lib/guardian.rb to allow category-specific moderators to pin topics, then use the same mechanisms as Theme JS (except in the plugin) to change the UI so that the “pin topic” option appears when appropriate.

1 Like

Ahh–that makes sense. So sounds like using the API for that one would not work (your reply potentially saves me a lot of time).

I might try this in a way that’s slightly different than the normal pinning behavior. Instead, I’d give certain users “ownership” rights over a category, and that would let them highlight certain topics in a category.

The way I’d do it with the JSON API is–from my customize dashboard–give the relevant users a custom field (like category-name: owner) or something like that. And then, when the category page loads, call the API to evaluate the user; if they have that custom field, show the “highlight” button, and then if they hit the “highlight” button for a topic assign that topic into the category’s highlight group (also a custom field for that category).

This is a rough sketch–no need to go piece by piece to confirm these steps (when I code it I may have to adjust it anyway). But my question for now: can this way of using the JSON API–especially to create and retrieve custom fields from within my discourse app–work?

1 Like

Dear @JQ331,

But my question for now: can this way of using the JSON API–especially to create and retrieve custom fields from within my discourse app–work?

There has been some excellent replies to your questions about the different between a theme component, a plugin, and the discourse API and I doubt I can add much more value; but here is another way of looking at this, which you may or may not find helpful:

Both theme-components and plugins use template hooks (plugin hooks) to evaluate code in the Ember.js lifecycle (which is good to learn about, BTW).

In addition, the DIscourse API is also available to both theme-components and plugins.

The API basically exposes a subset, but not all, of the data in the underlying PostgreSQL DB.

When you are developing functionality, it would be a good idea to start with the API and determine if the data you need is available from the API.

If there is some data which you need which is not in the API, you need to examine the PostgreSQL DB to see if the data exists in the DB.

If the additional data you need exists in the DB, then you need to expose that data, and in general this means adding data to the Discourse data serializer and extending the API.

The data serializer is simply the process of creating the JSON object which is exposed by the API. This can be extended to add more objects.

To examine the PostgreSQL DB, I assume there are many ways to do this (reading the code on GitHub for example), but I do this by logging into the DB directly and looking at the tables in the DB and study what are the structure of these tables and what fields are in each table (using basic SQL).

So, to summarize (and keep this short), we need to have both the API and the DB tables as references, and in general, when you want to “extend the API” by adding data which is not provided by the API OOTB, we create a plugin for that; and then that data will be exposed along with the API (extended) and we can then use that data in both theme-components and plugins.

Hope this perspective was useful or helpful in some small way.


Outstanding explanation. Thanks very much for this response. It highlights something I had not realized before:

From these responses I am gathering that (like you say) interacting with the JSON API can be a good start in many cases, that could avoid the need for coding up a new theme or plugin. But there are some types of data that are not exposed by the API. To access and do stuff with those data types, you would need to use the Discourse data serializer to expose that data; and to do that serialization, you need to use a plugin.

Seems like one good example of data that is not available through the API are the group owners of a group. I say this because (regarding accessing group owners):

One point of confusion–in the Discourse API, when you get a specific group, one of the returned traits is listed as "is_group_owner": true, so not sure what that is supposed to mean…

But seems like to get the group owner I’d need to serialize the group owner trait.

Are there good examples of using the Discourse serializer? I’ve seen this, but given its importance a how-to with a few examples would be extremely helpful.

The closest example I’ve got is:

This is helpful, but not quite right (at least it gives me errors saying “invalid plugin”). I’m not sure how to adjust it so that on the group index page I can access the group owners for each group.


I’m not sure how you tried using the plugin example, but I had it running successfully on a development instance when using the file structure and code I posted in your other topic.

is_group_owner is used in the context of the current user viewing the groups.

You can get the owner information through an ajax call, but as far as I know it would need to be done for each individual group in the list, potentially resulting in a lot of requests. I think overall it would be challenging to get it to work using this method. In any case, if you want to experiment, you can try the following proof of concept code snippet in a theme. Just replace GROUP_NAME with the name of one of your groups. (EDIT: Here’s also a theme component with an example of how an ajax call can be utilized:

<script type="text/discourse-plugin" version="0.8.40">
  const { ajax } = require("discourse/lib/ajax");
  ajax(`/groups/GROUP_NAME/members.json`).then(response => {
    console.log( => owner.username))

With all that said, using a plugin would definitely be the easiest, cleanest solution.

1 Like