diff --git a/app/assets/javascripts/admin/controllers/admin_badges_controller.js b/app/assets/javascripts/admin/controllers/admin_badges_controller.js
index 9ec65709a..baac6bf7f 100644
--- a/app/assets/javascripts/admin/controllers/admin_badges_controller.js
+++ b/app/assets/javascripts/admin/controllers/admin_badges_controller.js
@@ -9,14 +9,54 @@
 Discourse.AdminBadgesController = Ember.ArrayController.extend({
   itemController: 'adminBadge',
   queryParams: ['badgeId'],
+  badgeId: Em.computed.alias('selectedId'),
 
   /**
     ID of the currently selected badge.
 
-    @property badgeId
+    @property selectedId
     @type {Integer}
   **/
-  badgeId: Em.computed.alias('selectedItem.id'),
+  selectedId: null,
+
+  /**
+    Badge that is currently selected.
+
+    @property selectedItem
+    @type {Discourse.Badge}
+  **/
+  selectedItem: function() {
+    if (this.get('selectedId') === undefined || this.get('selectedId') === "undefined") {
+      // New Badge
+      return this.get('newBadge');
+    } else {
+      // Existing Badge
+      var selectedId = parseInt(this.get('selectedId'));
+      return this.get('model').filter(function(badge) {
+        return parseInt(badge.get('id')) === selectedId;
+      })[0];
+    }
+  }.property('selectedId', 'newBadge'),
+
+  /**
+    Unsaved badge, if one exists.
+
+    @property newBadge
+    @type {Discourse.Badge}
+  **/
+  newBadge: function() {
+    return this.get('model').filter(function(badge) {
+      return badge.get('id') === undefined;
+    })[0];
+  }.property('model.@each.id'),
+
+  /**
+    Whether a new unsaved badge exists.
+
+    @property newBadgeExists
+    @type {Discourse.Badge}
+  **/
+  newBadgeExists: Em.computed.notEmpty('newBadge'),
 
   /**
     We don't allow setting a description if a translation for the given badge
@@ -42,7 +82,7 @@ Discourse.AdminBadgesController = Ember.ArrayController.extend({
 
       @method newBadge
     **/
-    newBadge: function() {
+    createNewBadge: function() {
       var badge = Discourse.Badge.create({
         name: I18n.t('admin.badges.new_badge')
       });
@@ -57,7 +97,7 @@ Discourse.AdminBadgesController = Ember.ArrayController.extend({
       @param {Discourse.Badge} badge The badge to be selected
     **/
     selectBadge: function(badge) {
-      this.set('selectedItem', badge);
+      this.set('selectedId', badge.get('id'));
     },
 
     /**
@@ -80,7 +120,7 @@ Discourse.AdminBadgesController = Ember.ArrayController.extend({
       // Delete immediately if the selected badge is new.
       if (!this.get('selectedItem.id')) {
         this.get('model').removeObject(this.get('selectedItem'));
-        this.set('selectedItem', null);
+        this.set('selectedId', null);
         return;
       }
 
@@ -90,7 +130,7 @@ Discourse.AdminBadgesController = Ember.ArrayController.extend({
           var selected = self.get('selectedItem');
           selected.destroy().then(function() {
             // Success.
-            self.set('selectedItem', null);
+            self.set('selectedId', null);
             self.get('model').removeObject(selected);
           }, function() {
             // Failure.
diff --git a/app/assets/javascripts/admin/controllers/admin_user_badges_controller.js b/app/assets/javascripts/admin/controllers/admin_user_badges_controller.js
index 4c1fdea0c..1cfc77195 100644
--- a/app/assets/javascripts/admin/controllers/admin_user_badges_controller.js
+++ b/app/assets/javascripts/admin/controllers/admin_user_badges_controller.js
@@ -27,7 +27,7 @@ Discourse.AdminUserBadgesController = Ember.ArrayController.extend({
 
     var badges = [];
     this.get('badges').forEach(function(badge) {
-      if (!granted[badge.get('id')]) {
+      if (badge.get('multiple_grant') || !granted[badge.get('id')]) {
         badges.push(badge);
       }
     });
diff --git a/app/assets/javascripts/admin/templates/badges.js.handlebars b/app/assets/javascripts/admin/templates/badges.js.handlebars
index 6b1b513c0..ae0524def 100644
--- a/app/assets/javascripts/admin/templates/badges.js.handlebars
+++ b/app/assets/javascripts/admin/templates/badges.js.handlebars
@@ -14,7 +14,7 @@
       </li>
       {{/each}}
     </ul>
-    <button {{action newBadge}} class='btn'><i class="fa fa-plus"></i>{{i18n admin.badges.new}}</button>
+    <button {{action createNewBadge}} {{bind-attr disabled=newBadgeExists}} class='btn'><i class="fa fa-plus"></i>{{i18n admin.badges.new}}</button>
   </div>
 
   {{#if selectedItem}}
@@ -47,7 +47,7 @@
         {{#if controller.canEditDescription}}
           {{textarea name="description" value=description}}
         {{else}}
-          {{textarea name="description" value=translatedDescription disabled=true}}
+          {{textarea name="description" value=displayDescription disabled=true}}
         {{/if}}
       </div>
 
@@ -58,6 +58,13 @@
         </span>
       </div>
 
+      <div>
+        <span>
+          {{input type="checkbox" checked=multiple_grant disabled=readOnly}}
+          {{i18n admin.badges.multiple_grant}}
+        </span>
+      </div>
+
       {{#unless readOnly}}
         <div class='buttons'>
           <button {{action save}} {{bind-attr disabled=controller.disableSave}} class='btn btn-primary'>{{i18n admin.badges.save}}</button>
diff --git a/app/assets/javascripts/discourse/components/user_badge_component.js b/app/assets/javascripts/discourse/components/user_badge_component.js
index daf3e6d30..725169b23 100644
--- a/app/assets/javascripts/discourse/components/user_badge_component.js
+++ b/app/assets/javascripts/discourse/components/user_badge_component.js
@@ -3,5 +3,9 @@ Discourse.UserBadgeComponent = Ember.Component.extend({
 
   badgeTypeClassName: function() {
     return "badge-type-" + this.get('badge.badge_type.name').toLowerCase();
-  }.property('badge.badge_type.name')
+  }.property('badge.badge_type.name'),
+
+  showGrantCount: function() {
+    return this.get('count') && this.get('count') > 1;
+  }.property('count')
 });
diff --git a/app/assets/javascripts/discourse/models/badge.js b/app/assets/javascripts/discourse/models/badge.js
index f95775b68..f709ef29c 100644
--- a/app/assets/javascripts/discourse/models/badge.js
+++ b/app/assets/javascripts/discourse/models/badge.js
@@ -40,8 +40,8 @@ Discourse.Badge = Discourse.Model.extend({
   }.property('name', 'i18nNameKey'),
 
   /**
-    The i18n translated description for this badge. Returns the original
-    description if no translation exists.
+    The i18n translated description for this badge. Returns the null if no
+    translation exists.
 
     @property translatedDescription
     @type {String}
@@ -50,11 +50,23 @@ Discourse.Badge = Discourse.Model.extend({
     var i18nKey = "badges.badge." + this.get('i18nNameKey') + ".description",
         translation = I18n.t(i18nKey);
     if (translation.indexOf(i18nKey) !== -1) {
-      translation = this.get('description');
+      translation = null;
     }
     return translation;
   }.property('i18nNameKey'),
 
+  /**
+    Display-friendly description string. Returns either a translation or the
+    original description string.
+
+    @property displayDescription
+    @type {String}
+  **/
+  displayDescription: function() {
+    var translated = this.get('translatedDescription');
+    return translated === null ? this.get('description') : translated;
+  }.property('description', 'translatedDescription'),
+
   /**
     Update this badge with the response returned by the server on save.
 
@@ -103,7 +115,8 @@ Discourse.Badge = Discourse.Model.extend({
         name: this.get('name'),
         description: this.get('description'),
         badge_type_id: this.get('badge_type_id'),
-        allow_title: this.get('allow_title')
+        allow_title: !!this.get('allow_title'),
+        multiple_grant: !!this.get('multiple_grant')
       }
     }).then(function(json) {
       self.updateFromJson(json);
diff --git a/app/assets/javascripts/discourse/models/user_badge.js b/app/assets/javascripts/discourse/models/user_badge.js
index 23b987e60..a7005341c 100644
--- a/app/assets/javascripts/discourse/models/user_badge.js
+++ b/app/assets/javascripts/discourse/models/user_badge.js
@@ -79,10 +79,15 @@ Discourse.UserBadge.reopenClass({
 
     @method findByUsername
     @param {String} username
+    @param {Object} options
     @returns {Promise} a promise that resolves to an array of `Discourse.UserBadge`.
   **/
-  findByUsername: function(username) {
-    return Discourse.ajax("/user_badges.json?username=" + username).then(function(json) {
+  findByUsername: function(username, options) {
+    var url = "/user_badges.json?username=" + username;
+    if (options && options.aggregated) {
+      url += "&aggregated=true";
+    }
+    return Discourse.ajax(url).then(function(json) {
       return Discourse.UserBadge.createFromJson(json);
     });
   },
diff --git a/app/assets/javascripts/discourse/routes/user_badges_route.js b/app/assets/javascripts/discourse/routes/user_badges_route.js
index 97ad6ddf9..c5ce95d1c 100644
--- a/app/assets/javascripts/discourse/routes/user_badges_route.js
+++ b/app/assets/javascripts/discourse/routes/user_badges_route.js
@@ -8,7 +8,7 @@
 **/
 Discourse.UserBadgesRoute = Discourse.Route.extend({
   model: function() {
-    return Discourse.UserBadge.findByUsername(this.modelFor('user').get('username_lower'));
+    return Discourse.UserBadge.findByUsername(this.modelFor('user').get('username_lower'), {aggregated: true});
   },
 
   setupController: function(controller, model) {
diff --git a/app/assets/javascripts/discourse/templates/badges/index.js.handlebars b/app/assets/javascripts/discourse/templates/badges/index.js.handlebars
index 0551537fe..201e75aa2 100644
--- a/app/assets/javascripts/discourse/templates/badges/index.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/badges/index.js.handlebars
@@ -5,7 +5,7 @@
     {{#each}}
       <tr>
         <td class='badge'>{{user-badge badge=this}}</td>
-        <td class='description'>{{translatedDescription}}</td>
+        <td class='description'>{{displayDescription}}</td>
         <td class='grant-count'>{{i18n badges.granted count=grant_count}}</td>
       </tr>
     {{/each}}
diff --git a/app/assets/javascripts/discourse/templates/badges/show.js.handlebars b/app/assets/javascripts/discourse/templates/badges/show.js.handlebars
index 26f7a0906..551a655fd 100644
--- a/app/assets/javascripts/discourse/templates/badges/show.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/badges/show.js.handlebars
@@ -8,7 +8,7 @@
   <table class='badges-listing'>
     <tr>
       <td class='badge'>{{user-badge badge=this}}</td>
-      <td class='description'>{{translatedDescription}}</td>
+      <td class='description'>{{displayDescription}}</td>
       <td class='grant-count'>{{i18n badges.granted count=grant_count}}</td>
     </tr>
   </table>
diff --git a/app/assets/javascripts/discourse/templates/components/user-badge.js.handlebars b/app/assets/javascripts/discourse/templates/components/user-badge.js.handlebars
index 64419e527..1e749080a 100644
--- a/app/assets/javascripts/discourse/templates/components/user-badge.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/components/user-badge.js.handlebars
@@ -1,6 +1,9 @@
 {{#link-to 'badges.show' badge}}
-  <span {{bind-attr class=":user-badge badgeTypeClassName" data-badge-name="badge.name" title="badge.translatedDescription"}}>
+  <span {{bind-attr class=":user-badge badgeTypeClassName" data-badge-name="badge.name" title="badge.displayDescription"}}>
     <i class='fa fa-certificate'></i>
     {{badge.displayName}}
+    {{#if showGrantCount}}
+      <span class="count">(&times;&nbsp;{{count}})</span>
+    {{/if}}
   </span>
 {{/link-to}}
diff --git a/app/assets/javascripts/discourse/templates/user/badges.js.handlebars b/app/assets/javascripts/discourse/templates/user/badges.js.handlebars
index 6cbf2003c..897e46c3f 100644
--- a/app/assets/javascripts/discourse/templates/user/badges.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/user/badges.js.handlebars
@@ -1,5 +1,5 @@
 <section class='user-content user-badges-list'>
   {{#each}}
-    {{user-badge badge=badge}}
+    {{user-badge badge=badge count=count}}
   {{/each}}
 </section>
diff --git a/app/assets/stylesheets/common/base/user-badges.scss b/app/assets/stylesheets/common/base/user-badges.scss
index b6b0e48ec..5f013f1da 100644
--- a/app/assets/stylesheets/common/base/user-badges.scss
+++ b/app/assets/stylesheets/common/base/user-badges.scss
@@ -44,6 +44,12 @@
       font-size: 50px;
       margin-bottom: 5px;
     }
+
+    .count {
+      display: block;
+      font-size: 0.8em;
+      color: scale-color($primary, $lightness: 50%);
+    }
   }
 }
 
diff --git a/app/controllers/admin/badges_controller.rb b/app/controllers/admin/badges_controller.rb
index 08991e8ef..2715f4cdf 100644
--- a/app/controllers/admin/badges_controller.rb
+++ b/app/controllers/admin/badges_controller.rb
@@ -30,11 +30,12 @@ class Admin::BadgesController < Admin::AdminController
     end
 
     def update_badge_from_params(badge)
-      params.permit(:name, :description, :badge_type_id, :allow_title)
+      params.permit(:name, :description, :badge_type_id, :allow_title, :multiple_grant)
       badge.name = params[:name]
       badge.description = params[:description]
       badge.badge_type = BadgeType.find(params[:badge_type_id])
       badge.allow_title = params[:allow_title]
+      badge.multiple_grant = params[:multiple_grant]
       badge
     end
 end
diff --git a/app/controllers/user_badges_controller.rb b/app/controllers/user_badges_controller.rb
index ad11f75f4..373234bb5 100644
--- a/app/controllers/user_badges_controller.rb
+++ b/app/controllers/user_badges_controller.rb
@@ -16,6 +16,10 @@ class UserBadgesController < ApplicationController
 
     user_badges = user_badges.includes(:user, :granted_by, badge: :badge_type)
 
+    if params[:aggregated]
+      user_badges = user_badges.group(:badge_id).select(UserBadge.attribute_names.map {|x| "MAX(#{x}) as #{x}" }, 'COUNT(*) as count')
+    end
+
     render_serialized(user_badges, UserBadgeSerializer, root: "user_badges")
   end
 
diff --git a/app/models/badge.rb b/app/models/badge.rb
index 422317346..b891ef02d 100644
--- a/app/models/badge.rb
+++ b/app/models/badge.rb
@@ -5,6 +5,7 @@ class Badge < ActiveRecord::Base
   validates :name, presence: true, uniqueness: true
   validates :badge_type, presence: true
   validates :allow_title, inclusion: [true, false]
+  validates :multiple_grant, inclusion: [true, false]
 
   def self.trust_level_badge_ids
     (1..4).to_a
@@ -15,22 +16,28 @@ class Badge < ActiveRecord::Base
     save!
   end
 
+  def single_grant?
+    !self.multiple_grant?
+  end
+
 end
 
 # == Schema Information
 #
 # Table name: badges
 #
-#  id            :integer          not null, primary key
-#  name          :string(255)      not null
-#  description   :text
-#  badge_type_id :integer          not null
-#  grant_count   :integer          default(0), not null
-#  created_at    :datetime
-#  updated_at    :datetime
-#  allow_title   :boolean          default(FALSE), not null
+#  id             :integer          not null, primary key
+#  name           :string(255)      not null
+#  description    :text
+#  badge_type_id  :integer          not null
+#  grant_count    :integer          default(0), not null
+#  created_at     :datetime
+#  updated_at     :datetime
+#  allow_title    :boolean          default(FALSE), not null
+#  multiple_grant :boolean          default(FALSE), not null
 #
 # Indexes
 #
-#  index_badges_on_name  (name) UNIQUE
+#  index_badges_on_badge_type_id  (badge_type_id)
+#  index_badges_on_name           (name) UNIQUE
 #
diff --git a/app/models/user_badge.rb b/app/models/user_badge.rb
index edf24521e..e938a8558 100644
--- a/app/models/user_badge.rb
+++ b/app/models/user_badge.rb
@@ -3,7 +3,7 @@ class UserBadge < ActiveRecord::Base
   belongs_to :user
   belongs_to :granted_by, class_name: 'User'
 
-  validates :badge_id, presence: true, uniqueness: {scope: :user_id}
+  validates :badge_id, presence: true, uniqueness: {scope: :user_id}, if: 'badge.single_grant?'
   validates :user_id, presence: true
   validates :granted_at, presence: true
   validates :granted_by, presence: true
@@ -21,5 +21,6 @@ end
 #
 # Indexes
 #
-#  index_user_badges_on_badge_id_and_user_id  (badge_id,user_id) UNIQUE
+#  index_user_badges_on_badge_id_and_user_id  (badge_id,user_id)
+#  index_user_badges_on_user_id               (user_id)
 #
diff --git a/app/serializers/badge_serializer.rb b/app/serializers/badge_serializer.rb
index 90be747c4..ac6e2fcc0 100644
--- a/app/serializers/badge_serializer.rb
+++ b/app/serializers/badge_serializer.rb
@@ -1,5 +1,5 @@
 class BadgeSerializer < ApplicationSerializer
-  attributes :id, :name, :description, :grant_count, :allow_title
+  attributes :id, :name, :description, :grant_count, :allow_title, :multiple_grant
 
   has_one :badge_type
 end
diff --git a/app/serializers/user_badge_serializer.rb b/app/serializers/user_badge_serializer.rb
index f751ecd6d..c2235dcf0 100644
--- a/app/serializers/user_badge_serializer.rb
+++ b/app/serializers/user_badge_serializer.rb
@@ -1,7 +1,11 @@
 class UserBadgeSerializer < ApplicationSerializer
-  attributes :id, :granted_at
+  attributes :id, :granted_at, :count
 
   has_one :badge
   has_one :user, serializer: BasicUserSerializer, root: :users
   has_one :granted_by, serializer: BasicUserSerializer, root: :users
+
+  def include_count?
+    object.respond_to? :count
+  end
 end
diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb
index 7fc2ff149..a15e7d23f 100644
--- a/app/services/badge_granter.rb
+++ b/app/services/badge_granter.rb
@@ -14,7 +14,7 @@ class BadgeGranter
 
     user_badge = UserBadge.find_by(badge_id: @badge.id, user_id: @user.id)
 
-    unless user_badge
+    if user_badge.nil? || @badge.multiple_grant?
       UserBadge.transaction do
         user_badge = UserBadge.create!(badge: @badge, user: @user,
                                        granted_by: @granted_by, granted_at: Time.now)
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 977a8cc42..ac7f0ed9f 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1819,6 +1819,7 @@ en:
         no_user_badges: "%{name} has not been granted any badges."
         no_badges: There are no badges that can be granted.
         allow_title: Allow badge to be used as a title
+        multiple_grant: Can be granted multiple times
 
     lightbox:
       download: "download"
diff --git a/db/migrate/20140520062826_add_multiple_award_to_badges.rb b/db/migrate/20140520062826_add_multiple_award_to_badges.rb
new file mode 100644
index 000000000..4e7fb1bdf
--- /dev/null
+++ b/db/migrate/20140520062826_add_multiple_award_to_badges.rb
@@ -0,0 +1,17 @@
+class AddMultipleAwardToBadges < ActiveRecord::Migration
+  def change
+    add_column :badges, :multiple_grant, :boolean, default: false, null: false
+
+    reversible do |dir|
+      dir.up do
+        remove_index :user_badges, column: [:badge_id, :user_id]
+        add_index :user_badges, [:badge_id, :user_id]
+      end
+
+      dir.down do
+        remove_index :user_badges, column: [:badge_id, :user_id]
+        add_index :user_badges, [:badge_id, :user_id], unique: true
+      end
+    end
+  end
+end
diff --git a/spec/controllers/admin/badges_controller_spec.rb b/spec/controllers/admin/badges_controller_spec.rb
index b507c6a7a..478b61b68 100644
--- a/spec/controllers/admin/badges_controller_spec.rb
+++ b/spec/controllers/admin/badges_controller_spec.rb
@@ -35,12 +35,12 @@ describe Admin::BadgesController do
 
     context '.update' do
       it 'returns success' do
-        xhr :put, :update, id: badge.id, name: "123456", badge_type_id: badge.badge_type_id, allow_title: false
+        xhr :put, :update, id: badge.id, name: "123456", badge_type_id: badge.badge_type_id, allow_title: false, multiple_grant: false
         response.should be_success
       end
 
       it 'updates the badge' do
-        xhr :put, :update, id: badge.id, name: "123456", badge_type_id: badge.badge_type_id, allow_title: false
+        xhr :put, :update, id: badge.id, name: "123456", badge_type_id: badge.badge_type_id, allow_title: false, multiple_grant: true
         badge.reload.name.should eq('123456')
       end
     end
diff --git a/spec/controllers/user_badges_controller_spec.rb b/spec/controllers/user_badges_controller_spec.rb
index 5320760fe..a26b52650 100644
--- a/spec/controllers/user_badges_controller_spec.rb
+++ b/spec/controllers/user_badges_controller_spec.rb
@@ -26,6 +26,14 @@ describe UserBadgesController do
       parsed = JSON.parse(response.body)
       parsed["user_badges"].length.should == 1
     end
+
+    it 'includes counts when passed the aggregate argument' do
+      xhr :get, :index, username: user.username, aggregated: true
+
+      response.status.should == 200
+      parsed = JSON.parse(response.body)
+      parsed["user_badges"].first.has_key?('count').should be_true
+    end
   end
 
   context 'create' do
diff --git a/test/javascripts/admin/controllers/admin_badges_controller_test.js b/test/javascripts/admin/controllers/admin_badges_controller_test.js
index f310d7852..ccf6aa7cf 100644
--- a/test/javascripts/admin/controllers/admin_badges_controller_test.js
+++ b/test/javascripts/admin/controllers/admin_badges_controller_test.js
@@ -15,11 +15,10 @@ test("canEditDescription", function() {
   ok(!controller.get('canEditDescription'), "shows the displayName when it is different from the name");
 });
 
-test("newBadge", function() {
+test("createNewBadge", function() {
   var controller = testController(Discourse.AdminBadgesController, []);
-  controller.send('newBadge');
+  controller.send('createNewBadge');
   equal(controller.get('model.length'), 1, "adds a new badge to the list of badges");
-  equal(controller.get('model')[0], controller.get('selectedItem'), "the new badge is selected");
 });
 
 test("selectBadge", function() {
diff --git a/test/javascripts/models/badge_test.js b/test/javascripts/models/badge_test.js
index ff688ee8e..f04279d97 100644
--- a/test/javascripts/models/badge_test.js
+++ b/test/javascripts/models/badge_test.js
@@ -18,13 +18,22 @@ test('displayName', function() {
 
 test('translatedDescription', function() {
   var badge1 = Discourse.Badge.create({id: 1, name: "Test Badge 1", description: "TEST"});
-  equal(badge1.get('translatedDescription'), "TEST", "returns original description when no translation exists");
+  equal(badge1.get('translatedDescription'), null, "returns null when no translation exists");
 
   var badge2 = Discourse.Badge.create({id: 2, name: "Test Badge 2 **"});
   this.stub(I18n, "t").returns("description translation");
   equal(badge2.get('translatedDescription'), "description translation", "users translated description");
 });
 
+test('displayDescription', function() {
+  var badge1 = Discourse.Badge.create({id: 1, name: "Test Badge 1", description: "TEST"});
+  equal(badge1.get('displayDescription'), "TEST", "returns original description when no translation exists");
+
+  var badge2 = Discourse.Badge.create({id: 2, name: "Test Badge 2 **"});
+  this.stub(I18n, "t").returns("description translation");
+  equal(badge2.get('displayDescription'), "description translation", "users translated description");
+});
+
 test('createFromJson array', function() {
   var badgesJson = {"badge_types":[{"id":6,"name":"Silver 1"}],"badges":[{"id":1126,"name":"Badge 1","description":null,"badge_type_id":6}]};