Learn how to start building stuff for Discourse if you're newbie (like myself)

There are a few ‘how to start’ guides for working with Discourse already and a wealth of useful info on meta, but I thought it might help to give an insight to the mental processes of starting from little, if any, prior coding experience to building substantial Discourse plugins.

Discourse is written by experienced developers and has a large codebase. This can feel intimidating. This intimidation factor can be a significant barrier for novice developers. This is a kind of ‘psychological primer’ to building stuff for Discourse.

I’m not an expert at coding. I only taught myself how to code last year by playing around with Discourse. I’m partly putting this here because this is the basic approach I use to answer any questions I (or others) have when working with Discourse and it will be useful to link to.

Know the basics

Despite it’s size, the Discourse codebase is easy to navigate if you know some basics. This is a testament to the quality of the work that has gone into it.

You have to start with understanding roughly how things work. That makes building new things a lot easier.

Learn how widgets, the virtual DOM and the Plugin API work

Read these two topics: Virtual Dom; Plugin API and read the documentation in the Virtual Dom github repo.

Time: about 30 minutes to 1 hour.

Learn some Ember.js basics

Lean the basics of how templates, views, controllers, models and routes work in Ember.js. You can cover this by reading the Ember.js guide.

Time: about 30 minutes.

Basic Discourse application structure

Learn the basic structure of the Discourse application itself so you know where to look for stuff:

Time: 10 mins.

How to build a plugin

Understand the basics of how to build a plugin. Just read the 6-part guide to building plugins and you’re done.

Time: 30 mins

How Ruby on Rails works

Learn the basics of how ruby on rails works (optional). There are hundreds of RoR guides and how-tos out there. Actually the only ‘text’ on ruby I’ve read is Why’s Poignant Guide, which is very readable and quite funny.

Time: about 1 hour.

How the Discourse client and server interact

Simply put all server / client interactions happen through ajax requests. Invariably, the answer to any question like “How do I get this data?” is that you retrieve it form the server via ajax. Do a search for Discourse.ajax in the github repository and you will see lots of examples.

Time: 10 mins.

This background reading and absorption should take a couple of hours. You could do it one Saturday morning in your PJs.

Start with what you know (and can see)

Question: How do I get the information about a user on their summary page? (e.g. Profile - angus - Discourse Meta)

Answer: The best way to find what you’re looking for is to start from what you know.

We know that the information we want is available at /users/[username]/summary. So how does that page get the information? Well let’s start by having a look at the template or whatever is actually rendering the information. That is the next level down from “I am seeing the information on the screen”.

In this case, the the user summary has a template. We know from our reading that all the templates are in this folder. Look through the folder for things that look relevant. There’s a folder named ‘User’. This data is about a user, so let’s look in there. In that folder, there’s a template called ‘Summary’. That’s a word that’s used in the path where we know the info is. Cool, looks like we found it.

Hm, looks like the information that’s being rendered is coming from a model.

We know from our reading that in Ember.js the model is loaded by a route file (see this part of the guide). We know from our reading that the route file for /user/[username]/summary is in assets/javascripts/discourse/routes/. Looking through that folder, the file user-summary looks like it’s relevant.

Thankfully, it’s nice and simple. It’s using the method summary from the User model. So let’s have a look at the User model method summary:

Turns out that this is exactly what we’re after. This is a ajax request from the client to the server. We know that this is how the client gets all its info from the server.

So we already have our answer. We can do exactly the same thing in a plugin. Start by copying that ajax request and then modify it to suit your needs. This leads us to the next point.

Copy and use the Discourse code

Discourse is an open source project. It’s a very well written open source project. So if Discourse already does something, or does something similar to what you want to do, start by copying or using what Discourse does.

For example, I started writing my Quick Messages plugin by literally copying swathes of code from the composer controller and the code that, prior to the widgetized header, displayed the header menu items. You can see that perhaps the most important method in the whole plugin, the method that saves a new post, is just a modified version of the same method in Discourse.

