diff --git a/app/serializers/reviewable_score_serializer.rb b/app/serializers/reviewable_score_serializer.rb
index db6331be6b9..143239b0fa3 100644
--- a/app/serializers/reviewable_score_serializer.rb
+++ b/app/serializers/reviewable_score_serializer.rb
@@ -1,6 +1,19 @@
 # frozen_string_literal: true
 
 class ReviewableScoreSerializer < ApplicationSerializer
+  REASONS_AND_SETTINGS = {
+    post_count: 'approve_post_count',
+    trust_level: 'approve_unless_trust_level',
+    new_topics_unless_trust_level: 'approve_new_topics_unless_trust_level',
+    fast_typer: 'min_first_post_typing_time',
+    auto_silence_regexp: 'auto_silence_first_post_regex',
+    staged: 'approve_unless_staged',
+    must_approve_users: 'must_approve_users',
+    invite_only: 'invite_only',
+    email_spam: 'email_in_spam_header',
+    suspect_user: 'approve_suspect_users',
+    contains_media: 'review_media_unless_trust_level',
+  }
 
   attributes :id, :score, :agree_stats, :status, :reason, :created_at, :reviewed_at
   has_one :user, serializer: BasicUserSerializer, root: 'users'
@@ -19,8 +32,8 @@ class ReviewableScoreSerializer < ApplicationSerializer
   def reason
     return unless object.reason
 
-    link_text = I18n.t("reviewables.reasons.site_setting_links.#{object.reason}", default: nil)
-    link_text = I18n.t("reviewables.reasons.regular_links.#{object.reason}", default: nil) if link_text.nil?
+    link_text = setting_name_for_reason(object.reason)
+    link_text = I18n.t("reviewables.reasons.links.#{object.reason}", default: nil) if link_text.nil?
 
     if link_text
       link = build_link_for(object.reason, link_text)
@@ -41,6 +54,19 @@ class ReviewableScoreSerializer < ApplicationSerializer
     reason.present?
   end
 
+  def setting_name_for_reason(reason)
+    setting_name = REASONS_AND_SETTINGS[reason.to_sym]
+
+    if setting_name.nil?
+      plugin_options = DiscoursePluginRegistry.reviewable_score_links
+      option = plugin_options.detect { |o| o[:reason] == reason.to_sym }
+
+      setting_name = option[:setting] if option
+    end
+
+    setting_name
+  end
+
   private
 
   def url_for(reason, text)
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index cdc007b3286..8c93681396a 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -5012,21 +5012,9 @@ en:
       suspect_user: "This new user entered profile information without reading any topics or posts, which strongly suggests they may be a spammer. See %{link}."
       contains_media: "This post includes embedded media. See %{link}."
       queued_by_staff: "A staff member thinks this post needs review. It'll remain hidden until then."
-      regular_links:
+      links:
         watched_word: list of watched words
         category: category settings
-      site_setting_links:
-        post_count: approve_post_count
-        trust_level: approve_unless_trust_level
-        new_topics_unless_trust_level: approve_new_topics_unless_trust_level
-        fast_typer: min_first_post_typing_time
-        auto_silence_regexp: auto_silence_first_post_regex
-        staged: approve_unless_staged
-        must_approve_users: must_approve_users
-        invite_only: invite_only
-        email_spam: email_in_spam_header
-        suspect_user: approve_suspect_users
-        contains_media: review_media_unless_trust_level
 
     actions:
       agree:
diff --git a/lib/discourse_plugin_registry.rb b/lib/discourse_plugin_registry.rb
index 28a5c717270..8106809144d 100644
--- a/lib/discourse_plugin_registry.rb
+++ b/lib/discourse_plugin_registry.rb
@@ -87,6 +87,7 @@ class DiscoursePluginRegistry
 
   define_filtered_register :permitted_bulk_action_parameters
   define_filtered_register :reviewable_params
+  define_filtered_register :reviewable_score_links
 
   define_filtered_register :presence_channel_prefixes
 
diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb
index fcd25b1025c..f7e9305e34f 100644
--- a/lib/plugin/instance.rb
+++ b/lib/plugin/instance.rb
@@ -950,6 +950,26 @@ class Plugin::Instance
     DiscoursePluginRegistry.register_presence_channel_prefix([prefix, block], self)
   end
 
+  # Register a ReviewableScore setting_name associated with a reason.
+  # We'll use this to build a site setting link and add it to the reason's translation.
+  #
+  # If your plugin has a reason translation looking like this:
+  #
+  #   my_plugin_reason: "This is the reason this post was flagged. See %{link}."
+  #
+  # And you associate the reason with a setting:
+  #
+  #   add_reviewable_score_link(:my_plugin_reason, 'a_plugin_setting')
+  #
+  # We'll generate the following link and attach it to the translation:
+  #
+  #   <a href="/admin/site_settings/category/all_results?filter=a_plugin_setting">
+  #     a plugin setting
+  #   </a>
+  def add_reviewable_score_link(reason, setting_name)
+    DiscoursePluginRegistry.register_reviewable_score_link({ reason: reason.to_sym, setting: setting_name }, self)
+  end
+
   protected
 
   def self.js_path
diff --git a/spec/serializers/reviewable_score_serializer_spec.rb b/spec/serializers/reviewable_score_serializer_spec.rb
index 103148de6f1..8c82085ea4a 100644
--- a/spec/serializers/reviewable_score_serializer_spec.rb
+++ b/spec/serializers/reviewable_score_serializer_spec.rb
@@ -11,7 +11,7 @@ describe ReviewableScoreSerializer do
       it 'adds a link for watched words' do
         serialized = serialized_score('watched_word')
         link_url = "#{Discourse.base_url}/admin/customize/watched_words"
-        watched_words_link = "<a href=\"#{link_url}\">#{I18n.t('reviewables.reasons.regular_links.watched_word')}</a>"
+        watched_words_link = "<a href=\"#{link_url}\">#{I18n.t('reviewables.reasons.links.watched_word')}</a>"
 
         expect(serialized.reason).to include(watched_words_link)
       end
@@ -21,7 +21,7 @@ describe ReviewableScoreSerializer do
         reviewable.category = category
         serialized = serialized_score('category')
         link_url = "#{Discourse.base_url}/c/#{category.name}/edit/settings"
-        category_link = "<a href=\"#{link_url}\">#{I18n.t('reviewables.reasons.regular_links.category')}</a>"
+        category_link = "<a href=\"#{link_url}\">#{I18n.t('reviewables.reasons.links.category')}</a>"
 
         expect(serialized.reason).to include(category_link)
       end
@@ -36,7 +36,7 @@ describe ReviewableScoreSerializer do
       reasons.each do |r|
         it "addd a link to a site setting for the #{r} reason" do
           serialized = serialized_score(r)
-          setting_name = I18n.t("reviewables.reasons.site_setting_links.#{r}")
+          setting_name = described_class::REASONS_AND_SETTINGS[r.to_sym]
           link_url = "#{Discourse.base_url}/admin/site_settings/category/all_results?filter=#{setting_name}"
           link = "<a href=\"#{link_url}\">#{setting_name.gsub('_', ' ')}</a>"
 
@@ -46,6 +46,25 @@ describe ReviewableScoreSerializer do
     end
   end
 
+  describe '#setting_name_for_reason' do
+    after { DiscoursePluginRegistry.reset_register!(:reviewable_score_links) }
+
+    describe 'when a plugin adds a setting name to linkify' do
+      it 'gets the setting name from the registry' do
+        reason = :plugin_reason
+        setting_name = 'max_username_length'
+        DiscoursePluginRegistry.register_reviewable_score_link(
+          { reason: reason, setting: setting_name },
+          Plugin::Instance.new
+        )
+
+        score = serialized_score(reason)
+
+        expect(score.setting_name_for_reason(reason)).to eq(setting_name)
+      end
+    end
+  end
+
   def serialized_score(reason)
     score = ReviewableScore.new(
       reviewable: reviewable,