Prevent Self Moderation

In Discourse, a theme component can use the plugin API to inject client-side logic and modify the user interface. To prevent moderators from managing or clearing flags on their own posts, we can customize the reviewable item interface to hide or disable the action buttons if the current user matches the post creator.
Here is a complete, production-ready Discourse theme component to achieve this.

1. Component Structure

A Discourse theme component requires a specific structure. Create a folder named discourse-prevent-self-moderation with the following files:

discourse-prevent-self-moderation/
├── about.json
└── common/
    └── head_tag.html

2. File Contents

about.json

This file defines the metadata for your theme component.

{
  "name": "Prevent Self-Moderation",
  "component": true,
  "license_url": "https://github.com/discourse/discourse/blob/main/LICENSE.txt",
  "about_url": null,
  "authors": "AI Collaborator",
  "version": "1.0.0",
  "minimum_discourse_version": "3.1.0"
}

common/head_tag.html

This is where the core logic lives. We hook into Discourse’s reviewable-item API to check if the post’s author matches the current user viewing the review queue.

<script type="text/javascript-initializer" version="0.10.1" name="prevent-self-moderation">
  import { withPluginApi } from "discourse/lib/plugin-api";

  export default {
    name: "prevent-self-moderation",
    initialize() {
      withPluginApi("0.10.1", (api) => {
        // Target the reviewable items in the review queue
        api.modifyClass("component:reviewable-item", {
          pluginId: "prevent-self-moderation",

          // Computed property to check if the current user owns the flagged post
          get isOwnPostFlag() {
            const currentUser = this.currentUser;
            const targetCreatedBy = this.args.reviewable.get("target_created_by");

            if (!currentUser || !targetCreatedBy) {
              return false;
            }

            // Compare user IDs
            return currentUser.id === targetCreatedBy.id;
          },

          // Override or adjust actions based on ownership
          get availableActions() {
            const actions = this._super(...arguments);
            
            if (this.isOwnPostFlag) {
              // Option A: Strip out all actions so they can't click anything
              return [];
              
              // Option B (Alternative): If you want them to see a warning instead, 
              // you could map through actions and set `disabled: true` on them.
            }
            
            return actions;
          },

          // Optional: Add a visual indicator or warning message
          didInsertElement() {
            this._super(...arguments);
            
            if (this.isOwnPostFlag) {
              // Add a CSS class to the reviewable item container for custom styling
              this.element.classList.add("is-self-flagged");
              
              // Prepend a warning message to the reviewable item card
              const warning = document.createElement("div");
              warning.className = "alert alert-error self-moderation-warning";
              warning.style.margin = "10px 0";
              warning.innerText = "Conflict of Interest: You cannot moderate your own flagged post.";
              
              this.element.insertBefore(warning, this.element.firstChild);
            }
          }
        });
      });
    },
  };
</script>

3. Optional CSS (For styling the warning)

If you want to make the warning look integrated, you can add a common/common.scss file to your component:

.reviewable-item.is-self-flagged {
  border: 2px solid var(--danger-medium);
  background-color: var(--danger-low);
  
  // Hide the action buttons area completely just in case
  .reviewable-actions {
    display: none !important;
  }
}

(If you add this, make sure to add “scss”: [“common/common.scss”] to your about.json targets).

4. How to Install This Component

  1. Compress the files: Zip the folder containing about.json and common/.
  2. Upload to Discourse:
  • Go to your Discourse Admin panel \rightarrow Customize \rightarrow Themes.
  • Click Install \rightarrow Upload a theme or component.
  • Upload your .zip file.
  1. Activate it: Add this component to your main active theme(s) under the “Components” section at the bottom of the theme page.

[!WARNING]
Important Note on Security: Because theme components run entirely in the user’s browser (client-side), a tech-savvy moderator could technically bypass this restriction by using the browser console or making a direct API call to resolve the flag.
For absolute, bulletproof security, this logic should ideally be handled via a server-side Discourse Plugin using a Ruby backend hook (e.g., overriding Reviewable policies). However, this theme component is highly effective for enforcing standard UI guardrails and preventing accidental self-moderation.

To allow moderators to Defer the flag (which clears the flag without penalizing the post author) while still blocking them from choosing options like Agree, Disagree, or Delete, we can filter the actions list instead of wiping it completely.
In Discourse, the actions inside the review queue have specific id strings. The action for deferring a flag is typically named “defer”.
Here is the updated common/head_tag.html script that preserves only the defer action when a moderator views their own post.

Updated common/head_tag.html

Replace the contents of your previous common/head_tag.html with this updated logic:

<script type="text/javascript-initializer" version="0.10.1" name="prevent-self-moderation">
  import { withPluginApi } from "discourse/lib/plugin-api";

  export default {
    name: "prevent-self-moderation",
    initialize() {
      withPluginApi("0.10.1", (api) => {
        api.modifyClass("component:reviewable-item", {
          pluginId: "prevent-self-moderation",

          // Computed property to check if the current user owns the flagged post
          get isOwnPostFlag() {
            const currentUser = this.currentUser;
            const targetCreatedBy = this.args.reviewable.get("target_created_by");

            if (!currentUser || !targetCreatedBy) {
              return false;
            }

            return currentUser.id === targetCreatedBy.id;
          },

          // Filter actions instead of erasing them entirely
          get availableActions() {
            const actions = this._super(...arguments);
            
            if (this.isOwnPostFlag && actions) {
              // Filter out everything EXCEPT the 'defer' action
              return actions.filter(action => action.id === "defer");
            }
            
            return actions;
          },

          // Update the warning message to reflect the change
          didInsertElement() {
            this._super(...arguments);
            
            if (this.isOwnPostFlag) {
              this.element.classList.add("is-self-flagged");
              
              const warning = document.createElement("div");
              warning.className = "alert alert-error self-moderation-warning";
              warning.style.margin = "10px 0";
              warning.innerText = "Conflict of Interest: You can only 'Ignore/Defer' flags on your own post. Other actions are disabled.";
              
              this.element.insertBefore(warning, this.element.firstChild);
            }
          }
        });
      });
    },
  };
</script>

A Quick Adjustment to Your CSS

If you used the optional CSS from the previous step, you will need to remove the rule that hid the entire .reviewable-actions container, otherwise, the Defer button won’t show up.
Update your common/common.scss to look like this:

.reviewable-item.is-self-flagged {
  border: 2px solid var(--danger-medium);
  background-color: var(--danger-low);
  
  // Custom styling for our inline warning alert
  .self-moderation-warning {
    font-weight: bold;
  }
}

Now, when a moderator encounters a flag on their own content, they will see the warning banner, the hazardous options will be stripped away, but the Defer button will remain fully interactive.