diff --git a/app/assets/javascripts/discourse/controllers/topic_bulk_actions_controller.js b/app/assets/javascripts/discourse/controllers/topic_bulk_actions_controller.js
index 7d24571b0..37245d25a 100644
--- a/app/assets/javascripts/discourse/controllers/topic_bulk_actions_controller.js
+++ b/app/assets/javascripts/discourse/controllers/topic_bulk_actions_controller.js
@@ -10,5 +10,40 @@
 Discourse.TopicBulkActionsController = Ember.ArrayController.extend(Discourse.ModalFunctionality, {
   onShow: function() {
     this.set('controllers.modal.modalClass', 'topic-bulk-actions-modal');
+  },
+
+  perform: function(operation) {
+    this.set('loading', true);
+
+    var self = this,
+        topics = this.get('model');
+    return Discourse.Topic.bulkOperation(this.get('model'), operation).then(function(result) {
+      self.set('loading', false);
+      if (result && result.topic_ids) {
+        return result.topic_ids.map(function (t) {
+          return topics.findBy('id', t);
+        });
+      }
+      return result;
+    }).catch(function() {
+      self.set('loading', false);
+    });
+  },
+
+  actions: {
+    showChangeCategory: function() {
+      this.send('changeBulkTemplate', 'modal/bulk_change_category');
+    },
+
+    changeCategory: function() {
+      var category = Discourse.Category.findById(parseInt(this.get('newCategoryId'), 10)),
+          self = this;
+      this.perform({type: 'change_category', category_id: this.get('newCategoryId')}).then(function(topics) {
+        topics.forEach(function(t) {
+          t.set('category', category);
+        });
+        self.send('closeModal');
+      });
+    }
   }
 });
diff --git a/app/assets/javascripts/discourse/models/topic.js b/app/assets/javascripts/discourse/models/topic.js
index 3a0cefa1b..5513e4c1a 100644
--- a/app/assets/javascripts/discourse/models/topic.js
+++ b/app/assets/javascripts/discourse/models/topic.js
@@ -382,6 +382,16 @@ Discourse.Topic.reopenClass({
       promise.reject(new Error("error moving posts topic"));
     });
     return promise;
+  },
+
+  bulkOperation: function(topics, operation) {
+    return Discourse.ajax("/topics/bulk", {
+      type: 'PUT',
+      data: {
+        topic_ids: topics.map(function(t) { return t.get('id'); }),
+        operation: operation
+      }
+    });
   }
 
 });
diff --git a/app/assets/javascripts/discourse/routes/discovery_route.js b/app/assets/javascripts/discourse/routes/discovery_route.js
index 6fbf9e8d8..d83f0378e 100644
--- a/app/assets/javascripts/discourse/routes/discovery_route.js
+++ b/app/assets/javascripts/discourse/routes/discovery_route.js
@@ -37,9 +37,14 @@ Discourse.DiscoveryRoute = Discourse.Route.extend({
       });
     },
 
+    changeBulkTemplate: function(w) {
+      this.render(w, {into: 'topicBulkActions', outlet: 'bulkOutlet', controller: 'topicBulkActions'});
+    },
+
     showBulkActions: function() {
       var selected = this.controllerFor('discoveryTopics').get('selected');
       Discourse.Route.showModal(this, 'topicBulkActions', selected);
+      this.send('changeBulkTemplate', 'modal/bulk_actions_buttons');
     }
   }
 });
diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.js.handlebars b/app/assets/javascripts/discourse/templates/discovery/topics.js.handlebars
index c23fcd049..87b31ce47 100644
--- a/app/assets/javascripts/discourse/templates/discovery/topics.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/discovery/topics.js.handlebars
@@ -11,7 +11,7 @@
       <tr>
         <th>
           {{#if canBulkSelect}}
-          <button class='btn bulk-select' {{action toggleBulkSelect}} title="{{i18n topics.toggle_bulk_select}}"><i class='fa fa-list'></i></button>
+          <button class='btn bulk-select' {{action toggleBulkSelect}} title="{{i18n topics.bulk.toggle}}"><i class='fa fa-list'></i></button>
           {{/if}}
         </th>
         {{#sortable-heading sortBy="default" sortOrder=sortOrder}}
diff --git a/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars b/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars
index d02d7d23e..0f5281f80 100644
--- a/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/list/topic_list_item.js.handlebars
@@ -37,7 +37,7 @@
 </td>
 
 {{#unless hideCategory}}
-<td class='category'>{{categoryLink category}}</td>
+<td class='category'>{{boundCategoryLink category}}</td>
 {{/unless}}
 
 <td class='posters'>
diff --git a/app/assets/javascripts/discourse/templates/modal/bulk_actions_buttons.js.handlebars b/app/assets/javascripts/discourse/templates/modal/bulk_actions_buttons.js.handlebars
new file mode 100644
index 000000000..88c8763eb
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/modal/bulk_actions_buttons.js.handlebars
@@ -0,0 +1 @@
+<button class='btn' {{action showChangeCategory}}>{{i18n topics.bulk.change_category}}</button>
diff --git a/app/assets/javascripts/discourse/templates/modal/bulk_change_category.js.handlebars b/app/assets/javascripts/discourse/templates/modal/bulk_change_category.js.handlebars
new file mode 100644
index 000000000..6a5fe566a
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/modal/bulk_change_category.js.handlebars
@@ -0,0 +1,10 @@
+<p>Choose the new category for the topics:</p>
+
+<p>{{categoryChooser value=newCategoryId}}</p>
+
+{{#if loading}}
+  <div class='loading'>{{i18n loading}}</div>
+{{else}}
+  <button class='btn' {{action changeCategory}}>Change Category</button>
+{{/if}}
+
diff --git a/app/assets/javascripts/discourse/templates/modal/topic_bulk_actions.js.handlebars b/app/assets/javascripts/discourse/templates/modal/topic_bulk_actions.js.handlebars
index e0368634d..92d3a03e6 100644
--- a/app/assets/javascripts/discourse/templates/modal/topic_bulk_actions.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/modal/topic_bulk_actions.js.handlebars
@@ -1,7 +1,7 @@
 <div class='modal-body'>
 
-  <p>{{{i18n topics.selected count=length}}}</p>
+  <p>{{{i18n topics.bulk.selected count=length}}}</p>
 
-  <button class='btn'>Change Category</button>
+  {{outlet bulkOutlet}}
 
 </div>
diff --git a/app/assets/javascripts/discourse/views/topic_bulk_actions_view.js b/app/assets/javascripts/discourse/views/topic_bulk_actions_view.js
index 53eee4ba5..a72348415 100644
--- a/app/assets/javascripts/discourse/views/topic_bulk_actions_view.js
+++ b/app/assets/javascripts/discourse/views/topic_bulk_actions_view.js
@@ -8,5 +8,5 @@
 **/
 Discourse.TopicBulkActionsView = Discourse.ModalBodyView.extend({
   templateName: 'modal/topic_bulk_actions',
-  title: I18n.t('topics.bulk_actions')
+  title: I18n.t('topics.bulk.actions')
 });
diff --git a/app/assets/stylesheets/desktop/modal.scss b/app/assets/stylesheets/desktop/modal.scss
index 4c7ec965c..8445d42a4 100644
--- a/app/assets/stylesheets/desktop/modal.scss
+++ b/app/assets/stylesheets/desktop/modal.scss
@@ -251,6 +251,10 @@
   p {
     margin-top: 0;
   }
+  .modal-body {
+    height: 420px;
+    max-height: 420px;
+  }
 }
 .tabbed-modal {
   .modal-body {
diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss
index d8098c18c..2881416d3 100644
--- a/app/assets/stylesheets/desktop/topic-list.scss
+++ b/app/assets/stylesheets/desktop/topic-list.scss
@@ -69,9 +69,6 @@
     &:nth-child(even) {
       background-color: #f8f8f8;
     }
-    &.checked {
-      background-color: $highlight;
-    }
     &.archived a {
       opacity: 0.6;
     }
diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb
index fc8dd43e9..f2dec37ce 100644
--- a/app/controllers/topics_controller.rb
+++ b/app/controllers/topics_controller.rb
@@ -19,7 +19,8 @@ class TopicsController < ApplicationController
                                           :move_posts,
                                           :merge_topic,
                                           :clear_pin,
-                                          :autoclose]
+                                          :autoclose,
+                                          :bulk]
 
   before_filter :consider_user_for_promotion, only: :show
 
@@ -266,6 +267,11 @@ class TopicsController < ApplicationController
     render 'topics/show', formats: [:rss]
   end
 
+  def bulk
+    topic_ids = params.require(:topic_ids).map {|t| t.to_i}
+    render_json_dump topic_ids: topic_ids
+  end
+
   private
 
   def toggle_mute
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 2c99efe14..0d16de9cd 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -600,11 +600,13 @@ en:
         unstar: 'remove this topic from your starred list'
 
     topics:
-      toggle_bulk_select: "toggle bulk selection of topics"
-      bulk_actions: "Bulk Actions"
-      selected:
-        one: "You have selected <b>1</b> topic."
-        other: "You have selected <b>{{count}}</b> topics."
+      bulk:
+        toggle: "toggle bulk selection of topics"
+        actions: "Bulk Actions"
+        change_category: "Change Category"
+        selected:
+          one: "You have selected <b>1</b> topic."
+          other: "You have selected <b>{{count}}</b> topics."
 
       none:
         starred: "You haven't starred any topics yet. To star a topic, click or tap the star next to the title."
diff --git a/config/routes.rb b/config/routes.rb
index 4b232420f..f782d0b74 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -242,6 +242,7 @@ Discourse::Application.routes.draw do
   post "t" => "topics#create"
   put "t/:id" => "topics#update"
   delete "t/:id" => "topics#destroy"
+  put "topics/bulk"
   post "topics/timings"
   get "topics/similar_to"
   get "topics/created-by/:username" => "list#topics_by", as: "topics_by", constraints: {username: USERNAME_ROUTE_FORMAT}