diff --git a/CHANGELOG b/CHANGELOG
index f1346885ab48d055a159202c9570bf13473a2f56..8468c9a7aef7f63002afc06f16ce12a44f1bfa21 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -181,6 +181,7 @@ v 7.4.0
   - Fail harder in the backup script
   - Changes to Slack service structure, only webhook url needed
   - Zen mode for wiki and milestones (Robert Schilling)
+  - API: Add support for editing an existing project (Mika Mäenpää)
   - Move Emoji parsing to html-pipeline-gitlab (Robert Schilling)
   - Font Awesome 4.2 integration (Sullivan Senechal)
   - Add Pushover service integration (Sullivan Senechal)
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 027a8ec2e7f3ab492741f8cce67497d0585a8a2b..d7804689c25e980e7c3724c3c0f53aff0db772f8 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -287,6 +287,31 @@ Parameters:
 - `visibility_level` (optional)
 - `import_url` (optional)
 
+### Edit project
+
+Updates an existing project
+
+```
+PUT /projects/:id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `name` (optional) - project name
+- `path` (optional) - repository name for project
+- `description` (optional) - short project description
+- `default_branch` (optional)
+- `issues_enabled` (optional)
+- `merge_requests_enabled` (optional)
+- `wiki_enabled` (optional)
+- `snippets_enabled` (optional)
+- `public` (optional) - if `true` same as setting visibility_level = 20
+- `visibility_level` (optional)
+
+On success, method returns 200 with the updated project. If parameters are 
+invalid, 400 is returned.
+
 ### Fork project
 
 Forks a project into the user namespace of the authenticated user.
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 5b0c31f1898119afadd4a87b19801d1cdac771d7..d96288bb98260b698abfaa5f15fdea471d53751e 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -200,6 +200,49 @@ module API
         end
       end
 
+      # Update an existing project
+      #
+      # Parameters:
+      #   id (required) - the id of a project
+      #   name (optional) - name of a project
+      #   path (optional) - path of a project
+      #   description (optional) - short project description
+      #   issues_enabled (optional)
+      #   merge_requests_enabled (optional)
+      #   wiki_enabled (optional)
+      #   snippets_enabled (optional)
+      #   public (optional) - if true same as setting visibility_level = 20
+      #   visibility_level (optional) - visibility level of a project
+      # Example Request
+      #   PUT /projects/:id
+      put ':id' do
+        attrs = attributes_for_keys [:name,
+                                     :path,
+                                     :description,
+                                     :default_branch,
+                                     :issues_enabled,
+                                     :merge_requests_enabled,
+                                     :wiki_enabled,
+                                     :snippets_enabled,
+                                     :public,
+                                     :visibility_level]
+        attrs = map_public_to_visibility_level(attrs)
+        authorize_admin_project
+        authorize! :rename_project, user_project if attrs[:name].present?
+        if attrs[:visibility_level].present?
+          authorize! :change_visibility_level, user_project
+        end
+
+        ::Projects::UpdateService.new(user_project,
+                                      current_user, attrs).execute
+
+        if user_project.valid?
+          present user_project, with: Entities::Project
+        else
+          render_validation_error!(user_project)
+        end
+      end
+
       # Remove project
       #
       # Parameters:
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 3098b0f77f99014c4026a14f52e4740d0a5f961e..26d1a8d193ed3449c4970ec4fb3509719826e39d 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 require 'spec_helper'
 
 describe API::API, api: true  do
@@ -12,6 +13,24 @@ describe API::API, api: true  do
   let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
   let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
   let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) }
+  let(:user4) { create(:user) }
+  let(:project3) { create(:project,
+                          name: 'second_project',
+                          path: 'second_project',
+                          creator_id: user.id,
+                          namespace: user.namespace,
+                          merge_requests_enabled: false,
+                          issues_enabled: false, wiki_enabled: false,
+                          snippets_enabled: false, visibility_level: 0) }
+  let(:project_member3) { create(:project_member,
+                                 user: user4,
+                                 project: project3,
+                                 access_level: ProjectMember::MASTER) }
+  let(:project4) { create(:project,
+                          name: 'third_project',
+                          path: 'third_project',
+                          creator_id: user4.id,
+                          namespace: user4.namespace) }
 
   describe "GET /projects" do
     before { project }
@@ -650,6 +669,118 @@ describe API::API, api: true  do
     end
   end
 
+  describe 'PUT /projects/:id̈́' do
+    before { project }
+    before { user }
+    before { user3 }
+    before { user4 }
+    before { project3 }
+    before { project4 }
+    before { project_member3 }
+    before { project_member2 }
+
+    context 'when unauthenticated' do
+      it 'should return authentication error' do
+        project_param = { name: 'bar' }
+        put api("/projects/#{project.id}"), project_param
+        response.status.should == 401
+      end
+    end
+
+    context 'when authenticated as project owner' do
+      it 'should update name' do
+        project_param = { name: 'bar' }
+        put api("/projects/#{project.id}", user), project_param
+        response.status.should == 200
+        project_param.each_pair do |k, v|
+          json_response[k.to_s].should == v
+        end
+      end
+
+      it 'should update visibility_level' do
+        project_param = { visibility_level: 20 }
+        put api("/projects/#{project3.id}", user), project_param
+        response.status.should == 200
+        project_param.each_pair do |k, v|
+          json_response[k.to_s].should == v
+        end
+      end
+
+      it 'should not update name to existing name' do
+        project_param = { name: project3.name }
+        put api("/projects/#{project.id}", user), project_param
+        response.status.should == 400
+        json_response['message']['name'].should == ['has already been taken']
+      end
+
+      it 'should update path & name to existing path & name in different namespace' do
+        project_param = { path: project4.path, name: project4.name }
+        put api("/projects/#{project3.id}", user), project_param
+        response.status.should == 200
+        project_param.each_pair do |k, v|
+          json_response[k.to_s].should == v
+        end
+      end
+    end
+
+    context 'when authenticated as project master' do
+      it 'should update path' do
+        project_param = { path: 'bar' }
+        put api("/projects/#{project3.id}", user4), project_param
+        response.status.should == 200
+        project_param.each_pair do |k, v|
+          json_response[k.to_s].should == v
+        end
+      end
+
+      it 'should update other attributes' do
+        project_param = { issues_enabled: true,
+                          wiki_enabled: true,
+                          snippets_enabled: true,
+                          merge_requests_enabled: true,
+                          description: 'new description' }
+
+        put api("/projects/#{project3.id}", user4), project_param
+        response.status.should == 200
+        project_param.each_pair do |k, v|
+          json_response[k.to_s].should == v
+        end
+      end
+
+      it 'should not update path to existing path' do
+        project_param = { path: project.path }
+        put api("/projects/#{project3.id}", user4), project_param
+        response.status.should == 400
+        json_response['message']['path'].should == ['has already been taken']
+      end
+
+      it 'should not update name' do
+        project_param = { name: 'bar' }
+        put api("/projects/#{project3.id}", user4), project_param
+        response.status.should == 403
+      end
+
+      it 'should not update visibility_level' do
+        project_param = { visibility_level: 20 }
+        put api("/projects/#{project3.id}", user4), project_param
+        response.status.should == 403
+      end
+    end
+
+    context 'when authenticated as project developer' do
+      it 'should not update other attributes' do
+        project_param = { path: 'bar',
+                          issues_enabled: true,
+                          wiki_enabled: true,
+                          snippets_enabled: true,
+                          merge_requests_enabled: true,
+                          description: 'new description' }
+        put api("/projects/#{project.id}", user3), project_param
+        response.status.should == 403
+      end
+    end
+  end
+
   describe "DELETE /projects/:id" do
     context "when authenticated as user" do
       it "should remove project" do