# A versioned API for client side plugins

**URL:** https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051
**Category:** Development
**Tags:** rest-api
**Created:** [February 23, 2016, 7:20pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051 "2016-02-23T19:20:31Z")
**Posts on this page:** 15
**Page:** 2

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [February 25, 2016, 4:41pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/21 "2016-02-25T16:41:00Z")

</div>

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

> [@ccdw](#):
>
> Thank you for the prompt reply. No guarantees of course, but how long do you think will you continue to support that type of snippet?

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.

> [@fantasticfears](#):
>
> But the thing actually I want to touch is the plugin enable settings. That’s something api.pluginSettings seems suitable…

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.

> [@fantasticfears](#):
>
> What API library can’t give to me is the ability to monkey patch. For sure, Discourse may have more decorators and hook point. However, the change takes 2 years at least until plugin authors enjoy the rich hook point.

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.

> [@fantasticfears](#):
>
> api.decorateWidget can apply before and after type to the decorator. Without applying type, is it applying both?

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

> [@fantasticfears](#):
>
> api.decorateCooked is a little bit tricky. model may exists in stream, but may not appear when composer appear.

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.

> [@angus](#):
>
> I’m still a little uncomfortable about helper.widget.container.lookup(‘controller:topic’) though. That feels a bit long winded.

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 ☹ – `rawHTML` will work but the widget would be nicer and faster.

---

<div class="post-metadata">

### Author: ![angus](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/angus/32/341715_2.png) [@angus](https://meta.discourse.org/u/angus)
#### Post date: [February 25, 2016, 5:06pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/22 "2016-02-25T17:06:21Z")

</div>

> [@eviltrout](#):
>
> 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?

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

 ![](https://global.discourse-cdn.com/meta/original/3X/b/e/be0838adabc01b89b0fadb9401216ae5a3b4c2b4.png)

 ![](https://global.discourse-cdn.com/meta/original/3X/1/0/1093d2490d088e02721ecb1ae874a356a711bf7d.png)

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.](https://meta.discourse.org/t/topic-ratings-plugin/39578/14))

However, looking now at @joebuhlig’s nice [Feature Voting plugin](https://meta.discourse.org/t/discourse-feature-voting/40121), 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.

 ![](https://global.discourse-cdn.com/meta/original/3X/c/2/c2a6a85fcf46238608810c754108a91427bdc443.png)

* * *

> 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 ☹ – rawHTML will work but the widget would be nicer and faster

Ah yes, thanks for the tip.

---

<div class="post-metadata">

### Author: ![codinghorror](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/codinghorror/32/110067_2.png) [@codinghorror](https://meta.discourse.org/u/codinghorror)
#### Post date: [February 25, 2016, 9:49pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/23 "2016-02-25T21:49:15Z")

</div>

7 posts were split to a new topic: [New vdom rendering missing inbound/outbound CSS classes in gutter](https://meta.discourse.org/t/new-vdom-rendering-missing-inbound-outbound-css-classes-in-gutter/40192)

---

<div class="post-metadata">

### Author: ![Mittineague](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/mittineague/32/114259_2.png) [@Mittineague](https://meta.discourse.org/u/Mittineague)
#### Post date: [February 27, 2016, 4:12am UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/31 "2016-02-27T04:12:29Z")

</div>

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

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?

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

```

---

<div class="post-metadata">

### Author: ![cpradio](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/cpradio/32/4970_2.png) [@cpradio](https://meta.discourse.org/u/cpradio)
#### Post date: [February 27, 2016, 4:16am UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/32 "2016-02-27T04:16:43Z")

</div>

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…

---

<div class="post-metadata">

### Author: ![Mittineague](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/mittineague/32/114259_2.png) [@Mittineague](https://meta.discourse.org/u/Mittineague)
#### Post date: [February 27, 2016, 4:24am UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/33 "2016-02-27T04:24:13Z")

</div>

Ah, it is in master here

[https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/lib/plugin-api.js.es6](https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/lib/plugin-api.js.es6)

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.

EDIT  
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

```plaintext
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() });
  }
}

```

 ![](https://global.discourse-cdn.com/meta/original/3X/f/9/f9bf9b79b53ec3db1e51d8cbafccdae436335e2d.png)

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [February 29, 2016, 5:18pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/34 "2016-02-29T17:18:12Z")

</div>

> [@cpradio](#):
>
> 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.

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.

---

<div class="post-metadata">

### Author: ![cpradio](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/cpradio/32/4970_2.png) [@cpradio](https://meta.discourse.org/u/cpradio)
#### Post date: [February 29, 2016, 6:25pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/35 "2016-02-29T18:25:23Z")

</div>

> [@eviltrout](#):
>
> 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.

Cool, I missed that it was back ported. Thanks 🙂

---

<div class="post-metadata">

### Author: ![LeoMcA](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/leomca/32/87233_2.png) [@LeoMcA](https://meta.discourse.org/u/LeoMcA)
#### Post date: [July 3, 2016, 5:50pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/36 "2016-07-03T17:50:08Z")

</div>

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:

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

```

When trying:

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

```

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

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [July 4, 2016, 2:31pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/37 "2016-07-04T14:31:24Z")

</div>

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.

---

<div class="post-metadata">

### Author: ![profcaju](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/profcaju/32/301565_2.png) [@profcaju](https://meta.discourse.org/u/profcaju)
#### Post date: [February 22, 2023, 3:26am UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/38 "2023-02-22T03:26:09Z")

</div>

Hello, @cpradio. Did you managed to add click events to certain cooked content?

I need to do this exact same thing. Could you share the solution?

Kind regards,

---

<div class="post-metadata">

### Author: ![Arkshine](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/arkshine/32/298682_2.png) [@Arkshine](https://meta.discourse.org/u/Arkshine)
#### Post date: [October 13, 2023, 2:48pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/39 "2023-10-13T14:48:28Z")

</div>

> [@eviltrout](#):
>
> should work. If you want a more involved example, you can [see here how I updated the solved plugin](https://github.com/discourse/discourse-solved/blob/master/assets/javascripts/discourse/initializers/extend-for-solved-button.js.es6) to use this approach.

Sorry for the necro topic. This link needs to be updated; it should be now: `https://github.com/discourse/discourse-solved/blob/main/assets/javascripts/discourse/initializers/extend-for-solved-button.js`

Also, the `noApi` option doesn’t exist (anymore). From what I see, it was removed the next day `withPluginApi` was introduced.

---

<div class="post-metadata">

### Author: ![Canapin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/canapin/32/119591_2.png) [@Canapin](https://meta.discourse.org/u/Canapin)
#### Post date: [October 13, 2023, 3:14pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/40 "2023-10-13T15:14:47Z")

</div>

I replaced the link, thanks 🤝

---

<div class="post-metadata">

### Author: ![rsigg](https://avatars.discourse-cdn.com/v4/letter/r/ac8455/32.png) [@rsigg](https://meta.discourse.org/u/rsigg)
#### Post date: [June 21, 2024, 9:05pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/41 "2024-06-21T21:05:58Z")

</div>

> [@eviltrout](#):
>
> The documentation for the PluginAPI version 0.1 can [be found in plugin-api.js](https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/app/lib/plugin-api.js)

This link leads to a “404 - Page not found”

---

<div class="post-metadata">

### Author: ![RGJ](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/rgj/32/523185_2.png) [@RGJ](https://meta.discourse.org/u/RGJ)
#### Post date: [June 21, 2024, 10:18pm UTC](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051/42 "2024-06-21T22:18:20Z")

</div>

It’s now [https://github.com/discourse/discourse/blob/main/app/assets/javascripts/discourse/app/lib/plugin-api.gjs](https://github.com/discourse/discourse/blob/main/app/assets/javascripts/discourse/app/lib/plugin-api.gjs)

Updated the OP

[Previous page](https://meta.discourse.org/t/a-versioned-api-for-client-side-plugins/40051.md?page=1)
