diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index aba4ed97d28d0fa8855e4d27859f4b50e7653816..f1e6dac20a06345642f6d5b5e0b0f7e923afe973 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -1,8 +1,8 @@
 class TagsController < ApplicationController
-  before_action :authenticate_user!, only: [:edit, :update, :rename, :merge, :select_merge]
+  before_action :authenticate_user!, only: [:new, :create, :edit, :update, :rename, :merge, :select_merge]
   before_action :set_category, except: [:index]
   before_action :set_tag, only: [:show, :edit, :update, :children, :rename, :merge, :select_merge, :nuke, :nuke_warning]
-  before_action :verify_moderator, only: [:rename, :merge, :select_merge]
+  before_action :verify_moderator, only: [:new, :create, :rename, :merge, :select_merge]
   before_action :verify_admin, only: [:nuke, :nuke_warning]
 
   def index
@@ -59,6 +59,21 @@ class TagsController < ApplicationController
     end
   end
 
+  def new
+    @tag = Tag.new
+  end
+
+  def create
+    @tag = Tag.new(tag_params.merge(tag_set_id: @category.tag_set.id))
+    if @tag.save
+      flash[:danger] = nil
+      redirect_to tag_path(id: @category.id, tag_id: @tag.id)
+    else
+      flash[:danger] = @tag.errors.full_messages.join(', ')
+      render :new, status: :bad_request
+    end
+  end
+
   def edit
     check_your_privilege('edit_tags', nil, true)
   end
@@ -67,7 +82,7 @@ class TagsController < ApplicationController
     return unless check_your_privilege('edit_tags', nil, true)
 
     wiki_md = params[:tag][:wiki_markdown]
-    if @tag.update(tag_params.merge(wiki: wiki_md.present? ? helpers.render_markdown(wiki_md) : nil))
+    if @tag.update(tag_params.merge(wiki: wiki_md.present? ? helpers.render_markdown(wiki_md) : nil).except(:name))
       redirect_to tag_path(id: @category.id, tag_id: @tag.id)
     else
       render :edit, status: :bad_request
@@ -177,7 +192,7 @@ class TagsController < ApplicationController
   end
 
   def tag_params
-    params.require(:tag).permit(:excerpt, :wiki_markdown, :parent_id)
+    params.require(:tag).permit(:excerpt, :wiki_markdown, :parent_id, :name)
   end
 
   def exec(sql_array)
diff --git a/app/views/tags/_form.html.erb b/app/views/tags/_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f88e1d6d4ec6d83d59d6ffc398ba75858b4e8de3
--- /dev/null
+++ b/app/views/tags/_form.html.erb
@@ -0,0 +1,54 @@
+<%= render 'posts/markdown_script' %>
+
+<% if @tag.errors.any? %>
+  <div class="notice is-danger">
+    There were some errors while saving this tag:
+
+    <ul>
+      <% @tag.errors.full_messages.each do |msg| %>
+        <li><%= msg %></li>
+      <% end %>
+    </ul>
+  </div>
+<% end %>
+
+<%= form_for @tag, url: submit_path do |f| %>
+  <% if submit_path == create_tag_path %>
+    <div class="form-group">
+      <%= f.label :name, 'Name', class: 'form-element' %>
+      <span class="form-caption">
+        Name of the tag
+      </span>
+      <%= f.text_field :name, class: 'form-element' %>
+    </div>
+  <% end %>
+
+  <div class="form-group">
+    <%= f.label :parent_id, 'Parent tag', class: 'form-element' %>
+    <span class="form-caption">
+      Optional. Select a parent tag to make this part of a tag hierarchy.
+    </span>
+    <%= f.select :parent_id, options_for_select(@tag.parent.present? ? [[@tag.parent.name, @tag.parent_id]] : [],
+                                                selected: @tag.parent.present? ? @tag.parent_id : nil),
+                 { include_blank: true }, class: "form-element js-tag-select",
+                 data: { tag_set: @category.tag_set_id, use_ids: true, placeholder: "None" } %>
+  </div>
+
+  <div class="form-group">
+    <%= f.label :excerpt, 'Usage guidance', class: 'form-element' %>
+    <span class="form-caption">
+      Short usage guidance for this tag. Will be cut off at 120 characters in the tags list, but displayed in full on
+      the tag page.
+    </span>
+    <%= f.text_area :excerpt, class: 'form-element js-tag-excerpt', rows: 3 %>
+    <span class="has-float-right has-font-size-caption js-character-count"
+          data-target=".js-tag-excerpt" data-max="600">0 / 600</span>
+  </div>
+
+  <%= render 'shared/body_field', f: f, field_name: :wiki_markdown, field_label: 'Wiki', post: @tag do %>
+    Full usage guidance and any other information you want people to know about this tag.
+  <% end %>
+  <div class="post-preview"></div>
+
+  <%= f.submit 'Save', class: 'button is-filled' %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/tags/category.html.erb b/app/views/tags/category.html.erb
index d783816d7a3d15dc4003fd9a601c18396c44f980..a3119f65f319489ff6a1b513d9b2826e7fb56a03 100644
--- a/app/views/tags/category.html.erb
+++ b/app/views/tags/category.html.erb
@@ -2,6 +2,10 @@
 
 <h1>Tags used in <%= @category.name %></h1>
 
