diff --git a/CHANGELOG b/CHANGELOG
index f9cd92a83c6ec2f72eaf44ec9ca52444ee57e79e..ac44c0db2ea8ba71df79104b57e223d199167779 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -15,6 +15,7 @@ v 7.3.0
   - API: filter issues by labels (Julien Bianchi)
   - Add system hook for ssh key changes
   - Add blob permalink link (Ciro Santilli)
+  - Create annotated tags through UI and API (Sean Edge)
 
 v 7.2.0
   - Explore page
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index b84c497131a86d437c1b8f421bf59278ec5a20cb..86788b9963b19104c9024b674102a3a668e6ba32 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -14,7 +14,8 @@ class Projects::TagsController < Projects::ApplicationController
 
   def create
     result = CreateTagService.new.execute(@project, params[:tag_name],
-                                          params[:ref], current_user)
+                                          params[:ref], params[:message],
+                                          current_user)
     if result[:status] == :success
       @tag = result[:tag]
       redirect_to project_tags_path(@project)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e970c449a7342b01b69d1ce8236514c8f0ed063f..9dd8603621f0c1c626275583f434b4eba0a1420c 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -64,10 +64,10 @@ class Repository
     gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
   end
 
-  def add_tag(tag_name, ref)
+  def add_tag(tag_name, ref, message = nil)
     Rails.cache.delete(cache_key(:tag_names))
 
-    gitlab_shell.add_tag(path_with_namespace, tag_name, ref)
+    gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
   end
 
   def rm_branch(branch_name)
diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb
index 6869acbe467efe31522a8069d26c6bb4f8a240c1..3716abd4b2b6edd77cc7371ced575e74c4024293 100644
--- a/app/services/create_tag_service.rb
+++ b/app/services/create_tag_service.rb
@@ -1,5 +1,5 @@
 class CreateTagService
-  def execute(project, tag_name, ref, current_user)
+  def execute(project, tag_name, ref, message, current_user)
     valid_tag = Gitlab::GitRefValidator.validate(tag_name)
     if valid_tag == false
       return error('Tag name invalid')
@@ -11,7 +11,11 @@ class CreateTagService
       return error('Tag already exists')
     end
 
-    repository.add_tag(tag_name, ref)
+    if message
+      message.gsub!(/^\s+|\s+$/, '')
+    end
+
+    repository.add_tag(tag_name, ref, message)
     new_tag = repository.find_tag(tag_name)
 
     if new_tag
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index f3a34d37df5ec0c7d73c115f9fdb57b56105c4db..45ee61caf686c4d263d89545ebfe6062aa9cee07 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -15,6 +15,11 @@
     .col-sm-10
       = text_field_tag :ref, params[:ref], placeholder: 'master', required: true, tabindex: 2, class: 'form-control'
       .light Branch name or commit SHA
+  .form-group
+    = label_tag :message, 'Message', class: 'control-label'
+    .col-sm-10
+      = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
+      .light (Optional) Entering a message will create an annotated tag.
   .form-actions
     = submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3
     = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index c9f6a45c3472c9fe327081d247fa252b076a2146..a412f60c0d9769b9568f6a1abbfac519a1fb9d4b 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -50,6 +50,7 @@ Parameters:
 - `id` (required) - The ID of a project
 - `tag_name` (required) - The name of a tag
 - `ref` (required) - Create tag using commit SHA, another tag name, or branch name.
+- `message` (optional) - Creates annotated tag.
 
 ```json
 [
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index a3773d2c59333676a43737e085116f11760770f5..ce89177ef6523cfd3722e97fcd5a0ecfe5f3f0d2 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -32,12 +32,15 @@ module API
       #   id (required) - The ID of a project
       #   tag_name (required) - The name of the tag
       #   ref (required) - Create tag from commit sha or branch
+      #   message (optional) - Specifying a message creates an annotated tag.
       # Example Request:
       #   POST /projects/:id/repository/tags
       post ':id/repository/tags' do
         authorize_push_project
+        message = params[:message] || nil
         result = CreateTagService.new.execute(user_project, params[:tag_name],
-                                              params[:ref], current_user)
+                                              params[:ref], message,
+                                              current_user)
         if result[:status] == :success
           present result[:tag],
                   with: Entities::RepoObject,
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 53bff3037e5b8f37f4e1bd5450777cd9c714b681..907373ab991047554609445fc156c76808262a82 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -107,12 +107,17 @@ module Gitlab
     # path - project path with namespace
     # tag_name - new tag name
     # ref - HEAD for new tag
+    # message - optional message for tag (annotated tag)
     #
     # Ex.
     #   add_tag("gitlab/gitlab-ci", "v4.0", "master")
+    #   add_tag("gitlab/gitlab-ci", "v4.0", "master", "message")
     #
-    def add_tag(path, tag_name, ref)
-      system "#{gitlab_shell_path}/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref
+    def add_tag(path, tag_name, ref, message = nil)
+      cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git
+               #{tag_name} #{ref})
+      cmd << message unless message.nil? || message.empty?
+      system *cmd
     end
 
     # Remove repository tag
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index ffcdbc4255ee9e986856d32eebd4f1fec1375657..3ada945ae057fe38cfc4ed9d6115bec74413fc38 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -23,12 +23,29 @@ describe API::API, api: true  do
   end
 
   describe 'POST /projects/:id/repository/tags' do
-    it 'should create a new tag' do
-      post api("/projects/#{project.id}/repository/tags", user),
-           tag_name: 'v2.0.0',
-           ref: 'master'
-      response.status.should == 201
-      json_response['name'].should == 'v2.0.0'
+    context 'lightweight tags' do
+      it 'should create a new tag' do
+        post api("/projects/#{project.id}/repository/tags", user),
+             tag_name: 'v1.0.0',
+             ref: 'master'
+
+        response.status.should == 201
+        json_response['name'].should == 'v1.0.0'
+      end
+    end
+    context 'annotated tag' do
+      it 'should create a new annotated tag' do
+        post api("/projects/#{project.id}/repository/tags", user),
+             tag_name: 'v1.0.0',
+             ref: 'master',
+             message: 'tag message'
+
+        response.status.should == 201
+        json_response['name'].should == 'v1.0.0'
+        # The message is not part of the JSON response.
+        # Additional changes to the gitlab_git gem may be required.
+        # json_response['message'].should == 'tag message'
+      end
     end
 
     it 'should deny for user without push access' do