diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index ca6dffe1cc57ac1f755547d9adc163f0826579f0..ffea712a833a59c440115a306444a558b6c1aea4 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -5,10 +5,12 @@ module SnippetsActions
   end
 
   def raw
+    disposition = params[:inline] == 'false' ? 'attachment' : 'inline'
+
     send_data(
       convert_line_endings(@snippet.content),
       type: 'text/plain; charset=utf-8',
-      disposition: 'inline',
+      disposition: disposition,
       filename: @snippet.sanitized_file_name
     )
   end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index e338db8353b2f1e22fb8c8c8597ddb74fd18a3e6..da1ae9a34d98020f5a83c7a64e50e175c0dda460 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -6,10 +6,10 @@ class SnippetsController < ApplicationController
   include MarkdownPreview
   include RendersBlob
 
-  before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
+  before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
 
   # Allow read snippet
-  before_action :authorize_read_snippet!, only: [:show, :raw, :download]
+  before_action :authorize_read_snippet!, only: [:show, :raw]
 
   # Allow modify snippet
   before_action :authorize_update_snippet!, only: [:edit, :update]
@@ -17,7 +17,7 @@ class SnippetsController < ApplicationController
   # Allow destroy snippet
   before_action :authorize_admin_snippet!, only: [:destroy]
 
-  skip_before_action :authenticate_user!, only: [:index, :show, :raw, :download]
+  skip_before_action :authenticate_user!, only: [:index, :show, :raw]
 
   layout 'snippets'
   respond_to :html
@@ -89,14 +89,6 @@ class SnippetsController < ApplicationController
     redirect_to snippets_path
   end
 
-  def download
-    send_data(
-      convert_line_endings(@snippet.content),
-      type: 'text/plain; charset=utf-8',
-      filename: @snippet.sanitized_file_name
-    )
-  end
-
   def preview_markdown
     render_markdown_preview(params[:text], skip_project_check: true)
   end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 979264c94217c1b1ce4a9d33403af196c39941ec..2fd64b3441e4c33b9110f8034817c8c07a452c2e 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -8,6 +8,14 @@ module SnippetsHelper
     end
   end
 
+  def download_snippet_path(snippet)
+    if snippet.project_id
+      raw_namespace_project_snippet_path(@project.namespace, @project, snippet, inline: false)
+    else
+      raw_snippet_path(snippet, inline: false)
+    end
+  end
+
   # Return the path of a snippets index for a user or for a project
   #
   # @returns String, path to snippet index
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index 67d186e2874a4ccc5cc9875981f6cbbd7b25e6e8..9bcb4544b975d11e1fffbd452304637ac398f409 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -18,7 +18,6 @@
       = copy_blob_source_button(blob)
       = open_raw_blob_button(blob)
 
-      - if defined?(download_path) && download_path
-        = link_to icon('download'), download_path, class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
+      = link_to icon('download'), download_snippet_path(@snippet), target: '_blank', class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
 
 = render 'projects/blob/content', blob: blob
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index abd0d5e9b73e6fd27c8df993a5f9b36b01b48de8..98287cba5b47c2e57b6df129d48fa1b8422f7fef 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -3,7 +3,7 @@
 = render 'shared/snippets/header'
 
 %article.file-holder.snippet-file-content
-  = render 'shared/snippets/blob', download_path: download_snippet_path(@snippet)
+  = render 'shared/snippets/blob'
 
 .row-content-block.top-block.content-component-block
   = render 'award_emoji/awards_block', awardable: @snippet, inline: true
diff --git a/changelogs/unreleased/dm-snippet-download-button.yml b/changelogs/unreleased/dm-snippet-download-button.yml
new file mode 100644
index 0000000000000000000000000000000000000000..09ece1e7f98440b42ae9dc80d2a08c094ce47e36
--- /dev/null
+++ b/changelogs/unreleased/dm-snippet-download-button.yml
@@ -0,0 +1,4 @@
+---
+title: Add download button to project snippets
+merge_request:
+author:
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 71fc84a7a12cb85f81bc7be02490bb96e9683669..aaad503eba4049272ae5d62c670c5b70b4aa247b 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -44,7 +44,7 @@ constraints(ProjectUrlConstrainer.new) do
 
       resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
         member do
-          get 'raw'
+          get :raw
           post :mark_as_spam
         end
       end
diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb
index bced4a7975efcab6f99a90b33d2acf6e98ba29ce..dae83734fe67cd3ba5262810c6ed12ab2c0c8244 100644
--- a/config/routes/snippets.rb
+++ b/config/routes/snippets.rb
@@ -1,7 +1,6 @@
 resources :snippets, concerns: :awardable do
   member do
-    get 'raw'
-    get 'download'
+    get :raw
     post :mark_as_spam
     post :preview_markdown
   end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 234f3edd3d8c13eaf1d5fc3d11d065986a31ba8d..41cd5bdcdd8197b00dc51122532e5fc046579f19 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -350,144 +350,138 @@ describe SnippetsController do
     end
   end
 
-  %w(raw download).each do |action|
-    describe "GET #{action}" do
-      context 'when the personal snippet is private' do
-        let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
+  describe "GET #raw" do
+    context 'when the personal snippet is private' do
+      let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
 
-        context 'when signed in' do
-          before do
-            sign_in(user)
-          end
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
 
-          context 'when signed in user is not the author' do
-            let(:other_author) { create(:author) }
-            let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
+        context 'when signed in user is not the author' do
+          let(:other_author) { create(:author) }
+          let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
 
-            it 'responds with status 404' do
-              get action, id: other_personal_snippet.to_param
+          it 'responds with status 404' do
+            get :raw, id: other_personal_snippet.to_param
 
-              expect(response).to have_http_status(404)
-            end
+            expect(response).to have_http_status(404)
           end
+        end
 
-          context 'when signed in user is the author' do
-            before { get action, id: personal_snippet.to_param }
+        context 'when signed in user is the author' do
+          before { get :raw, id: personal_snippet.to_param }
 
-            it 'responds with status 200' do
-              expect(assigns(:snippet)).to eq(personal_snippet)
-              expect(response).to have_http_status(200)
-            end
+          it 'responds with status 200' do
+            expect(assigns(:snippet)).to eq(personal_snippet)
+            expect(response).to have_http_status(200)
+          end
 
-            it 'has expected headers' do
-              expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
+          it 'has expected headers' do
+            expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
 
-              if action == :download
-                expect(response.header['Content-Disposition']).to match(/attachment/)
-              elsif action == :raw
-                expect(response.header['Content-Disposition']).to match(/inline/)
-              end
-            end
+            expect(response.header['Content-Disposition']).to match(/inline/)
           end
         end
+      end
 
-        context 'when not signed in' do
-          it 'redirects to the sign in page' do
-            get action, id: personal_snippet.to_param
+      context 'when not signed in' do
+        it 'redirects to the sign in page' do
+          get :raw, id: personal_snippet.to_param
 
-            expect(response).to redirect_to(new_user_session_path)
-          end
+          expect(response).to redirect_to(new_user_session_path)
         end
       end
+    end
 
-      context 'when the personal snippet is internal' do
-        let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
+    context 'when the personal snippet is internal' do
+      let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
 
-        context 'when signed in' do
-          before do
-            sign_in(user)
-          end
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
 
-          it 'responds with status 200' do
-            get action, id: personal_snippet.to_param
+        it 'responds with status 200' do
+          get :raw, id: personal_snippet.to_param
 
-            expect(assigns(:snippet)).to eq(personal_snippet)
-            expect(response).to have_http_status(200)
-          end
+          expect(assigns(:snippet)).to eq(personal_snippet)
+          expect(response).to have_http_status(200)
         end
+      end
 
-        context 'when not signed in' do
-          it 'redirects to the sign in page' do
-            get action, id: personal_snippet.to_param
+      context 'when not signed in' do
+        it 'redirects to the sign in page' do
+          get :raw, id: personal_snippet.to_param
 
-            expect(response).to redirect_to(new_user_session_path)
-          end
+          expect(response).to redirect_to(new_user_session_path)
         end
       end
+    end
 
-      context 'when the personal snippet is public' do
-        let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
+    context 'when the personal snippet is public' do
+      let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
 
-        context 'when signed in' do
-          before do
-            sign_in(user)
-          end
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
 
-          it 'responds with status 200' do
-            get action, id: personal_snippet.to_param
+        it 'responds with status 200' do
+          get :raw, id: personal_snippet.to_param
 