+<% if current_user&.is_moderator %>
+  <%= link_to 'New', new_tag_path(id: @category.id), class: 'button is-muted is-outlined' %>
+<% end %>
+
 <%= form_tag category_tags_path(@category), method: :get, class: 'form-inline' do %>
   <div class="form-group-horizontal">
     <div class="form-group">
diff --git a/app/views/tags/edit.html.erb b/app/views/tags/edit.html.erb
index 93b6ec10eda409acaca8e8567df6ddce9ee1b527..ee785c700c59d13003cb86383846067f0026a7ef 100644
--- a/app/views/tags/edit.html.erb
+++ b/app/views/tags/edit.html.erb
@@ -1,50 +1,7 @@
 <% content_for :title, 'Edit tag' %>
 
-<%= render 'posts/markdown_script' %>
-
 <h1>
   Edit <span class="<%= @classes %> is-large"><%= @tag.name %></span>
 </h1>
 
-<% if @tag.errors.any? %>
-  <div class="notice is-danger">
-    There were some errors while saving this tag:
-
-    <ul>
-      <% @tag.errors.full_messages.each do |msg| %>
-        <li><%= msg %></li>
-      <% end %>
-    </ul>
-  </div>
-<% end %>
-
-<%= form_for @tag, url: update_tag_path(id: @category.id, tag_id: @tag.id) do |f| %>
-  <div class="form-group">
-    <%= f.label :parent_id, 'Parent tag', class: 'form-element' %>
-    <span class="form-caption">
-      Optional. Select a parent tag to make this part of a tag hierarchy.
-    </span>
-    <%= f.select :parent_id, options_for_select(@tag.parent.present? ? [[@tag.parent.name, @tag.parent_id]] : [],
-                                                selected: @tag.parent.present? ? @tag.parent_id : nil),
-                 { include_blank: true }, class: "form-element js-tag-select",
-                 data: { tag_set: @category.tag_set_id, use_ids: true, placeholder: "None" } %>
-  </div>
-
-  <div class="form-group">
-    <%= f.label :excerpt, 'Usage guidance', class: 'form-element' %>
-    <span class="form-caption">
-      Short usage guidance for this tag. Will be cut off at 120 characters in the tags list, but displayed in full on
-      the tag page.
-    </span>
-    <%= f.text_area :excerpt, class: 'form-element js-tag-excerpt', rows: 3 %>
-    <span class="has-float-right has-font-size-caption js-character-count"
-          data-target=".js-tag-excerpt" data-max="600">0 / 600</span>
-  </div>
-
-  <%= render 'shared/body_field', f: f, field_name: :wiki_markdown, field_label: 'Wiki', post: @tag do %>
-    Full usage guidance and any other information you want people to know about this tag.
-  <% end %>
-  <div class="post-preview"></div>
-
-  <%= f.submit 'Save', class: 'button is-filled' %>
-<% end %>
\ No newline at end of file
+<%= render 'form', submit_path: update_tag_path(id: @category.id, tag_id: @tag.id) %>
\ No newline at end of file
diff --git a/app/views/tags/new.html.erb b/app/views/tags/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..1dfb8c674a1ca6982200c0ac26057fa86ca9188d
--- /dev/null
+++ b/app/views/tags/new.html.erb
@@ -0,0 +1,7 @@
+<% content_for :title, 'Create tag' %>
+
+<h1>
+  Create <span class="<%= @classes %> is-large">Tag</span>
+</h1>
+
+<%= render 'form', submit_path: create_tag_path %>
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index a23af01425039e24739445c1894a09778da1376c..694164c6de56abfc142cac41545eee02815b9a05 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -246,6 +246,8 @@ Rails.application.routes.draw do
     get    ':id/types',                    to: 'categories#post_types', as: :category_post_types
     get    ':id/feed',                     to: 'categories#rss_feed', as: :category_feed
     get    ':id/tags',                     to: 'tags#category', as: :category_tags
+    get    ':id/tags/new',                 to: 'tags#new', as: :new_tag
+    post   ':id/tags/new',                 to: 'tags#create', as: :create_tag
     get    ':id/tags/:tag_id',             to: 'tags#show', as: :tag
     get    ':id/tags/:tag_id/children',    to: 'tags#children', as: :tag_children
     get    ':id/tags/:tag_id/edit',        to: 'tags#edit', as: :edit_tag