If Discourse actually does exactly what you want to do, but just in a different context, then there’s no need to re-invent the wheel. In most cases it is possible to use the existing Discourse solution in a different context. This is easy to do with ECMAScript 6 modules. For example, the Topic Previews Plugin imports a bunch of existing Discourse helpers that solve various issues it needs overcome.

Another trick I like to use is to start by actually modifying the Discourse code itself, before trying to work with it in the context of a plugin. You want to add another icon to the header? Start by trying to add another item to the header directly in the Discourse codebase on your local machine. Find the code that renders the current items, then just try to add some additional items by directly modifying that code. Once you’ve managed to do that, then try to apply that approach within your plugin. This approach removes additional sources of errors that can arise when working in the context of a plugin. If things are going wrong in your plugin it may be because you’re not using the plugin api correctly, or it may be because you’re not using the virtual dom correctly. Sometimes it helps to start by reducing the scope of the source of your errors.

Keep it simple and open

As the Discourse codebase is large, you can sometimes get lost in it. Sometimes it can feel like your search for a solution is leading you down a rabbit-hole of complexity.

Always remember that in 99% of cases the answer to your current problem will be simple. It’s a matter of finding the simple solution. One way to find that solution is to use the community, either by posting on meta or by opening up your code to use and scrutiny. There’s a good discussion of how this open approach works in Eric Raymond’s The Cathedral and the Bazaar:

“Given enough eyeballs, all bugs are shallow.” I dub this: “Linus’s Law”. My original formulation was that every problem “will be transparent to somebody”. Linus demurred that the person who understands and fixes the problem is not necessarily or even usually the person who first characterizes it. “Somebody finds the problem,” he says, “and somebody else understands it. And I’ll go on record as saying that finding it is the bigger challenge.” … In the bazaar … you assume that bugs are generally shallow phenomena—or, at least, that they turn shallow pretty quickly when exposed to a thousand eager co-developers

On an individual scale, this means that you should stay focused on what you know and what you want to achieve. Don’t get lost in trying to understand the inner workings of everything. In most cases, the basic background knowledge listed at the beginning of this post are enough to achieve what you want to do.

If you get stuck, either use the community, or simply persevere. 99% of problems are a matter of time and patience, not some deep technical understanding.

I hope that helps somebody.

233 Likes

@angus thank you for posting this. It has helped me a lot, and I will try to read the links in greater detail this weekend.

12 Likes

This is super amazing, let me link this from the blog entry as well!

edit: done, plus linked on our official Twitter as well! :beers: cheers Angus! http://blog.discourse.org/2016/04/beginners-guide-to-creating-discourse-plugins/

29 Likes

Thankyou @angus . The article was really helpful for me. :slight_smile:

8 Likes

Hi, this is not working anymore(2018-5-16)
seach Discourse.ajax got no result.

but search ajax work

just want point this out for future reader.

Thanks for writing this post, very helpful

3 Likes

You won’t want to use Discourse.ajax anyway… (especially if you are communicating with a third party site)

That is meant for internal discourse communications, not for extending. To extend just use jquery ajax commands.

6 Likes

Thankyou @angus . The article was really helpful for me. :slight_smile:

3 Likes

Thank you

I tried to analyze the discourse.
but
It was difficult because it was different from how I knew how it worked.

A portion of the source was extracted.
“posts = postsToRender”
It was hard to figure out where to call and where to create the screen.

posts=postsToRender
{{plugin-outlet name=“topic-above-posts” args=(hash model=model)}}
{{#unless model.postStream.loadingFilter}}
{{scrolling-post-stream
posts=postsToRender

1 Like

Hello, what is the development tool in the picture above

1 Like

It’s Atom. You can read more about it here.

6 Likes

IMHO

It makes more sense to hit another site using the backend. The reason being, if the request is being sent to an http server, your response will contain data from an http source and browsers don’t like this when the sender site it https.

2 Likes

I am going to leave this here since I am finding it very useful for navigating the codebase (I’m new too)
https://www.tutorialspoint.com/ruby-on-rails/rails-directory-structure.htm

7 Likes

Clear and simple to understand

2 Likes

I think it might have moved to this folder instead

1 Like

but at what point in the process do we use this info?