-            expect(assigns(:snippet)).to eq(personal_snippet)
-            expect(response).to have_http_status(200)
-          end
+          expect(assigns(:snippet)).to eq(personal_snippet)
+          expect(response).to have_http_status(200)
+        end
 
-          context 'CRLF line ending' do
-            let(:personal_snippet) do
-              create(:personal_snippet, :public, author: user, content: "first line\r\nsecond line\r\nthird line")
-            end
+        context 'CRLF line ending' do
+          let(:personal_snippet) do
+            create(:personal_snippet, :public, author: user, content: "first line\r\nsecond line\r\nthird line")
+          end
 
-            it 'returns LF line endings by default' do
-              get action, id: personal_snippet.to_param
+          it 'returns LF line endings by default' do
+            get :raw, id: personal_snippet.to_param
 
-              expect(response.body).to eq("first line\nsecond line\nthird line")
-            end
+            expect(response.body).to eq("first line\nsecond line\nthird line")
+          end
 
-            it 'does not convert line endings when parameter present' do
-              get action, id: personal_snippet.to_param, line_ending: :raw
+          it 'does not convert line endings when parameter present' do
+            get :raw, id: personal_snippet.to_param, line_ending: :raw
 
-              expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
-            end
+            expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
           end
         end
+      end
 
-        context 'when not signed in' do
-          it 'responds with status 200' do
-            get action, id: personal_snippet.to_param
+      context 'when not signed in' do
+        it 'responds with status 200' do
+          get :raw, id: personal_snippet.to_param
 
-            expect(assigns(:snippet)).to eq(personal_snippet)
-            expect(response).to have_http_status(200)
-          end
+          expect(assigns(:snippet)).to eq(personal_snippet)
+          expect(response).to have_http_status(200)
         end
       end
+    end
 
-      context 'when the personal snippet does not exist' do
-        context 'when signed in' do
-          before do
-            sign_in(user)
-          end
+    context 'when the personal snippet does not exist' do
+      context 'when signed in' do
+        before do
+          sign_in(user)
+        end
 
-          it 'responds with status 404' do
-            get action, id: 'doesntexist'
+        it 'responds with status 404' do
+          get :raw, id: 'doesntexist'
 
-            expect(response).to have_http_status(404)
-          end
+          expect(response).to have_http_status(404)
         end
+      end
 
-        context 'when not signed in' do
-          it 'responds with status 404' do
-            get action, id: 'doesntexist'
+      context 'when not signed in' do
+        it 'responds with status 404' do
+          get :raw, id: 'doesntexist'
 
-            expect(response).to have_http_status(404)
-          end
+          expect(response).to have_http_status(404)
         end
       end
     end
diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb
index 7eb1210e3079870137a167a5ef8e4ba94e44d588..cedf3778c7ec3e8807ba2ea54e7da1e43ebffb3b 100644
--- a/spec/features/projects/snippets/show_spec.rb
+++ b/spec/features/projects/snippets/show_spec.rb
@@ -30,6 +30,12 @@ feature 'Project snippet', :js, feature: true do
 
         # shows an enabled copy button
         expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
+
+        # shows a raw button
+        expect(page).to have_link('Open raw')
+
+        # shows a download button
+        expect(page).to have_link('Download')
       end
     end
   end
@@ -59,6 +65,12 @@ feature 'Project snippet', :js, feature: true do
 
           # shows a disabled copy button
           expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
+
+          # shows a raw button
+          expect(page).to have_link('Open raw')
+
+          # shows a download button
+          expect(page).to have_link('Download')
         end
       end
 
diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb
index cebcba6a230771dab02b267a31777ea3ee54c0fa..e36cf547f803e1a044b0e20825d9652eb5c6e278 100644
--- a/spec/features/snippets/show_spec.rb
+++ b/spec/features/snippets/show_spec.rb
@@ -24,6 +24,12 @@ feature 'Snippet', :js, feature: true do
 
         # shows an enabled copy button
         expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
+
+        # shows a raw button
+        expect(page).to have_link('Open raw')
+
+        # shows a download button
+        expect(page).to have_link('Download')
       end
     end
   end
@@ -53,6 +59,12 @@ feature 'Snippet', :js, feature: true do
 
           # shows a disabled copy button
           expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
+
+          # shows a raw button
+          expect(page).to have_link('Open raw')
+
+          # shows a download button
+          expect(page).to have_link('Download')
         end
       end