diff --git a/falcon/request.py b/falcon/request.py
index c7322b4..eac9b95 100644
--- a/falcon/request.py
+++ b/falcon/request.py
@@ -167,19 +167,13 @@ class Request(object):
 
             Note:
                 If an HTML form is POSTed to the API using the
-                *application/x-www-form-urlencoded* media type, Falcon
+                *application/x-www-form-urlencoded* media type, and
+                the :py:attr:`~.RequestOptions.auto_parse_form_urlencoded`
+                option is set, the framework
                 will consume `stream` in order to parse the parameters
                 and merge them into the query string parameters. In this
                 case, the stream will be left at EOF.
 
-                Note also that the character encoding for fields, before
-                percent-encoding non-ASCII bytes, is assumed to be
-                UTF-8. The special `_charset_` field is ignored if present.
-
-                Falcon expects form-encoded request bodies to be
-                encoded according to the standard W3C algorithm (see
-                also http://goo.gl/6rlcux).
-
         date (datetime): Value of the Date header, converted to a
             ``datetime`` instance. The header value is assumed to
             conform to RFC 1123.
@@ -320,7 +314,8 @@ class Request(object):
         # PERF(kgriffs): Technically, we should spend a few more
         # cycles and parse the content type for real, but
         # this heuristic will work virtually all the time.
-        if (self.content_type is not None and
+        if (self.options.auto_parse_form_urlencoded and
+                self.content_type is not None and
                 'application/x-www-form-urlencoded' in self.content_type):
             self._parse_form_urlencoded()
 
@@ -1159,11 +1154,28 @@ class RequestOptions(object):
     Attributes:
         keep_blank_qs_values (bool): Set to ``True`` in order to retain
             blank values in query string parameters (default ``False``).
+        auto_parse_form_urlencoded: Set to ``True`` in order to
+            automatically consume the request stream and merge the
+            results into the request's query string params when the
+            request's content type is
+            *application/x-www-form-urlencoded* (default ``False``). In
+            this case, the request's content stream will be left at EOF.
+
+            Note:
+                The character encoding for fields, before
+                percent-encoding non-ASCII bytes, is assumed to be
+                UTF-8. The special `_charset_` field is ignored if present.
+
+                Falcon expects form-encoded request bodies to be
+                encoded according to the standard W3C algorithm (see
+                also http://goo.gl/6rlcux).
 
     """
     __slots__ = (
         'keep_blank_qs_values',
+        'auto_parse_form_urlencoded',
     )
 
     def __init__(self):
         self.keep_blank_qs_values = False
+        self.auto_parse_form_urlencoded = False

Add an option to RequestOptions to toggle parsing of form params
Add an option to RequestOptions to enable/disable parsing form params as currently implemented. It would be disabled by default (breaking change, but easy to work around). This would mitigate the problems people have experienced re the request stream being automatically slurped when the POSTed content type is `application/x-www-form-urlencoded`. 

This would not preclude working on a longer-term strategy for handling forms in a manner distinct from query parameters.

See also:
- #418
- #493
diff --git a/tests/test_query_params.py b/tests/test_query_params.py
index 50ed010..93a429f 100644
--- a/tests/test_query_params.py
+++ b/tests/test_query_params.py
@@ -473,6 +473,10 @@ class _TestQueryParams(testing.TestBase):
 
 
 class PostQueryParams(_TestQueryParams):
+    def before(self):
+        super(PostQueryParams, self).before()
+        self.api.req_options.auto_parse_form_urlencoded = True
+
     def simulate_request(self, path, query_string, **kwargs):
         headers = {"Content-Type": "application/x-www-form-urlencoded"}
         super(PostQueryParams, self).simulate_request(
@@ -484,10 +488,29 @@ class PostQueryParams(_TestQueryParams):
         self.simulate_request('/', query_string=query_string)
 
         req = self.resource.req
-        self.assertEqual(req.get_param('q'), None)
+        self.assertIs(req.get_param('q'), None)
+
+    def test_explicitly_disable_auto_parse(self):
+        self.api.req_options.auto_parse_form_urlencoded = False
+        self.simulate_request('/', query_string='q=42')
+
+        req = self.resource.req
+        self.assertIs(req.get_param('q'), None)
 
 
 class GetQueryParams(_TestQueryParams):
     def simulate_request(self, path, query_string, **kwargs):
         super(GetQueryParams, self).simulate_request(
             path, query_string=query_string, **kwargs)
+
+
+class PostQueryParamsDefaultBehavior(testing.TestBase):
+    def test_dont_auto_parse_by_default(self):
+        self.resource = testing.TestResource()
+        self.api.add_route('/', self.resource)
+
+        headers = {"Content-Type": "application/x-www-form-urlencoded"}
+        self.simulate_request('/', body='q=42', headers=headers)
+
+        req = self.resource.req
+        self.assertIs(req.get_param('q'), None)

pip install -e .
parse_log_pytest
requirements.txt
nose coverage ddt pyyaml requests testtools pytest
apt-get update apt-get install -y gcc
3.9
tools/test-requires
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi==2025.1.31
charset-normalizer==3.4.1
coverage==7.8.0
ddt==1.7.2
exceptiongroup==1.2.2
-e git+https://github.com/falconry/falcon.git@7bffb3342fea9fff5677c43e89e7e6eccfc388ed#egg=falcon
idna==3.10
iniconfig==2.1.0
nose==1.3.7
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
python-mimeparse==2.0.0
PyYAML==6.0.2
requests==2.32.3
six==1.17.0
testtools==2.7.2
tomli==2.2.1
urllib3==2.3.0

name: falcon
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - coverage==7.8.0
      - ddt==1.7.2
      - exceptiongroup==1.2.2
      - idna==3.10
      - iniconfig==2.1.0
      - nose==1.3.7
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - python-mimeparse==2.0.0
      - pyyaml==6.0.2
      - requests==2.32.3
      - six==1.17.0
      - testtools==2.7.2
      - tomli==2.2.1
      - urllib3==2.3.0
prefix: /opt/conda/envs/falcon

tests/test_query_params.py::PostQueryParams::test_allowed_names tests/test_query_params.py::PostQueryParams::test_bad_percentage tests/test_query_params.py::PostQueryParams::test_blank tests/test_query_params.py::PostQueryParams::test_boolean tests/test_query_params.py::PostQueryParams::test_boolean_blank tests/test_query_params.py::PostQueryParams::test_explicitly_disable_auto_parse tests/test_query_params.py::PostQueryParams::test_get_date_invalid tests/test_query_params.py::PostQueryParams::test_get_date_missing_param tests/test_query_params.py::PostQueryParams::test_get_date_store tests/test_query_params.py::PostQueryParams::test_get_date_valid tests/test_query_params.py::PostQueryParams::test_get_date_valid_with_format tests/test_query_params.py::PostQueryParams::test_int tests/test_query_params.py::PostQueryParams::test_int_neg tests/test_query_params.py::PostQueryParams::test_list_transformer tests/test_query_params.py::PostQueryParams::test_list_type tests/test_query_params.py::PostQueryParams::test_list_type_blank tests/test_query_params.py::PostQueryParams::test_multiple_form_keys tests/test_query_params.py::PostQueryParams::test_multiple_form_keys_as_list tests/test_query_params.py::PostQueryParams::test_multiple_keys_as_bool tests/test_query_params.py::PostQueryParams::test_multiple_keys_as_int tests/test_query_params.py::PostQueryParams::test_non_ascii tests/test_query_params.py::PostQueryParams::test_none tests/test_query_params.py::PostQueryParams::test_param_property tests/test_query_params.py::PostQueryParams::test_percent_encoded tests/test_query_params.py::PostQueryParams::test_required_1_get_param tests/test_query_params.py::PostQueryParams::test_required_2_get_param_as_int tests/test_query_params.py::PostQueryParams::test_required_3_get_param_as_bool tests/test_query_params.py::PostQueryParams::test_required_4_get_param_as_list tests/test_query_params.py::PostQueryParams::test_simple tests/test_query_params.py::PostQueryParamsDefaultBehavior::test_dont_auto_parse_by_default
tests/test_query_params.py::_TestQueryParams::test_allowed_names tests/test_query_params.py::_TestQueryParams::test_bad_percentage tests/test_query_params.py::_TestQueryParams::test_blank tests/test_query_params.py::_TestQueryParams::test_boolean tests/test_query_params.py::_TestQueryParams::test_boolean_blank tests/test_query_params.py::_TestQueryParams::test_get_date_invalid tests/test_query_params.py::_TestQueryParams::test_get_date_missing_param tests/test_query_params.py::_TestQueryParams::test_get_date_store tests/test_query_params.py::_TestQueryParams::test_get_date_valid tests/test_query_params.py::_TestQueryParams::test_get_date_valid_with_format tests/test_query_params.py::_TestQueryParams::test_int tests/test_query_params.py::_TestQueryParams::test_int_neg tests/test_query_params.py::_TestQueryParams::test_list_transformer tests/test_query_params.py::_TestQueryParams::test_list_type tests/test_query_params.py::_TestQueryParams::test_list_type_blank tests/test_query_params.py::_TestQueryParams::test_multiple_form_keys tests/test_query_params.py::_TestQueryParams::test_multiple_form_keys_as_list tests/test_query_params.py::_TestQueryParams::test_multiple_keys_as_bool tests/test_query_params.py::_TestQueryParams::test_multiple_keys_as_int tests/test_query_params.py::_TestQueryParams::test_none tests/test_query_params.py::_TestQueryParams::test_param_property tests/test_query_params.py::_TestQueryParams::test_percent_encoded tests/test_query_params.py::_TestQueryParams::test_required_1_get_param tests/test_query_params.py::_TestQueryParams::test_required_2_get_param_as_int tests/test_query_params.py::_TestQueryParams::test_required_3_get_param_as_bool tests/test_query_params.py::_TestQueryParams::test_required_4_get_param_as_list tests/test_query_params.py::_TestQueryParams::test_simple tests/test_query_params.py::GetQueryParams::test_allowed_names tests/test_query_params.py::GetQueryParams::test_bad_percentage tests/test_query_params.py::GetQueryParams::test_blank tests/test_query_params.py::GetQueryParams::test_boolean tests/test_query_params.py::GetQueryParams::test_boolean_blank tests/test_query_params.py::GetQueryParams::test_get_date_invalid tests/test_query_params.py::GetQueryParams::test_get_date_missing_param tests/test_query_params.py::GetQueryParams::test_get_date_store tests/test_query_params.py::GetQueryParams::test_get_date_valid tests/test_query_params.py::GetQueryParams::test_get_date_valid_with_format tests/test_query_params.py::GetQueryParams::test_int tests/test_query_params.py::GetQueryParams::test_int_neg tests/test_query_params.py::GetQueryParams::test_list_transformer tests/test_query_params.py::GetQueryParams::test_list_type tests/test_query_params.py::GetQueryParams::test_list_type_blank tests/test_query_params.py::GetQueryParams::test_multiple_form_keys tests/test_query_params.py::GetQueryParams::test_multiple_form_keys_as_list tests/test_query_params.py::GetQueryParams::test_multiple_keys_as_bool tests/test_query_params.py::GetQueryParams::test_multiple_keys_as_int tests/test_query_params.py::GetQueryParams::test_none tests/test_query_params.py::GetQueryParams::test_param_property tests/test_query_params.py::GetQueryParams::test_percent_encoded tests/test_query_params.py::GetQueryParams::test_required_1_get_param tests/test_query_params.py::GetQueryParams::test_required_2_get_param_as_int tests/test_query_params.py::GetQueryParams::test_required_3_get_param_as_bool tests/test_query_params.py::GetQueryParams::test_required_4_get_param_as_list tests/test_query_params.py::GetQueryParams::test_simple
falcon/request.py
falcon/request.py