diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb
index d8d8e6717c6..380a63e9875 100644
--- a/lib/plugin/instance.rb
+++ b/lib/plugin/instance.rb
@@ -208,6 +208,14 @@ class Plugin::Instance
     Search.advanced_filter(trigger, &block)
   end
 
+  # Allows to define TopicView posts filters. Example usage:
+  #   TopicView.advanced_filter do |posts, opts|
+  #     posts.where(wiki: true)
+  #   end
+  def register_topic_view_posts_filter(trigger, &block)
+    TopicView.add_custom_filter(trigger, &block)
+  end
+
   # Allow to eager load additional tables in Search. Useful to avoid N+1 performance problems.
   # Example usage:
   #   register_search_topic_eager_load do |opts|
diff --git a/lib/topic_view.rb b/lib/topic_view.rb
index a646aede74a..d56b064c06a 100644
--- a/lib/topic_view.rb
+++ b/lib/topic_view.rb
@@ -51,6 +51,15 @@ class TopicView
     wpcf.flatten.uniq
   end
 
+  def self.add_custom_filter(key, &blk)
+    @custom_filters ||= {}
+    @custom_filters[key] = blk
+  end
+
+  def self.custom_filters
+    @custom_filters || {}
+  end
+
   def initialize(topic_or_topic_id, user = nil, options = {})
     @topic = find_topic(topic_or_topic_id)
     @user = user
@@ -772,6 +781,10 @@ class TopicView
       @contains_gaps = true
     end
 
+    if @filter.present? && @filter != 'summary' && TopicView.custom_filters[@filter].present?
+      @filtered_posts = TopicView.custom_filters[@filter].call(@filtered_posts, self)
+    end
+
     if @best.present?
       @filtered_posts = @filtered_posts.where('posts.post_type = ?', Post.types[:regular])
       @contains_gaps = true
diff --git a/spec/components/topic_view_spec.rb b/spec/components/topic_view_spec.rb
index d772becfd8e..9db72fd9f90 100644
--- a/spec/components/topic_view_spec.rb
+++ b/spec/components/topic_view_spec.rb
@@ -55,6 +55,29 @@ describe TopicView do
     end
   end
 
+  context 'custom filters' do
+    fab!(:p0) { Fabricate(:post, topic: topic) }
+    fab!(:p1) { Fabricate(:post, topic: topic, wiki: true) }
+
+    it 'allows to register custom filters' do
+      tv = TopicView.new(topic.id, evil_trout, { filter: 'wiki' })
+      expect(tv.filter_posts({ filter: "wiki" })).to eq([p0, p1])
+
+      TopicView.add_custom_filter("wiki") do |posts, topic_view|
+        posts.where(wiki: true)
+      end
+
+      tv = TopicView.new(topic.id, evil_trout, { filter: 'wiki' })
+      expect(tv.filter_posts).to eq([p1])
+
+      tv = TopicView.new(topic.id, evil_trout, { filter: 'whatever' })
+      expect(tv.filter_posts).to eq([p0, p1])
+
+      ensure
+        TopicView.instance_variable_set(:@custom_filters, [])
+    end
+  end
+
   context "setup_filtered_posts" do
     describe "filters posts with ignored users" do
       fab!(:ignored_user) { Fabricate(:ignored_user, user: evil_trout, ignored_user: user) }
diff --git a/spec/requests/topics_controller_spec.rb b/spec/requests/topics_controller_spec.rb
index d42354c59f6..68a010f6f16 100644
--- a/spec/requests/topics_controller_spec.rb
+++ b/spec/requests/topics_controller_spec.rb
@@ -2436,6 +2436,29 @@ RSpec.describe TopicsController do
           expect(body["post_ids"]).to eq([post2.id])
         end
       end
+
+      describe 'custom filters' do
+        fab!(:post2) { Fabricate(:post, topic: topic, percent_rank: 0.2) }
+        fab!(:post3) { Fabricate(:post, topic: topic, percent_rank: 0.5) }
+        it 'should return the right posts' do
+          TopicView.add_custom_filter("percent") do |posts, topic_view|
+            posts.where(percent_rank: 0.5)
+          end
+
+          get "/t/#{topic.id}.json", params: {
+            post_number: post.post_number,
+            filter: 'percent'
+          }
+
+          expect(response.status).to eq(200)
+
+          body = response.parsed_body
+
+          expect(body["post_stream"]["posts"].map { |p| p["id"] }).to eq([post3.id])
+        ensure
+          TopicView.instance_variable_set(:@custom_filters, [])
+        end
+      end
     end
   end