diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb
index 63064c7e70f..20617146cf0 100644
--- a/app/controllers/static_controller.rb
+++ b/app/controllers/static_controller.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
 class StaticController < ApplicationController
-
   skip_before_action :check_xhr, :redirect_to_login_if_required
   skip_before_action :verify_authenticity_token, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset]
   skip_before_action :preload_json, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset]
@@ -11,24 +10,26 @@ class StaticController < ApplicationController
 
   PAGES_WITH_EMAIL_PARAM = ['login', 'password_reset', 'signup']
   MODAL_PAGES = ['password_reset', 'signup']
+  DEFAULT_PAGES = {
+    "faq" => { redirect: "faq_url", topic_id: "guidelines_topic_id" },
+    "tos" => { redirect: "tos_url", topic_id: "tos_topic_id" },
+    "privacy" => { redirect: "privacy_policy_url", topic_id: "privacy_topic_id" },
+  }
+  CUSTOM_PAGES = {} # Add via `#add_topic_static_page` in plugin API
 
   def show
     return redirect_to(path '/') if current_user && (params[:id] == 'login' || params[:id] == 'signup')
+
     if SiteSetting.login_required? && current_user.nil? && ['faq', 'guidelines'].include?(params[:id])
       return redirect_to path('/login')
     end
 
-    map = {
-      "faq" => { redirect: "faq_url", topic_id: "guidelines_topic_id" },
-      "tos" => { redirect: "tos_url", topic_id: "tos_topic_id" },
-      "privacy" => { redirect: "privacy_policy_url", topic_id: "privacy_topic_id" }
-    }
-
+    map = DEFAULT_PAGES.deep_merge(CUSTOM_PAGES)
     @page = params[:id]
 
     if map.has_key?(@page)
       site_setting_key = map[@page][:redirect]
-      url = SiteSetting.get(site_setting_key)
+      url = SiteSetting.get(site_setting_key) if site_setting_key
       return redirect_to(url) if url.present?
     end
 
@@ -39,8 +40,12 @@ class StaticController < ApplicationController
     @page = @page.gsub(/[^a-z0-9\_\-]/, '')
 
     if map.has_key?(@page)
-      @topic = Topic.find_by_id(SiteSetting.get(map[@page][:topic_id]))
+      topic_id = map[@page][:topic_id]
+      topic_id = instance_exec(&topic_id) if topic_id.is_a?(Proc)
+
+      @topic = Topic.find_by_id(SiteSetting.get(topic_id))
       raise Discourse::NotFound unless @topic
+
       title_prefix = if I18n.exists?("js.#{@page}")
         I18n.t("js.#{@page}")
       else
@@ -48,17 +53,16 @@ class StaticController < ApplicationController
       end
       @title = "#{title_prefix} - #{SiteSetting.title}"
       @body = @topic.posts.first.cooked
-      @faq_overriden = !SiteSetting.faq_url.blank?
+      @faq_overridden = !SiteSetting.faq_url.blank?
+
       render :show, layout: !request.xhr?, formats: [:html]
       return
     end
 
-    unless @title.present?
-      @title = if SiteSetting.short_site_description.present?
-        "#{SiteSetting.title} - #{SiteSetting.short_site_description}"
-      else
-        SiteSetting.title
-      end
+    @title = SiteSetting.title
+
+    if SiteSetting.short_site_description.present?
+      @title << " - #{SiteSetting.short_site_description}"
     end
 
     if I18n.exists?("static.#{@page}")
diff --git a/app/views/static/show.html.erb b/app/views/static/show.html.erb
index b8a325f6617..b82fff8fa3b 100644
--- a/app/views/static/show.html.erb
+++ b/app/views/static/show.html.erb
@@ -3,7 +3,7 @@
   <ul class='nav-pills' role='navigation' itemscope itemtype='http://schema.org/SiteNavigationElement'>
     <% unless SiteSetting.login_required? && current_user.nil? %>
       <li class="nav-item-about"><%= link_to t('js.about.simple_title'), about_index_path %></a></li>
-      <% if @faq_overriden %>
+      <% if @faq_overridden %>
         <li class='nav-item-guidelines'><a class='<%= @page == 'faq' ? 'active' : '' %>' href='<%= guidelines_path %>'><%= t 'js.guidelines' %></a></li>
         <li class='nav-item-faq'><a href='<%= faq_path %>'><%= t 'js.faq' %></a></li>
       <% else %>
diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb
index ba526f4631e..1e9526aceda 100644
--- a/lib/plugin/instance.rb
+++ b/lib/plugin/instance.rb
@@ -994,6 +994,22 @@ class Plugin::Instance
     DiscoursePluginRegistry.register_notification_consolidation_plan(plan, self)
   end
 
+  # Allows customizing existing topic-backed static pages, like:
+  # faq, tos, privacy (see: StaticController) The block passed to this
+  # method has to return a SiteSetting name that contains a topic id.
+  #
+  #   add_topic_static_page("faq") do |controller|
+  #     current_user&.locale == "pl" ? "polish_faq_topic_id" : "faq_topic_id"
+  #   end
+  #
+  # You can also add new pages in a plugin, but remember to add a route,
+  # for example:
+  #
+  #   get "contact" => "static#show", id: "contact"
+  def add_topic_static_page(page, options = {}, &blk)
+    StaticController::CUSTOM_PAGES[page] = blk ? { topic_id: blk } : options
+  end
+
   protected
 
   def self.js_path
diff --git a/spec/requests/static_controller_spec.rb b/spec/requests/static_controller_spec.rb
index 3156b2a7ab1..31b4928f5c6 100644
--- a/spec/requests/static_controller_spec.rb
+++ b/spec/requests/static_controller_spec.rb
@@ -277,6 +277,52 @@ describe StaticController do
         expect(response.body).to include("<title>FAQ - Discourse</title>")
       end
     end
+
+    context "plugin api extensions" do
+      after do
+        Rails.application.reload_routes!
+        StaticController::CUSTOM_PAGES.clear
+      end
+
+      it "adds new topic-backed pages" do
+        routes = Proc.new do
+          get "contact" => "static#show", id: "contact"
+        end
+        Discourse::Application.routes.send(:eval_block, routes)
+
+        topic_id = Fabricate(:post, cooked: "contact info").topic_id
+        SiteSetting.setting(:contact_topic_id, topic_id)
+
+        Plugin::Instance.new.add_topic_static_page("contact", topic_id: "contact_topic_id")
+
+        get "/contact"
+
+        expect(response.status).to eq(200)
+        expect(response.body).to include("contact info")
+      end
+
+      it "replaces existing topic-backed pages" do
+        topic_id = Fabricate(:post, cooked: "Regular FAQ").topic_id
+        SiteSetting.setting(:faq_topic_id, topic_id)
+        polish_topic_id = Fabricate(:post, cooked: "Polish FAQ").topic_id
+        SiteSetting.setting(:polish_faq_topic_id, polish_topic_id)
+
+        Plugin::Instance.new.add_topic_static_page("faq") do
+          current_user&.locale == "pl" ? "polish_faq_topic_id" : "faq_topic_id"
+        end
+
+        get "/faq"
+
+        expect(response.status).to eq(200)
+        expect(response.body).to include("Regular FAQ")
+
+        sign_in(Fabricate(:user, locale: "pl"))
+        get "/faq"
+
+        expect(response.status).to eq(200)
+        expect(response.body).to include("Polish FAQ")
+      end
+    end
   end
 
   describe '#enter' do