diff --git a/app/assets/javascripts/categories.js b/app/assets/javascripts/categories.js
new file mode 100644
index 0000000000000000000000000000000000000000..8b766b11ba22e3014b8edfb5729ce1858a42935b
--- /dev/null
+++ b/app/assets/javascripts/categories.js
@@ -0,0 +1,27 @@
+$(() => {
+  $('.js-category-tag-set-select').on('change', ev => {
+    const $tgt = $(ev.target);
+    const tagSetId = $tgt.val();
+    const formGroups = $('.js-category-tags-group');
+    if (tagSetId) {
+      formGroups.each((i, el) => {
+        const $el = $(el);
+        const $caption = $el.find('.js-tags-group-caption');
+        $caption.find('[data-state="absent"]').hide();
+        $caption.find('[data-state="present"]').show();
+
+        $el.find('.js-tag-select').attr('data-tag-set', tagSetId);
+      });
+    }
+    else {
+      formGroups.each((i, el) => {
+        const $el = $(el);
+        const $caption = $el.find('.js-tags-group-caption');
+        $caption.find('[data-state="absent"]').show();
+        $caption.find('[data-state="present"]').hide();
+
+        $el.find('.js-tag-select').attr('data-tag-set', null);
+      });
+    }
+  });
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/tags.js b/app/assets/javascripts/tags.js
index 1439c371fbb44404ba78077aad700a62e59ba74e..3b07c93a3c9a5984ed97a33af79d3c6115e74d90 100644
--- a/app/assets/javascripts/tags.js
+++ b/app/assets/javascripts/tags.js
@@ -1,14 +1,17 @@
 $(() => {
-  $('.js-tag-select').select2({
-    tags: true,
-    ajax: {
-      url: '/tags',
-      data: function (params) {
-        return Object.assign(params, { tag_set: $(this).data('tag-set') });
-      },
-      headers: { 'Accept': 'application/json' },
-      delay: 100,
-      processResults: data => ({results: data.map(t => ({id: t.name, text: t.name}))}),
-    }
+  $('.js-tag-select').each((i, el) => {
+    const $tgt = $(el);
+    $tgt.select2({
+      tags: $tgt.attr('data-create') !== 'false',
+      ajax: {
+        url: '/tags',
+        data: function (params) {
+          return Object.assign(params, { tag_set: $(this).data('tag-set') });
+        },
+        headers: { 'Accept': 'application/json' },
+        delay: 100,
+        processResults: data => ({results: data.map(t => ({id: t.name, text: t.name}))}),
+      }
+    });
   });
 });
\ No newline at end of file
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index e1bd54c0ffea160de3bbed723f6d166219bfc664..62b0a6329f515529a6d3bcfeae4523c016b96516 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -75,7 +75,7 @@ class CategoriesController < ApplicationController
   def category_params
     params.require(:category).permit(:name, :short_wiki, :tag_set_id, :is_homepage, :min_trust_level, :button_text,
                                      :color_code, :min_view_trust_level, :license_id, :sequence, display_post_types: [],
-                                     post_type_ids: [])
+                                     post_type_ids: [], required_tag_ids: [], topic_tag_ids: [])
   end
 
   def verify_view_access
diff --git a/app/models/category.rb b/app/models/category.rb
index 486fcb87cdb1eff8302af486e14df3391e433031..cba36d5c375277c0d5167594751f06bb4cb98096 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -2,6 +2,8 @@ class Category < ApplicationRecord
   include CommunityRelated
 
   has_and_belongs_to_many :post_types
+  has_and_belongs_to_many :required_tags, class_name: 'Tag', join_table: 'categories_required_tags'
+  has_and_belongs_to_many :topic_tags, class_name: 'Tag', join_table: 'categories_topic_tags'
   has_many :posts
   belongs_to :tag_set
   belongs_to :license
diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb
index 9613e9d0cbf9cc64c572d419ba5182430c52640f..95a830f61756bd1b16766c982e6fbc12baaa3226 100644
--- a/app/views/categories/_form.html.erb
+++ b/app/views/categories/_form.html.erb
@@ -31,7 +31,7 @@
     <%= f.label :tag_set_id, 'Tag set', class: 'form-element' %>
     <span class="form-caption">Which tag set may posts in this category draw from?</span>
     <%= f.select :tag_set_id, options_for_select(TagSet.all.map { |ts|  [ts.name, ts.id] }, selected: @category.tag_set_id),
-                 { include_blank: true }, class: 'form-element' %>
+                 { include_blank: true }, class: 'form-element js-category-tag-set-select' %>
   </div>
 
   <div class="form-group">
@@ -90,5 +90,37 @@
     <%= f.number_field :sequence, class: 'form-element' %>
   </div>
 
+  <div class="form-group js-category-tags-group">
+    <%= f.label :required_tag_ids, 'Required tags', class: 'form-element' %>
+    <span class="form-caption js-tags-group-caption">
+      <span data-state="present" style="<%= @category.tag_set.nil? ? 'display: none' : '' %>">
+        Required tags for this category - every post will be required to have one of these tags.
+      </span>
+      <span data-state="absent" style="<%= @category.tag_set.nil? ? '' : 'display: none' %>">
+        Select a tag set first.
+      </span>
+    </span>
+    <%= f.select :required_tag_ids, options_for_select(@category.required_tags.map { |t| [t.name, t.id] },
+                                                       selected: @category.required_tags.pluck(:name)),
+                 { include_blank: true }, multiple: true, class: 'form-element js-tag-select',
+                 data: { tag_set: @category.tag_set&.id, create: 'false' } %>
+  </div>
+
+  <div class="form-group js-category-tags-group">
+    <%= f.label :required_tag_ids, 'Topic tags', class: 'form-element' %>
+    <span class="form-caption js-tags-group-caption">
+      <span data-state="present" style="<%= @category.tag_set.nil? ? 'display: none' : '' %>">
+        Tags that will be highlighted as the most important tag on a question.
+      </span>
+      <span data-state="absent" style="<%= @category.tag_set.nil? ? '' : 'display: none' %>">
+        Select a tag set first.
+      </span>
+    </span>
+    <%= f.select :topic_tag_ids, options_for_select(@category.topic_tags.map { |t| [t.name, t.id] },
+                                                       selected: @category.topic_tags.pluck(:name)),
+                 { include_blank: true }, multiple: true, class: 'form-element js-tag-select',
+                 data: { tag_set: @category.tag_set&.id, create: 'false' } %>
+  </div>
+
   <%= f.submit 'Save', class: 'button is-filled' %>
 <% end %>
diff --git a/db/migrate/20200508115752_add_category_tag_join_tables.rb b/db/migrate/20200508115752_add_category_tag_join_tables.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f79752d7c334bd55acda2dd6c834f5305deab2e7
--- /dev/null
+++ b/db/migrate/20200508115752_add_category_tag_join_tables.rb
@@ -0,0 +1,13 @@
+class AddCategoryTagJoinTables < ActiveRecord::Migration[5.2]
+  def change
+    create_table :categories_required_tags, id: false, primary_key: [:category_id, :tag_id] do |t|
+      t.bigint :category_id
+      t.bigint :tag_id
+    end
+
+    create_table :categories_topic_tags, id: false, primary_key: [:category_id, :tag_id] do |t|
+      t.bigint :category_id
+      t.bigint :tag_id
+    end
+  end
+end