As part of our continuous effort to improve the Discourse codebase, we’re removing uses of the legacy “widget” rendering system, and replacing them with Glimmer components.
Recently, we modernized the post menu, and is now available in Discourse behind the glimmer_post_menu_mode
setting.
This setting accepts three possible values:
disabled
: use the legacy “widget” systemauto
: will detect the compatibility of your current plugins and themes. If any are incompatible, it will use the legacy system; otherwise it will use the new menu.enabled
: will use the new menu. If you have any incompatible plugin or theme, your site may be broken.
We already updated our official plugins to be compatible with the new menu, but if you still have any third-party plugin, theme, or theme component incompatible with the new menu, upgrading them will be required.
Warnings will be printed in the browser console identifying the source of the incompatibility.
Roll-out Timeline
These are rough estimates subject to change
Q4 2024:
- core implementation finished
- official plugins updated
- enabled on Meta
-
glimmer_post_menu_mode
default toauto
; console deprecation messages enabled - published upgrade advice
Q1 2025:
- third-party plugins and themes should be updated
- deprecation messages start, triggering an admin warning banner for any remaining issues
- enabled the new post menu by default
Q2 2025
- removal of the feature flag setting and legacy code
What does it mean for me?
If your plugin or theme uses any ‘widget’ APIs to customize the post menu, those will need to be updated for compatibility with the new version.
How do I try the new Post Menu?
In the latest version of Discourse, the new post menu will be enabled if you don’t have any incompatible plugin or theme.
If you do have incompatible extensions installed, as an admin, you can still change the setting to enabled
to force using the new menu. Use this with caution as your site may be broken depending on the customizations you have installed.
In the unlikely event that this automatic system does not work as expected, you can temporarily override this ‘automatic feature flag’ using the setting above. If you need to that, please let us know in this topic.
Do I need to update my plugin and theme?
You will need to update your plugins or themes if they perform any of the customizations below:
-
Use
decorateWidget
,changeWidgetSetting
,reopenWidget
orattachWidgetAction
on these widgets:post-menu
post-user-tip-shim
small-user-list
-
Use any of the API methods below:
addPostMenuButton
removePostMenuButton
replacePostMenuButton
In case you have extensions that perform one of the customizations above, a warning will be printed in the console identifying the plugin or component that needs to be upgraded, when you access a topic page.
The deprecation ID is:
discourse.post-menu-widget-overrides
If you use more than one theme in your instance, be sure to check all of them as the warnings will be printed only for the active plugins and currently used themes and theme-components.
What are the replacements?
We introduced the value transformer post-menu-buttons
as the new API to customize the post menu.
The value transformer provides a DAG object which allows adding, replacing removing, or reordering the buttons. It also provides context information such as the post associated with the menu, the state of post being displayed and button keys to enable a easier placement of the items.
The DAG APIs expects to receive Ember components if the API needs a new button definition like .add
and .replace
Each customization is different, but here is some guidance for the most common use cases:
addPostMenuButton
Before:
withPluginApi("1.34.0", (api) => {
api.addPostMenuButton("solved", (attrs) => {
if (attrs.can_accept_answer) {
const isOp = currentUser?.id === attrs.topicCreatedById;
return {
action: "acceptAnswer",
icon: "far-check-square",
className: "unaccepted",
title: "solved.accept_answer",
label: isOp ? "solved.solution" : null,
position: attrs.topic_accepted_answer ? "second-last-hidden" : "first",
};
}
});
});
After:
The examples below use Ember’s Template Tag Format (gjs)
// components/solved-accept-answer-button.gjs
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class SolvedAcceptAnswerButton extends Component {
// indicates if the button will be prompty displayed or hidden behind the show more button
static hidden(args) {
return args.post.topic_accepted_answer;
}
...
<template>
<DButton
class="post-action-menu__solved-unaccepted unaccepted"
...attributes
@action={{this.acceptAnswer}}
@icon="far-check-square"
@label={{if this.showLabel "solved.solution"}}
@title="solved.accept_answer"
/>
</template>
}
// initializer.js
import SolvedAcceptAnswerButton from "../components/solved-accept-answer-button";
...
withPluginApi("1.34.0", (api) => {
api.registerValueTransformer(
"post-menu-buttons",
({
value: dag,
context: {
post,
firstButtonKey, // key of the first button
secondLastHiddenButtonKey, // key of the second last hidden button
lastHiddenButtonKey, // key of the last hidden button
},
}) => {
dag.add(
"solved",
SolvedAcceptAnswerButton,
post.topic_accepted_answer
? {
before: lastHiddenButtonKey,
after: secondLastHiddenButtonKey,
}
: {
before: [
"assign", // button added by the assign plugin
firstButtonKey,
],
}
);
}
);
});
replacePostMenuButton
- before:
withPluginApi("1.34.0", (api) => {
api.replacePostMenuButton("like", {
name: "discourse-reactions-actions",
buildAttrs: (widget) => {
return { post: widget.findAncestorModel() };
},
shouldRender: (widget) => {
const post = widget.findAncestorModel();
return post && !post.deleted_at;
},
});
});
- after:
import ReactionsActionButton from "../components/discourse-reactions-actions-button";
...
withPluginApi("1.34.0", (api) => {
api.registerValueTransformer(
"post-menu-buttons",
({ value: dag, context: { buttonKeys } }) => {
// ReactionsActionButton is the bnew button component
dag.replace(buttonKeys.LIKE, ReactionsActionButton);
}
);
});
removePostMenuButton
- before:
withPluginApi("1.34.0", (api) => {
api.removePostMenuButton('like', (attrs, state, siteSettings, settings, currentUser) => {
if (attrs.post_number === 1) {
return true;
}
});
});
- after:
withPluginApi("1.34.0", (api) => {
api.registerValueTransformer(
"post-menu-buttons",
({ value: dag, context: { post, buttonKeys } }) => {
if (post.post_number === 1) {
dag.delete(buttonKeys.LIKE);
}
}
);
});
What about other customizations?
If your customization cannot be achieved using the new API we’ve introduced, please let us know by creating a new dev topic to discuss.
I am a plugin/theme author. How do I update a theme/plugin to support both old and new post menu during the transition?
We’ve used the pattern below to support both the old and new version of the post menu in our plugins:
function customizePostMenu(api) {
const transformerRegistered = api.registerValueTransformer(
"post-menu-buttons",
({ value: dag, context }) => {
// new post menu customizations
...
}
);
const silencedKey =
transformerRegistered && "discourse.post-menu-widget-overrides";
withSilencedDeprecations(silencedKey, () => customizeWidgetPostMenu(api));
}
function customizeWidgetPostMenu(api) {
// old "widget" code customization here
...
}
export default {
name: "my-plugin",
initialize(container) {
withPluginApi("1.34.0", customizePostMenu);
}
};
More examples
You can check, our official plugins for examples on how to use the new API: