diff --git a/reviewboard/static/rb/js/views/reviewRequestEditorView.js b/reviewboard/static/rb/js/views/reviewRequestEditorView.js
index d6f6edfd21d6e513b93a36a7e9e97aadc4cefd72..7209780b22a2aaa6f4b26796422e0523fa63a385 100644
--- a/reviewboard/static/rb/js/views/reviewRequestEditorView.js
+++ b/reviewboard/static/rb/js/views/reviewRequestEditorView.js
@@ -502,8 +502,10 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
                 fieldName: 'groups',
                 nameKey: 'name',
                 descKey: 'display_name',
+                matchContains: true,
                 extraParams: {
-                    displayname: 1
+                    displayname: 1,
+                    contains: 1
                 }
             },
             formatter: function(view, data, $el) {
@@ -1355,6 +1357,7 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
                     return s;
                 },
                 matchCase: false,
+                matchContains: options.matchContains,
                 multiple: true,
                 parse: function(data) {
                     var parsed = [],
@@ -1759,4 +1762,4 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
 });
 
 
-})();
\ No newline at end of file
+})();
diff --git a/reviewboard/testing/testcase.py b/reviewboard/testing/testcase.py
index cb9c5cd05dbc413f73f972d69c7169231c2448c4..e5116afa872843bb97812f9bdb6976dba2a3c522 100644
--- a/reviewboard/testing/testcase.py
+++ b/reviewboard/testing/testcase.py
@@ -527,8 +527,9 @@ class TestCase(FixturesCompilerMixin, DjbletsTestCase):
 
         return review
 
-    def create_review_group(self, name='test-group', with_local_site=False,
-                            local_site=None, visible=True, invite_only=False,
+    def create_review_group(self, name='test-group', display_name='',
+                            with_local_site=False, local_site=None,
+                            visible=True, invite_only=False,
                             is_default_group=False):
         """Creates a review group for testing.
 
@@ -540,6 +541,7 @@ class TestCase(FixturesCompilerMixin, DjbletsTestCase):
 
         return Group.objects.create(
             name=name,
+            display_name=display_name,
             local_site=local_site,
             visible=visible,
             invite_only=invite_only,
diff --git a/reviewboard/webapi/features.py b/reviewboard/webapi/features.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdddcb25528afc1101fb6bffa504c19050877399
--- /dev/null
+++ b/reviewboard/webapi/features.py
@@ -0,0 +1,23 @@
+"""Feature definitions for webapi."""
+
+from __future__ import unicode_literals
+
+from django.utils.translation import ugettext_lazy as _
+from djblets.features import Feature, FeatureLevel
+
+
+class ExtendedGroupSearchFeature(Feature):
+    """A feature for extended group search.
+
+    Extended group search enables searching for groups by matching the search
+    string anywhere in the group name (or in the display name) and not just
+    from the start.
+    """
+
+    feature_id = 'webapi.extended_group_search'
+    name = _('Extended Group Search')
+    level = FeatureLevel.EXPERIMENTAL
+    summary = _('Allow searching for groups containing search string.')
+
+
+extended_group_search_feature = ExtendedGroupSearchFeature()
diff --git a/reviewboard/webapi/resources/review_group.py b/reviewboard/webapi/resources/review_group.py
index 8843284672d6afd02bd85a7191fa6794ddc8a274..e830a42c1db226043295f6e2d8fb2e3668e83a35 100644
--- a/reviewboard/webapi/resources/review_group.py
+++ b/reviewboard/webapi/resources/review_group.py
@@ -15,6 +15,7 @@ from reviewboard.webapi.base import WebAPIResource
 from reviewboard.webapi.decorators import webapi_check_local_site
 from reviewboard.webapi.errors import (GROUP_ALREADY_EXISTS,
                                        INVALID_USER)
+from reviewboard.webapi.features import extended_group_search_feature
 from reviewboard.webapi.resources import resources
 
 
@@ -100,6 +101,8 @@ class ReviewGroupResource(WebAPIResource):
     def get_queryset(self, request, is_list=False, local_site_name=None,
                      *args, **kwargs):
         search_q = request.GET.get('q', None)
+        contains = request.GET.get('contains', None) and \
+            extended_group_search_feature.is_enabled()
         local_site = self._get_local_site(local_site_name)
 
         if is_list:
@@ -109,10 +112,16 @@ class ReviewGroupResource(WebAPIResource):
             query = self.model.objects.filter(local_site=local_site)
 
         if search_q:
-            q = Q(name__istartswith=search_q)
+            if contains:
+                q = Q(name__icontains=search_q)
+            else:
+                q = Q(name__istartswith=search_q)
 
             if request.GET.get('displayname', None):
-                q = q | Q(display_name__istartswith=search_q)
+                if contains:
+                    q = q | Q(display_name__icontains=search_q)
+                else:
+                    q = q | Q(display_name__istartswith=search_q)
 
             query = query.filter(q)
 
@@ -155,6 +164,15 @@ class ReviewGroupResource(WebAPIResource):
                 'description': 'Specifies whether ``q`` should also match '
                                'the beginning of the display name.',
             },
+            'contains': {
+                'type': bool,
+                'description': 'Specifies whether ``q`` should match anywhere '
+                               'in the group name (or in the display name '
+                               'when using ``displayname``) and not just from '
+                               'the start. The extended group search feature '
+                               'must be enabled for this parameter to have '
+                               'effect.',
+            },
         },
         allow_unknown=True
     )
@@ -165,9 +183,10 @@ class ReviewGroupResource(WebAPIResource):
         The list of review groups can be filtered down using the ``q`` and
         ``displayname`` parameters.
 
-        Setting ``q`` to a value will by default limit the results to
-        group names starting with that value. This is a case-insensitive
-        comparison.
+        Setting ``q`` to a value will by default limit the results to group
+        names starting with that value (unless ``contains`` is set to ``1`` in
+        which case the value can appear anywhere in the group name). This is a
+        case-insensitive comparison.
 
         If ``displayname`` is set to ``1``, the display names will also be
         checked along with the username. ``displayname`` is ignored if ``q``
diff --git a/reviewboard/webapi/tests/base.py b/reviewboard/webapi/tests/base.py
index 0735b174f8671f765c250b3bb31782d6ef73fed1..e1474d2035249015500e134a359fd52fbc5e71d0 100644
--- a/reviewboard/webapi/tests/base.py
+++ b/reviewboard/webapi/tests/base.py
@@ -12,6 +12,7 @@ from djblets.webapi.testing.testcases import WebAPITestCaseMixin
 from reviewboard.notifications.tests import EmailTestHelper
 from reviewboard.reviews.models import Review
 from reviewboard.testing import TestCase
+from reviewboard.webapi.features import extended_group_search_feature
 from reviewboard.webapi.tests.mimetypes import (
     error_mimetype,
     file_attachment_comment_item_mimetype,
@@ -35,6 +36,8 @@ class BaseWebAPITestCase(WebAPITestCaseMixin, TestCase, EmailTestHelper):
         self.siteconfig = SiteConfiguration.objects.get_current()
         self.siteconfig.set("mail_send_review_mail", False)
         self.siteconfig.set("auth_require_sitewide_login", False)
+        self.siteconfig.set("enabled_features", {
+            extended_group_search_feature.feature_id: True})
         self.siteconfig.save()
         self._saved_siteconfig_settings = self.siteconfig.settings.copy()
 
diff --git a/reviewboard/webapi/tests/test_review_group.py b/reviewboard/webapi/tests/test_review_group.py
index b96ab6195327f42eaf4cc17998c3aba24c8777f0..5de53d4e08f419c475e2a366dde0e800cfe98cdd 100644
--- a/reviewboard/webapi/tests/test_review_group.py
+++ b/reviewboard/webapi/tests/test_review_group.py
@@ -69,7 +69,32 @@ class ResourceListTests(ExtraDataListMixin, BaseWebAPITestCase):
         rsp = self.api_get(get_review_group_list_url(), {'q': 'dev'},
                            expected_mimetype=review_group_list_mimetype)
         self.assertEqual(rsp['stat'], 'ok')
-        self.assertEqual(len(rsp['groups']), 1)  # devgroup
+        self.assertEqual(len(rsp['groups']), 1)
+        self.assertEqual(rsp['groups'][0]['name'], 'devgroup')
+
+    def test_get_with_q_contains(self):
+        """Testing the GET groups/?contains=1&q= API"""
+        self.create_review_group(name='group_devel', display_name='Not docs')
+        self.create_review_group(name='group_docs', display_name='Not devel')
+
+        rsp = self.api_get(get_review_group_list_url(),
+                           {'q': 'dev', 'contains': 1},
+                           expected_mimetype=review_group_list_mimetype)
+        self.assertEqual(rsp['stat'], 'ok')
+        self.assertEqual(len(rsp['groups']), 1)
+        self.assertEqual(rsp['groups'][0]['name'], 'group_devel')
+
+    def test_get_with_displayname_contains(self):
+        """Testing the GET groups/?displayname=1&contains=1&q= API"""
+        self.create_review_group(name='group_1', display_name='This is devel')
+        self.create_review_group(name='group_2', display_name='This is docs')
+
+        rsp = self.api_get(get_review_group_list_url(),
+                           {'q': 'dev', 'displayname': 1, 'contains': 1},
+                           expected_mimetype=review_group_list_mimetype)
+        self.assertEqual(rsp['stat'], 'ok')
+        self.assertEqual(len(rsp['groups']), 1)
+        self.assertEqual(rsp['groups'][0]['name'], 'group_1')
 
     #
     # HTTP POST tests
