A new versioned API for client side plugins

Sorry for the reply below with many quotes, but there’s a lot to respond to!

To be honest I’ve never been fully comfortable supporting that kind of snippet, as it’s not importing our modules the correct way and depends highly on our internals. Having said that, I will try my best not to break it until we can offer something that solves the use case.

There should probably be some way of auto detecting the plugin being disabled. I haven’t had to face this case myself but would be open to PRs or suggestions for how to do this.

Well I don’t think it takes us two years to add hook points! Generally we’ll add them very quickly once people ask for them and provide a use case. For reference, our discourse-tagging plugin is barely two years old!

Monkey patching is always risky. We do it in some plugins but the long term goal is to create APIs that handle most of the things people need to patch in. Having said that, if you are adding a new computed property to topic or post, chances are that will work forever.

No, nothing will happen if there is no :after or :before present.

Rather than think of whether you’re in the composer or the post stream, you should just account for sometimes having a model or not. For example, the user stream also applies decorators.

Yes I agree that’s a bit funky. In this case, showRating is a property on the controller right? Is it there because people can enable/disable it on the topic?

The reason I ask is because it’s much more complicated to add a topic controller attribute into the post stream without modifying the handlebars template topic.hbs, but if it’s an attribute on the topic model itself it would be pretty straightforward to add it.

Also in your code, I would recommend using includePostAttributes to add rating to attrs rather than calling getModel. I’d also recommend creating a new widget for the rating, but I haven’t written up how to do that yet because I’ve been quite busy :frowning:rawHTML will work but the widget would be nicer and faster.


You can disable/enable ratings for a category or by adding a tag to a topic.

I could just check whether the post’s rating property has a value and show it if it does, however I will need to provide for hiding ratings in topics that already have them (see e.g.)

However, looking now at @joebuhlig’s nice Feature Voting plugin, I think it would be better if I adopted the approach he’s taken for a similar case - i.e. put this logic in the topic view serializer on the server.

Also in your code, I would recommend using includePostAttributes to add rating to attrs rather than calling getModel. I’d also recommend creating a new widget for the rating, but I haven’t written up how to do that yet because I’ve been quite busy :frowning: – rawHTML will work but the widget would be nicer and faster

Ah yes, thanks for the tip.

1 Like

7 posts were split to a new topic: New vdom rendering missing inbound/outbound CSS classes in gutter

@cpradio I see you’ve been busy adding API to plugins :thumbsup:

I missed the “tests-passed” bit and tried while running master.

:sadpanda: I got

TypeError: withPluginApi is not a function

Have you found a way to make this conditional?

import { withPluginApi } from 'discourse/lib/plugin-api';

Do you mean beta or stable? As master should have plugin-api. As it is a branch higher than tests-passed. Both beta and master are below, so they wouldn’t have it.

To answer your question, I haven’t toyed with that aspect of it yet. @eviltrout, is there a concern here? As if the lib doesn’t exist in beta and stable and it throws an error due to its non-existence, then this backwards compatibility isn’t truly backwards compatible…

Ah, it is in master here

I could have sworn I did a git fetch upstream but I must have done so on a different copy of Discourse than I thought I had.

I really should get a new eyeglass prescription or at least put more effort into reading what I can barely see.

Looks like I probably missed the “Gemfile.lock changed commit … Aborted”
(though I didn’t change it, something obviously did)

I’m good now

import { withPluginApi } from 'discourse/lib/plugin-api';

function oldCode() {
  // migrate your old plugin code here. It will only be run if no PluginAPI  is present
  console.log("in old code initializer block");

function initializePlugin(api) {
  // do stuff with plugin API!
  console.log("in plugin API initializer block");

export default {
  name: 'plugin-outlet-locations',
  initialize() {
     withPluginApi('0.1', api => initializePlugin(api), { noApi: () => oldCode() });

I backported withPluginApi to beta and stable, so as long as you are on the latest version of any branch, you will have the function and will never need to conditionally load it. Note that the accepted way to install plugins always involved pulling from the latest branch you are tracking, so updating or adding any plugin will retrieve the withPluginApi support at the same time if you don’t have it.

The only case where you wouldn’t have it is if you are locked to an old commit hash for some reason. I would not recommend that setup.


Cool, I missed that it was back ported. Thanks :slightly_smiling:

1 Like

It may well be that there are no versions of Discourse for which noApi support is needed anymore, so this might be irrelevant, but I can’t seem to work out how to import a module which doesn’t exist in later versions of Discourse, in order to provide backwards compatibility, without breaking the plugin in those later versions.

The completely expected error I get (in the later versions) is:

Error: Could not find module `discourse/components/hamburger-menu` imported from `discourse/plugins/static-pages/discourse/initializers/static-pages-menu`

When trying:

import HamburgerMenuComponent from 'discourse/components/hamburger-menu';

As far as I’m aware, conditional imports aren’t possible in ES6, are they?

You unfortunately can’t do a proper ES6 import conditionally. However, if it’s for your noAPI section, I don’t consider it awful to do a manual style import:

const hamburger = require('discourse/components/hamburger-menu').default;

Definitely don’t do the above in regular code, but in noAPI it should work.