Change the default group pages to /messages


When accessing groups from the /g page, is there a way to have the group URL it points to be /g/group-slug/messages instead of /g/group-slug/members?

We’ve got several groups that are used for group messages. It would be helpful to give people one less click to reach the messages for their group. This is more important to us than the view of group members.

I know that I can do this by a direct link, but I want it to be from the /g?type=my page which is helpfully dynamic and user-centred.


They’re not regular links. They’re handled with a link-to Ember component, and there’s a little bit of routing involved.

The easiest way to do this is to hijack the click on that element and do your work there. This goes in the common > header of a theme


<script type="text/discourse-plugin" version="0.8">
  const DiscourseURL = require("discourse/lib/url").default;
  api.registerConnectorClass('group-index-box-after', 'link-to-group-inbox', {
    setupComponent(args) {
      const group = args.model;

       if (group.is_group_user && group.has_messages) {
         const groupLink = document.querySelector(`.group-box.${}`);
         const newHref = `/g/${}/messages`;

         // updates browser title and handle new tab
         groupLink.href = newHref;

         // redirect the link
         groupLink.addEventListener("click", (event) => {
         }, {once: true})

If the user is a member of the group and the group has messages, it will redirect to the inbox. Otherwise, it will fall back to the default /members page.


Wow - that is amazing!!! It works a treat. Thank you.


I’ve got a follow-on issue which I need a wee bit of help with sorry!

Several of my groups still display the /g/group-slug/messages despite not having any.

On closer inspection, they have had messages in the past, which have been converted to Topics or deleted. Data Explorer reveals that these groups have True in has_messages in the Groups table.

I’ve tried changing it for these groups via Rails but I can’t seem to achieve it:

[2] pry(main)> Group.find_by_name(“group-slug”).has_messages
=> true
[3] pry(main)> Group.find_by_name(“group-slug”).has_messages = false
=> false
[4] pry(main)> Group.find_by_name(“group-slug”).has_messages
=> true

Any suggestions short of deleting the groups and remaking them (which I’m not super keen to do)?


You only need to run this line in the rails console. It should update all the groups.

1 Like

That worked for all but two - thank you. Those last pesky two I think I’ll just have to delete and remake!

On doing this, I discovered the reason - which is a minor #bug.

When converting Group Private Messages to Topics, the invitee’s information is retained. While this is nice if the Topic is converted back, it does mean that they are counted erroneously when Group.refresh_has_messages! is run.


Is it possible to also hijack the links which you get when you click on a @mentioned group and then click through to the group? I think those are the only two ways to get to groups without resorting to URLs.

What I am now after (greedy aren’t I?) is actually to have all links to the groups behave the same as your code makes the /g page ones do.

1 Like

@Johani, is this doable? I’ve had a poke around, and it is clearly way above my pay grade.

Sure, you can try something like this in the header tab of your theme/component. I left some comments in the snippet if you want to follow along. You can delete those once you’re ready to use it.

The same rules apply. If the user is a member of the group and the group has messages, Discourse will navigate to the group inbox. Otherwise, the user will land on the member directory.

<script type="text/discourse-plugin" version="0.8">
  // Part 1: modify the path for the links in the group cards. This ensure that the link
  // title reflects where it leads and handles opening the link a new tab.
  const discourseComputed = require("discourse-common/utils/decorators").default;
  api.modifyClass("component:group-card-contents", {
    // The groupPath property is used to generate the href for the group links
    // in the group card. Let's modify it
    groupPath(group) {
      // get the default value from core
      let groupURL = this._super(...arguments);

      // if the user matches our requirment, modify the url so it goes to the inbox
      if (group.is_group_user && group.has_messages) {
        groupURL = `${groupURL}/messages/`;

      // return either the default or modified value
      return groupURL;

  // Part 2: modify in-app routing. This ensures that regular in-app navigation is 
  // handled correctly and takes the user to the group inbox if they meet the 
  // requirements
  const DiscourseURL = require("discourse/lib/url").default;
  const { groupPath } = require("discourse/lib/url");
  const { action } = require("@ember/object");

  api.modifyClass("controller:user-card", {

    // showGroup here is an action that belongs to the user-card controller. 
    // This is what gets called when you click on the group name / avatar inside group cards
    showGroup(group) {
      // call super to make sure code from core is loaded first

      // group path is a built-in Discourse url helper function
      let groupURL = groupPath(;

      // if the user matches the requirments, modify the url
      if (group.is_group_user && group.has_messages) {
        groupURL = `${groupURL}/messages/`;

      // call routeTo() with either the default or the modified groupURL 


You are amazing! It works great for any case that uses the cards.

There is now one type of link that remains undirected: that of the group when in a Group Private Message. This is actually quite handy for navigating to the group (and thus the inbox) from a Group Message.

Screenshot 2022-01-06 15.25.38

Can we catch that in the net too do you reckon?

(BTW I’ll package this into a TC and do a topic for it once we are done so it is very accessible for others)


Those links use a slightly different serializer - because not all the group data is needed there.

Still, you can store a filtered list of the current user’s groups on the initial page view and use those as a reference to check for membership. This information is already available in the initial payload, so there’s no overhead in terms of extra requests or anything like that. Something like this

<script type="text/discourse-plugin" version="0.8">
  const user = api.getCurrentUser();

  if (!user) {

  const userGroups = =>;

  api.reopenWidget("pm-map-user-group", {
    transform(attrs) {

      const group =;
      const isGroupUser = Object.values(userGroups).includes(;

      // {href: "/g/foo"};
      let groupURL = this._super(...arguments);

      if (isGroupUser && group.has_messages) {
        groupURL.href = `${groupURL.href}/messages/`;

      return groupURL;

Keep this snippet in its own script tags and add it to the header along with the other snippets. You keep it separate because it does a quick bail if the user is not logged in.

1 Like

:partying_face: we have arrived! It works a treat, also on mobile.

Thanks Joe for your amazing persistence. I’d better keep up my end of the bargain and get to work on that #theme-component, eh?

Here 'tis:

1 Like

I’ve got one more thing that I need a hand with in order to fully complete this. It is a bit cheeky to ask, I know.

I’m now trying to modify the 👨‍👨‍👧‍👦 Groups Widget - Layouts - Pavilion so that it can support this behaviour too, using the simple logic from your three awesome snippets. However, I just can’t get it to work due to my ineptitude with javascript. This is what I’m trying to modify:

createWidget('layouts-group-link', {
  tagName: 'li.layouts-group-link',
  buildKey: (attrs) => `layouts-group-link-${}`,

  getGroupTitle(group) {
    return h('',;

  isOwner(group) {
    if (group.owner) {
      return h('', iconNode('shield-alt'));

  html(attrs) {
    const contents = [this.getGroupTitle(attrs), this.isOwner(attrs)];
    return contents;

  click() {

How do I apply the bit below to that? I’ve tried everything that I could think of to the last click(){etc} bit but have failed. I just end up breaking the link completely.

      if (group.is_group_user && group.has_messages) {
        groupURL = `${groupURL}/messages/`;
1 Like

Never mind!!! @keegan has sorted it:

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.

For some groups where there are ‘message ghosts’ this doesn’t work. These groups can be identified by this query (provided that messaging has been restricted in the group interaction settings):

SELECT id, name, has_messages, messageable_level
From groups
Where messageable_level < 10
AND messageable_level <> 3
AND has_messages = true
ORDER by id

You can hack the groups table to force this to reset as well (in the Rails console of course, backup first!).

 Group.where(id: XXX).update_all(has_messages: false)

If this doesn’t work or reverts, you might also need to clean up the topic_groups, topic_allowed_groups tables too:

 TopicGroup.where(group_id: XXX).destroy_all
 TopicAllowedGroup.where(group_id: XXX).destroy_all

This topic was automatically closed after 30 days. New replies are no longer allowed.