diff --git a/docs/manual/extending/extensions/hooks/index.rst b/docs/manual/extending/extensions/hooks/index.rst
index a030b56afea3413b825e7363a6768b82cf158587..a79fa1c873cdc3c1d132d5c1664d7cbf9a56a962 100644
--- a/docs/manual/extending/extensions/hooks/index.rst
+++ b/docs/manual/extending/extensions/hooks/index.rst
@@ -25,6 +25,7 @@ Extension Hooks
    integration-hook
    navigation-bar-hook
    review-request-approval-hook
+   review-request-condition-choices-hook
    review-request-fields-hook
    review-request-fieldsets-hook
    review-ui-hook
diff --git a/docs/manual/extending/extensions/hooks/review-request-condition-choices-hook.rst b/docs/manual/extending/extensions/hooks/review-request-condition-choices-hook.rst
new file mode 100644
index 0000000000000000000000000000000000000000..11ba48202590aafedca850f0ddd6426d70c6d7f7
--- /dev/null
+++ b/docs/manual/extending/extensions/hooks/review-request-condition-choices-hook.rst
@@ -0,0 +1,191 @@
+.. _review-request-condition-choices-hook:
+
+=================================
+ReviewRequestConditionChoicesHook
+=================================
+
+.. versionadded:: 7.1
+
+When configuring integrations_, administrators can choose the conditions in
+which an integration will apply. For example, a Slack integration can be
+configured to post review request updates to a channel only when the review
+request is assigned to certain teams.
+
+By default, Review Board provides a number of conditions to choose from, but
+extensions can add custom choices using
+:py:class:`~reviewboard.extensions.hooks.conditions.
+ReviewRequestConditionChoicesHook`.
+
+This is particularly useful in combination with :ref:`custom fields
+<review-request-fields-hook>` or when storing custom data in review requests
+:ref:`via the API <webapi2.0-extra-data>` or extensions.
+
+
+Creating Choices
+================
+
+When creating a custom choice, you'll want to subclass one of the following
+base classes:
+
+* :py:class:`djblets.conditions.choices.BaseConditionChoice`:
+
+  The top-most base class used for condition choices. This is the right base
+  class when you need specialized logic and none of the other base classes
+  are appropriate.
+
+* :py:class:`djblets.conditions.choices.BaseConditionBooleanChoice`:
+
+  Base class for boolean choices. This provides the following operators:
+
+  * ``[Choice] Is [True/False]``
+
+* :py:class:`djblets.conditions.choices.BaseConditionIntegerChoice`:
+
+  Base class for integer choices. This provides the following operators:
+
+  * ``[Choice] Is [Integer]``
+  * ``[Choice] Is Not [Integer]``
+  * ``[Choice] Greater Than [Integer]``
+  * ``[Choice] Less Than [Integer]``
+
+* :py:class:`djblets.conditions.choices.BaseConditionStringChoice`:
+
+  Base class for string (text) choices. This provides the following operators:
+
+  * ``[Choice] Is [String]``
+  * ``[Choice] Is Not [String]``
+  * ``[Choice] Contains [String]``
+  * ``[Choice] Does Not Contain [String]``
+  * ``[Choice] Starts With [String]``
+  * ``[Choice] Ends With [String]``
+  * ``[Choice] Matches Regex [String]``
+  * ``[Choice] Does Not Match Regex [String]``
+
+* :py:class:`djblets.conditions.choices.BaseConditionModelChoice`:
+
+  Base class for choices that query the database, allowing users to select
+  a single database entry from a list to match. This provides the following
+  operators:
+
+  * ``[Choice] Is Unset``
+  * ``[Choice] Is [Database Entry]``
+  * ``[Choice] Is Not [Database Entry]``
+
+* :py:class:`djblets.conditions.choices.BaseConditionModelMultipleChoice`:
+
+  Base class for choices that query the database, allowing users to select
+  multiple database entries from a list to match. This provides the following
+  operators:
+
+  * ``[Choice] Is Any``
+  * ``[Choice] Is None``
+  * ``[Choice] Is One Of [One or More Database Entries]``
+  * ``[Choice] Is Not One Of [One or More Database Entries]``
+
+
+You will also want to mix in
+:py:class:`reviewboard.reviews.conditions.ReviewRequestConditionChoiceMixin`.
+
+For each choice, you will need to define the following:
+
+* :py:attr:`~djblets.conditions.choices.BaseConditionChoice.choice_id`:
+
+  A unique ID for the choice. It's recommended to prefix this with your
+  vendor or extension ID, so that you don't conflict with other choices.
+
+  Allowed characters are ``A-Z``, ``a-z``, ``0-9``, ``-``, and ``_``.
+
+* :py:attr:`~djblets.conditions.choices.BaseConditionChoice.name`:
+
+  The localizable display name for the choice.
+
+* :py:meth:`~djblets.conditions.choices.BaseConditionChoice.get_match_value`:
+
+  This method must take in the review request as an argument and return the
+  value the condition choice represents.
+
+You can also override:
+
+* :py:attr:`~djblets.conditions.choices.BaseConditionChoice.operators`:
+
+  An explicit list of operators to use for your choice. Useful if you want
+  to limit the list of operators, add custom operators, or define custom
+  labels for operators.
+
+* :py:attr:`~djblets.conditions.choices.BaseConditionChoice.default_value_field`:
+
+  An instance or function returning an instance of a
+  :py:class:`~djblets.conditions.values.BaseConditionValueField` from
+  :py:mod:`djblets.conditions.values`.
+
+  These wrap a form field for providing a value to match against. You can
+  use these to craft custom fields.
+
+  In particular, you may want to use
+  :py:class:`djblets.conditions.values.ConditionValueMultipleChoiceField` to
+  provide an explicit set of values.
+
+
+Example
+=======
+
+The following extension will match conditions against in-house
+``my_category`` and ``my_task_id`` extra data set on a review request via
+an extension or an API, or provided by a custom field.
+
+
+.. code-block:: python
+
+    from django.utils.translation import gettext_lazy as _
+    from djblets.conditions.choices import (BaseConditionIntegerChoice,
+                                            BaseConditionStringChoice
+    from djblets.conditions.operators import (ConditionOperators,
+                                              IsNotOneOfOperator,
+                                              IsOneOfOperator,
+                                              UnsetOperator)
+    from djblets.conditions.values import ConditionValueMultipleChoiceField
+    from reviewboard.extensions.base import Extension
+    from reviewboard.extensions.hooks import ReviewRequestConditionChoicesHook
+    from reviewboard.reviews.conditions import ReviewRequestConditionChoiceMixin
+
+
+    class MyCategoryChoice(ReviewRequestConditionChoiceMixin,
+                           BaseConditionStringChoice):
+        choice_id = 'my-category'
+        name = _('Category')
+
+        operators = ConditionOperators([
+            UnsetOperator,
+            IsOneOfOperator,
+            IsNotOneOfOperator,
+        ])
+
+        default_value_field = ConditionValueMultipleChoiceField[str](choices=[
+            ('architecture', _('Architecture')),
+            ('bug', _('Bug')),
+            ('docs', _('Documentation')),
+            ('feature', _('Feature')),
+            ('security', _('Security')),
+        ])
+
+        def get_match_value(self, review_request, **kwargs):
+            # This would return a string.
+            return review_request.extra_data.get('my_category')
+
+
+    class MyTaskIDChoice(ReviewRequestConditionChoiceMixin,
+                         BaseConditionIntegerChoice):
+        choice_id = 'my-task-id'
+        name = _('Task ID')
+
+        def get_match_value(self, review_request, **kwargs):
+            # This would return an integer.
+            return review_request.extra_data.get('my_task_id')
+
+
+    class SampleExtension(Extension):
+        def initialize(self):
+            ReviewRequestConditionChoicesHook(self, [
+                MyCategoryChoice,
+                MyTaskIDChoice,
+            ])
diff --git a/docs/manual/extending/index.rst b/docs/manual/extending/index.rst
index 37a6a72f44d66fd5a931497ab794e88eff42d47d..fbe7dd7a1672bedf37b5d2af0beb2484ef329bc7 100644
--- a/docs/manual/extending/index.rst
+++ b/docs/manual/extending/index.rst
@@ -204,12 +204,16 @@ extension hooks available to you.
     :ref:`hosting-service-hook`:
         Adds support for new source code or bug tracker hosting services.
 
-    :ref:`scmtool-hook`:
-        Adds support for new SCM implementations.
-
     :ref:`integration-hook`:
         Adds new integration options for services and tools.
 
+    :ref:`review-request-condition-choices-hook`:
+        Adds new condition choices available for matching review requests
+        when configuring integrations.
+
+    :ref:`scmtool-hook`:
+        Adds support for new SCM implementations.
+
 
 .. _js-extensions-hooks:
 
diff --git a/reviewboard/extensions/hooks/__init__.py b/reviewboard/extensions/hooks/__init__.py
index ba5e09cc4dfda74b3ee15e1e651882e90af6c626..72cf7bcad1ee0dcda1bb8f7f7ecf3841978a4505 100644
--- a/reviewboard/extensions/hooks/__init__.py
+++ b/reviewboard/extensions/hooks/__init__.py
@@ -24,6 +24,8 @@ from reviewboard.extensions.hooks.auth_backend import AuthBackendHook
 from reviewboard.extensions.hooks.avatar_service import AvatarServiceHook
 from reviewboard.extensions.hooks.comment_detail_display import \
     CommentDetailDisplayHook
+from reviewboard.extensions.hooks.conditions import \
+    ReviewRequestConditionChoicesHook
 from reviewboard.extensions.hooks.dashboard import (DashboardColumnsHook,
                                                     DashboardSidebarItemsHook,
                                                     DataGridSidebarItemsHook,
@@ -81,6 +83,7 @@ __all__ = [
     'ReviewRequestActionHook',
     'ReviewRequestApprovalHook',
     'ReviewRequestClosedEmailHook',
+    'ReviewRequestConditionChoicesHook',
     'ReviewRequestDropdownActionHook',
     'ReviewRequestFieldSetsHook',
     'ReviewRequestFieldsHook',
diff --git a/reviewboard/extensions/hooks/conditions.py b/reviewboard/extensions/hooks/conditions.py
new file mode 100644
index 0000000000000000000000000000000000000000..5134656dac78bd97b7acf8b546c7f02989a4693e
--- /dev/null
+++ b/reviewboard/extensions/hooks/conditions.py
@@ -0,0 +1,32 @@
+"""Hooks for working with condition choices.
+
+See :ref:`review-request-condition-choices-hook` for instructions.
+
+Version Added:
+    7.1
+"""
+
+from __future__ import annotations
+
+from typing import Type
+
+from djblets.conditions.choices import BaseConditionChoice
+from djblets.extensions.hooks import (BaseRegistryMultiItemHook,
+                                      ExtensionHookPoint)
+
+from reviewboard.reviews.conditions import review_request_condition_choices
+
+
+class ReviewRequestConditionChoicesHook(
+    BaseRegistryMultiItemHook[Type[BaseConditionChoice]],
+    metaclass=ExtensionHookPoint,
+):
+    """Hook to add custom condition choices for review requests.
+
+    See :ref:`review-request-condition-choices-hook` for instructions.
+
+    Version Added:
+        7.1
+    """
+
+    registry = review_request_condition_choices
diff --git a/reviewboard/extensions/tests/test_review_request_condition_choices_hook.py b/reviewboard/extensions/tests/test_review_request_condition_choices_hook.py
new file mode 100644
index 0000000000000000000000000000000000000000..73451e40ee7f57c66e078b1644524bf12fa7a0e1
--- /dev/null
+++ b/reviewboard/extensions/tests/test_review_request_condition_choices_hook.py
@@ -0,0 +1,77 @@
+"""Tests for reviewboard.extensions.hooks.ReviewRequestConditionChoicesHook.
+
+Version Added:
+    7.1
+"""
+
+from __future__ import annotations
+
+from djblets.conditions.choices import (BaseConditionIntegerChoice,
+                                        BaseConditionStringChoice)
+from djblets.conditions.operators import (ConditionOperators,
+                                          IsNotOneOfOperator,
+                                          IsOneOfOperator,
+                                          UnsetOperator)
+from djblets.conditions.values import ConditionValueMultipleChoiceField
+from reviewboard.extensions.hooks import ReviewRequestConditionChoicesHook
+from reviewboard.reviews.conditions import (ReviewRequestConditionChoiceMixin,
+                                            review_request_condition_choices)
+from reviewboard.extensions.tests.testcases import BaseExtensionHookTestCase
+
+
+class MyCategoryChoice(ReviewRequestConditionChoiceMixin,
+                       BaseConditionStringChoice):
+    choice_id = 'my-category'
+    name = 'Category'
+
+    operators = ConditionOperators([
+        UnsetOperator,
+        IsOneOfOperator,
+        IsNotOneOfOperator,
+    ])
+
+    default_value_field = ConditionValueMultipleChoiceField[str](choices=[
+       ('architecture', 'Architecture'),
+       ('bug', 'Bug'),
+       ('docs', 'Documentation'),
+       ('feature', 'Feature'),
+       ('security', 'Security'),
+    ])
+
+    def get_match_value(self, review_request, **kwargs):
+        return review_request.extra_data.get('my_category')
+
+
+class MyTaskIDChoice(ReviewRequestConditionChoiceMixin,
+                     BaseConditionIntegerChoice):
+    choice_id = 'my-task-id'
+    name = 'Task ID'
+
+    def get_match_value(self, review_request, **kwargs):
+        # This would return an integer.
+        return review_request.extra_data.get('my_task_id')
+
+
+class ReviewRequestConditionChoicesHookTests(BaseExtensionHookTestCase):
+    """Tests for ReviewRequestConditionChoicesHook."""
+
+    def test_register(self) -> None:
+        """Testing ReviewRequestConditionChoicesHook initializing"""
+        ReviewRequestConditionChoicesHook(self.extension, [
+            MyCategoryChoice,
+            MyTaskIDChoice,
+        ])
+
+        self.assertIn(MyCategoryChoice, review_request_condition_choices)
+        self.assertIn(MyTaskIDChoice, review_request_condition_choices)
+
+    def test_unregister(self) -> None:
+        """Testing ReviewRequestConditionChoicesHook uninitializing"""
+        hook = ReviewRequestConditionChoicesHook(self.extension, [
+            MyCategoryChoice,
+            MyTaskIDChoice,
+        ])
+        hook.disable_hook()
+
+        self.assertNotIn(MyCategoryChoice, review_request_condition_choices)
+        self.assertNotIn(MyTaskIDChoice, review_request_condition_choices)
diff --git a/reviewboard/reviews/conditions.py b/reviewboard/reviews/conditions.py
index 65faee68372db17ff647b3c97c5c5bb015e6f4bf..af70a9cc639234f880b3a0cd6072106eaadf7a9d 100644
--- a/reviewboard/reviews/conditions.py
+++ b/reviewboard/reviews/conditions.py
@@ -497,3 +497,12 @@ class ReviewRequestConditionChoices(ConditionChoices):
         ReviewRequestSummaryChoice,
         ReviewRequestTestingDoneChoice,
     ]
+
+
+#: All condition choices available for review requests.
+#:
+#: This can be used in condition fields or to register new conditions.
+#:
+#: Version Added:
+#:     7.1
+review_request_condition_choices = ReviewRequestConditionChoices()
diff --git a/reviewboard/reviews/forms.py b/reviewboard/reviews/forms.py
index be9f0f132d56862e3238c35ff48fdd05b5386d47..56c823ab21a6549619187934282532d04653413d 100644
--- a/reviewboard/reviews/forms.py
+++ b/reviewboard/reviews/forms.py
@@ -1,13 +1,20 @@
+"""Forms and fields related to review requests and operations."""
+
+from __future__ import annotations
+
 import re
+from typing import TYPE_CHECKING
 
 from django import forms
 from django.contrib.auth.models import User
 from django.core.exceptions import ValidationError
 from django.utils.translation import gettext, gettext_lazy as _
+from djblets.forms.fields import ConditionsField
 
 from reviewboard.admin.form_widgets import (RelatedGroupWidget,
                                             RelatedRepositoryWidget,
                                             RelatedUserWidget)
+from reviewboard.reviews.conditions import review_request_condition_choices
 from reviewboard.diffviewer import forms as diffviewer_forms
 from reviewboard.diffviewer.models import DiffSet
 from reviewboard.reviews.models import (DefaultReviewer, Group,
@@ -18,6 +25,10 @@ from reviewboard.site.validation import (validate_repositories,
                                          validate_review_groups,
                                          validate_users)
 
+if TYPE_CHECKING:
+    from djblets.forms.fields import ConditionsFieldChoices
+    from djblets.util.typing import StrOrPromise
+
 
 def regex_validator(value):
     """Validates that the specified regular expression is valid."""
@@ -272,3 +283,42 @@ class UploadScreenshotForm(forms.Form):
         draft.save()
 
         return screenshot
+
+
+class ReviewRequestConditionsField(ConditionsField):
+    """A field for selecting review request conditions.
+
+    This will include all conditions that are built into Review Board and
+    those registered by extensions.
+
+    Version Added:
+        7.1
+    """
+
+    def __init__(
+        self,
+        *,
+        choices: ConditionsFieldChoices = review_request_condition_choices,
+        label: StrOrPromise = _('Conditions'),
+        **kwargs,
+    ) -> None:
+        """Initialize the field.
+
+        Args:
+            choices (djblets.conditions.choices.ConditionChoices or type or
+                     callable):
+                The registry of choices available for the conditions.
+
+                This defaults to the standard review request condition choices.
+                It can be overridden to provide a more specialized list of
+                conditions.
+
+            label (str, optional):
+                A custom label to use for this field.
+
+            **kwargs (dict):
+                Keyword arguments to pass to the parent class.
+        """
+        super().__init__(choices=choices,
+                         label=label,
+                         **kwargs)
