diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js
index 1f7bd3f2df..118dbcddda 100644
--- a/public/src/client/groups/details.js
+++ b/public/src/client/groups/details.js
@@ -1,11 +1,13 @@
 "use strict";
 /* globals define, socket, ajaxify, app, bootbox, RELATIVE_PATH, utils */
 
-define('forum/groups/details', ['iconSelect', 'components', 'vendor/colorpicker/colorpicker', 'vendor/jquery/draggable-background/backgroundDraggable'], function(iconSelect, components) {
+define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescroll', 'vendor/colorpicker/colorpicker', 'vendor/jquery/draggable-background/backgroundDraggable'], function(iconSelect, components, infinitescroll) {
 	var Details = {
 			cover: {}
 		};
 
+	var searchInterval;
+
 	Details.init = function() {
 		var detailsPage = components.get('groups/container'),
 			settingsFormEl = detailsPage.find('form');
@@ -15,6 +17,9 @@ define('forum/groups/details', ['iconSelect', 'components', 'vendor/colorpicker/
 			Details.initialiseCover();
 		}
 
+		handleMemberSearch();
+		handleMemberInfiniteScroll();
+
 		components.get('groups/activity').find('.content img').addClass('img-responsive');
 
 		detailsPage.on('click', '[data-action]', function() {
@@ -280,5 +285,83 @@ define('forum/groups/details', ['iconSelect', 'components', 'vendor/colorpicker/
 		});
 	};
 
+	function handleMemberSearch() {
+		$('[component="groups/members/search"]').on('keyup', function() {
+			var query = $(this).val();
+			if (searchInterval) {
+				clearInterval(searchInterval);
+				searchInterval = 0;
+			}
+
+			searchInterval = setTimeout(function() {
+				socket.emit('groups.searchMembers', {groupName: ajaxify.variables.get('group_name'), query: query}, function(err, results) {
+					if (err) {
+						return app.alertError(err.message);
+					}
+
+					infinitescroll.parseAndTranslate('groups/details', 'members', {
+						group: {
+							members: results.users,
+							isOwner: ajaxify.variables.get('is_owner') === 'true'
+						}
+					}, function(html) {
+						$('[component="groups/members"] tbody').html(html);
+					});
+				});
+			}, 250);
+		})
+	}
+
+	function handleMemberInfiniteScroll() {
+		$('[component="groups/members"] tbody').on('scroll', function() {
+			var $this = $(this);
+			var bottom = ($this[0].scrollHeight - $this.height()) * 0.9;
+			if ($this.scrollTop() > bottom) {
+				loadMoreMembers();
+			}
+		});
+	}
+
+	function loadMoreMembers() {
+		var members = $('[component="groups/members"]');
+		if (members.attr('loading')) {
+			return;
+		}
+		members.attr('loading', 1);
+		socket.emit('groups.loadMoreMembers', {
+			groupName: ajaxify.variables.get('group_name'),
+			after: members.attr('data-nextstart')
+		}, function(err, data) {
+			if (err) {
+				return app.alertError(err.message);
+			}
+
+			if (data && data.users.length) {
+				onMembersLoaded(data.users, function() {
+					members.removeAttr('loading');
+					members.attr('data-nextstart', data.nextStart);
+				});
+			} else {
+				members.removeAttr('loading');
+			}
+		});
+	}
+
+	function onMembersLoaded(users, callback) {
+		users = users.filter(function(user) {
+			return !$('[component="groups/members"] [data-uid="' + user.uid + '"]').length;
+		});
+
+		infinitescroll.parseAndTranslate('groups/details', 'members', {
+			group: {
+				members: users,
+				isOwner: ajaxify.variables.get('is_owner') === 'true'
+			}
+		}, function(html) {
+			$('[component="groups/members"] tbody').append(html);
+			callback();
+		});
+	}
+
 	return Details;
 });
\ No newline at end of file
diff --git a/src/controllers/groups.js b/src/controllers/groups.js
index 8d7ddac3e2..84a3e25e2d 100644
--- a/src/controllers/groups.js
+++ b/src/controllers/groups.js
@@ -79,7 +79,9 @@ groupsController.details = function(req, res, next) {
 		async.parallel({
 			group: function(next) {
 				groups.get(res.locals.groupName, {
-					uid: req.uid
+					uid: req.uid,
+					truncateUserList: true,
+					userListCount: 20
 				}, next);
 			},
 			posts: function(next) {
diff --git a/src/groups.js b/src/groups.js
index 8db9afe80b..b08c6eed3c 100644
--- a/src/groups.js
+++ b/src/groups.js
@@ -115,27 +115,18 @@ var async = require('async'),
 		}
 
 		options.escape = options.hasOwnProperty('escape') ? options.escape : true;
+		var stop = -1;
 
 		async.parallel({
 			base: function (next) {
 				db.getObject('group:' + groupName, next);
 			},
-			owners: function (next) {
-				async.waterfall([
-					function(next) {
-						db.getSetMembers('group:' + groupName + ':owners', next);
-					},
-					function(uids, next) {
-						user.getUsers(uids, options.uid, next);
-					}
-				], next);
-			},
 			members: function (next) {
-				var stop = -1;
 				if (options.truncateUserList) {
 					stop = (parseInt(options.userListCount, 10) || 4) - 1;
 				}
-				user.getUsersFromSet('group:' + groupName + ':members', options.uid, 0, stop, next);
+
+				Groups.getOwnersAndMembers(groupName, options.uid, 0, stop, next);
 			},
 			pending: function (next) {
 				async.waterfall([
@@ -164,19 +155,6 @@ var async = require('async'),
 				results.base['cover:position'] = '50% 50%';
 			}
 
-			var ownerUids = [];
-			results.owners.forEach(function(user) {
-				if (user) {
-					user.isOwner = true;
-					ownerUids.push(user.uid.toString());
-				}
-			});
-
-			results.members = results.members.filter(function(user, index, array) {
-				return user && user.uid && ownerUids.indexOf(user.uid.toString()) === -1;
-			});
-			results.members = results.owners.concat(results.members);
-
 			plugins.fireHook('filter:parse.raw', results.base.description, function(err, descriptionParsed) {
 				if (err) {
 					return callback(err);
@@ -190,6 +168,7 @@ var async = require('async'),
 				results.base.userTitleEnabled = results.base.userTitleEnabled ? !!parseInt(results.base.userTitleEnabled, 10) : true;
 				results.base.createtimeISO = utils.toISOString(results.base.createtime);
 				results.base.members = results.members;
+				results.base.membersNextStart = stop + 1;
 				results.base.pending = results.pending.filter(Boolean);
 				results.base.deleted = !!parseInt(results.base.deleted, 10);
 				results.base.hidden = !!parseInt(results.base.hidden, 10);
@@ -207,6 +186,43 @@ var async = require('async'),
 		});
 	};
 
+	Groups.getOwnersAndMembers = function(groupName, uid, start, stop, callback) {
+		async.parallel({
+			owners: function (next) {
+				async.waterfall([
+					function(next) {
+						db.getSetMembers('group:' + groupName + ':owners', next);
+					},
+					function(uids, next) {
+						user.getUsers(uids, uid, next);
+					}
+				], next);
+			},
+			members: function (next) {
+				user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop, next);
+			}
+		}, function(err, results) {
+			if (err) {
+				return callback(err);
+			}
+
+			var ownerUids = [];
+			results.owners.forEach(function(user) {
+				if (user) {
+					user.isOwner = true;
+					ownerUids.push(user.uid.toString());
+				}
+			});
+
+			results.members = results.members.filter(function(user, index, array) {
+				return user && user.uid && ownerUids.indexOf(user.uid.toString()) === -1;
+			});
+			results.members = results.owners.concat(results.members);
+
+			callback(null, results.members);
+		});
+	};
+
 	Groups.escapeGroupData = function(group) {
 		if (group) {
 			group.nameEncoded = encodeURIComponent(group.name);
diff --git a/src/groups/ownership.js b/src/groups/ownership.js
index fa4beb70ba..4af6126aef 100644
--- a/src/groups/ownership.js
+++ b/src/groups/ownership.js
@@ -13,6 +13,14 @@ module.exports = function(Groups) {
 		db.isSetMember('group:' + groupName + ':owners', uid, callback);
 	};
 
+	Groups.ownership.isOwners = function(uids, groupName, callback) {
+		if (!Array.isArray(uids)) {
+			return callback(null, []);
+		}
+
+		db.isSetMembers('group:' + groupName + ':owners', uids, callback);
+	};
+
 	Groups.ownership.grant = function(toUid, groupName, callback) {
 		// Note: No ownership checking is done here on purpose!
 		db.setAdd('group:' + groupName + ':owners', toUid, callback);
diff --git a/src/groups/search.js b/src/groups/search.js
index d674b29dba..1491129f30 100644
--- a/src/groups/search.js
+++ b/src/groups/search.js
@@ -87,7 +87,48 @@ module.exports = function(Groups) {
 			], callback);
 		}
 
+		if (!data.query) {
+			Groups.getOwnersAndMembers(data.groupName, data.uid, 0, 19, function(err, users) {
+				if (err) {
+					return callback(err);
+				}
+				callback(null, {users: users});
+			});
+			return;
+		}
+
 		data.findUids = findUids;
-		user.search(data, callback);
+		var results;
+		async.waterfall([
+			function(next) {
+				user.search(data, next);
+			},
+			function(_results, next) {
+				results = _results;
+				var uids = results.users.map(function(user) {
+					return user && user.uid;
+				});
+				Groups.ownership.isOwners(uids, data.groupName, next);
+			},
+			function(isOwners, next) {
+
+				results.users.forEach(function(user, index) {
+					if (user) {
+						user.isOwner = isOwners[index];
+					}
+				});
+
+				results.users.sort(function(a,b) {
+					if (a.isOwner && !b.isOwner) {
+						return -1;
+					} else if (!a.isOwner && b.isOwner) {
+						return 1;
+					} else {
+						return 0;
+					}
+				})
+				next(null, results);
+			}
+		], callback);
 	};
 };
diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js
index 69ce6a367c..671777ea95 100644
--- a/src/socket.io/groups.js
+++ b/src/socket.io/groups.js
@@ -244,6 +244,20 @@ SocketGroups.searchMembers = function(socket, data, callback) {
 	groups.searchMembers(data, callback);
 };
 
+SocketGroups.loadMoreMembers = function(socket, data, callback) {
+	if (!data || !data.groupName || !parseInt(data.after, 10)) {
+		return callback(new Error('[[error:invalid-data]]'));
+	}
+	data.after = parseInt(data.after, 10);
+	user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9, function(err, users) {
+		if (err) {
+			return callback(err);
+		}
+
+		callback(null, {users: users, nextStart: data.after + 10});
+	});
+};
+
 SocketGroups.kick = function(socket, data, callback) {
 	if (!data) {
 		return callback(new Error('[[error:invalid-data]]'));