diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 8c56899..a88f6bc 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,16 @@
 Changelog
 ---------
 
+6.0.0b5 (Unreleased)
+********************
+
+Refactoring:
+
+* *Backwards-incompatible*: `DelimitedList` now requires that its input be a
+  string and always serializes as a string. It can still serialize and deserialize
+  using another field, e.g. `DelimitedList(Int())` is still valid and requires
+  that the values in the list parse as ints.
+
 6.0.0b4 (2020-01-28)
 ********************
 
diff --git a/src/webargs/fields.py b/src/webargs/fields.py
index 0d8d46b..fcdf05f 100644
--- a/src/webargs/fields.py
+++ b/src/webargs/fields.py
@@ -44,37 +44,35 @@ class Nested(ma.fields.Nested):
 
 
 class DelimitedList(ma.fields.List):
-    """Same as `marshmallow.fields.List`, except can load from either a list or
-    a delimited string (e.g. "foo,bar,baz").
+    """A field which is similar to a List, but takes its input as a delimited
+    string (e.g. "foo,bar,baz").
+
+    Like List, it can be given a nested field type which it will use to
+    de/serialize each element of the list.
 
     :param Field cls_or_instance: A field class or instance.
     :param str delimiter: Delimiter between values.
-    :param bool as_string: Dump values to string.
     """
 
+    default_error_messages = {"invalid": "Not a valid delimited list."}
     delimiter = ","
 
-    def __init__(self, cls_or_instance, delimiter=None, as_string=False, **kwargs):
+    def __init__(self, cls_or_instance, delimiter=None, **kwargs):
         self.delimiter = delimiter or self.delimiter
-        self.as_string = as_string
         super().__init__(cls_or_instance, **kwargs)
 
     def _serialize(self, value, attr, obj):
-        ret = super()._serialize(value, attr, obj)
-        if self.as_string:
-            return self.delimiter.join(format(each) for each in ret)
-        return ret
+        # serializing will start with List serialization, so that we correctly
+        # output lists of non-primitive types, e.g. DelimitedList(DateTime)
+        return self.delimiter.join(
+            format(each) for each in super()._serialize(value, attr, obj)
+        )
 
     def _deserialize(self, value, attr, data, **kwargs):
-        try:
-            ret = (
-                value
-                if ma.utils.is_iterable_but_not_string(value)
-                else value.split(self.delimiter)
-            )
-        except AttributeError:
+        # attempting to deserialize from a non-string source is an error
+        if not isinstance(value, (str, bytes)):
             if MARSHMALLOW_VERSION_INFO[0] < 3:
                 self.fail("invalid")
             else:
                 raise self.make_error("invalid")
-        return super()._deserialize(ret, attr, data, **kwargs)
+        return super()._deserialize(value.split(self.delimiter), attr, data, **kwargs)

RFC: Only accept delimited string in DelimitedList
`DelimitedList` accepts either a list or a delimited string (e.g. "foo,bar,baz").

I'd like to make it more strict by only accepting a delimited list. Rather than adding a `strict` parameter, I'm thinking of dropping the whole "also accept a list" feature.

Any reason to support both?

I understand it inherits from `List` because once the string is parsed, it can be deserialized as a normal list. But are there cases where you'd expect either a list or a delimited string?
diff --git a/tests/test_core.py b/tests/test_core.py
index 4ff341b..feef0c1 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -835,21 +835,6 @@ def test_delimited_list_default_delimiter(web_request, parser):
     parsed = parser.parse(schema, web_request)
     assert parsed["ids"] == [1, 2, 3]
 
-    dumped = schema.dump(parsed)
-    data = dumped.data if MARSHMALLOW_VERSION_INFO[0] < 3 else dumped
-    assert data["ids"] == [1, 2, 3]
-
-
-def test_delimited_list_as_string(web_request, parser):
-    web_request.json = {"ids": "1,2,3"}
-    schema_cls = dict2schema(
-        {"ids": fields.DelimitedList(fields.Int(), as_string=True)}
-    )
-    schema = schema_cls()
-
-    parsed = parser.parse(schema, web_request)
-    assert parsed["ids"] == [1, 2, 3]
-
     dumped = schema.dump(parsed)
     data = dumped.data if MARSHMALLOW_VERSION_INFO[0] < 3 else dumped
     assert data["ids"] == "1,2,3"
@@ -858,11 +843,7 @@ def test_delimited_list_as_string(web_request, parser):
 def test_delimited_list_as_string_v2(web_request, parser):
     web_request.json = {"dates": "2018-11-01,2018-11-02"}
     schema_cls = dict2schema(
-        {
-            "dates": fields.DelimitedList(
-                fields.DateTime(format="%Y-%m-%d"), as_string=True
-            )
-        }
+        {"dates": fields.DelimitedList(fields.DateTime(format="%Y-%m-%d"))}
     )
     schema = schema_cls()
 
@@ -886,13 +867,17 @@ def test_delimited_list_custom_delimiter(web_request, parser):
     assert parsed["ids"] == [1, 2, 3]
 
 
-def test_delimited_list_load_list(web_request, parser):
+def test_delimited_list_load_list_errors(web_request, parser):
     web_request.json = {"ids": [1, 2, 3]}
     schema_cls = dict2schema({"ids": fields.DelimitedList(fields.Int())})
     schema = schema_cls()
 
-    parsed = parser.parse(schema, web_request)
-    assert parsed["ids"] == [1, 2, 3]
+    with pytest.raises(ValidationError) as excinfo:
+        parser.parse(schema, web_request)
+    exc = excinfo.value
+    assert isinstance(exc, ValidationError)
+    errors = exc.args[0]
+    assert errors["ids"] == ["Not a valid delimited list."]
 
 
 # Regresion test for https://github.com/marshmallow-code/webargs/issues/149
@@ -903,7 +888,7 @@ def test_delimited_list_passed_invalid_type(web_request, parser):
 
     with pytest.raises(ValidationError) as excinfo:
         parser.parse(schema, web_request)
-    assert excinfo.value.messages == {"ids": ["Not a valid list."]}
+    assert excinfo.value.messages == {"ids": ["Not a valid delimited list."]}
 
 
 def test_missing_list_argument_not_in_parsed_result(web_request, parser):

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest tox pre-commit
apt-get update apt-get install -y gcc
3.7
examples/requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aiohttp==3.8.6
aiosignal==1.3.1
aniso8601==10.0.0
asgiref==3.7.2
async-timeout==4.0.3
asynctest==0.13.0
attrs==24.2.0
beautifulsoup4==4.13.3
bottle==0.13.2
cachetools==5.5.2
certifi @ file:///croot/certifi_1671487769961/work/certifi
cfgv==3.3.1
chardet==5.2.0
charset-normalizer==3.4.1
click==8.1.8
colorama==0.4.6
distlib==0.3.9
Django==3.2.25
entrypoints==0.3
exceptiongroup==1.2.2
falcon==3.1.3
filelock==3.12.2
flake8==3.7.9
flake8-bugbear==20.1.3
Flask==2.2.5
Flask-RESTful==0.3.10
frozenlist==1.3.3
hupper==1.12.1
identify==2.5.24
idna==3.10
importlib-metadata==6.7.0
iniconfig==2.0.0
itsdangerous==2.1.2
Jinja2==3.1.6
MarkupSafe==2.1.5
marshmallow==3.19.0
mccabe==0.6.1
multidict==6.0.5
mypy==0.761
mypy-extensions==0.4.4
nodeenv==1.9.1
packaging==24.0
PasteDeploy==3.1.0
plaster==1.1.2
plaster-pastedeploy==1.0.1
platformdirs==4.0.0
pluggy==1.2.0
pre-commit==2.21.0
pycodestyle==2.5.0
pyflakes==2.1.1
pyproject-api==1.5.3
pyramid==2.0.2
pytest==7.4.4
pytest-aiohttp==1.0.5
pytest-asyncio==0.21.2
python-dateutil==2.8.1
pytz==2025.2
PyYAML==6.0.1
six==1.17.0
soupsieve==2.4.1
sqlparse==0.4.4
tomli==2.0.1
tornado==6.2
tox==4.8.0
translationstring==1.4
typed-ast==1.4.3
typing_extensions==4.7.1
venusian==3.1.1
virtualenv==20.26.6
waitress==2.1.2
webapp2==3.0.0b1
-e git+https://github.com/marshmallow-code/webargs.git@01ef08a35a1ec9249725e54dc215efc682debfcf#egg=webargs
WebOb==1.8.9
WebTest==2.0.34
webtest-aiohttp==2.0.0
Werkzeug==2.2.3
yarl==1.9.4
zipp==3.15.0
zope.deprecation==5.0
zope.interface==6.4.post2

name: webargs
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
  - certifi=2022.12.7=py37h06a4308_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=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aiohttp==3.8.6
      - aiosignal==1.3.1
      - aniso8601==10.0.0
      - asgiref==3.7.2
      - async-timeout==4.0.3
      - asynctest==0.13.0
      - attrs==24.2.0
      - beautifulsoup4==4.13.3
      - bottle==0.13.2
      - cachetools==5.5.2
      - cfgv==3.3.1
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - click==8.1.8
      - colorama==0.4.6
      - distlib==0.3.9
      - django==3.2.25
      - entrypoints==0.3
      - exceptiongroup==1.2.2
      - falcon==3.1.3
      - filelock==3.12.2
      - flake8==3.7.9
      - flake8-bugbear==20.1.3
      - flask==2.2.5
      - flask-restful==0.3.10
      - frozenlist==1.3.3
      - hupper==1.12.1
      - identify==2.5.24
      - idna==3.10
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - itsdangerous==2.1.2
      - jinja2==3.1.6
      - markupsafe==2.1.5
      - marshmallow==3.19.0
      - mccabe==0.6.1
      - multidict==6.0.5
      - mypy==0.761
      - mypy-extensions==0.4.4
      - nodeenv==1.9.1
      - packaging==24.0
      - pastedeploy==3.1.0
      - plaster==1.1.2
      - plaster-pastedeploy==1.0.1
      - platformdirs==4.0.0
      - pluggy==1.2.0
      - pre-commit==2.21.0
      - pycodestyle==2.5.0
      - pyflakes==2.1.1
      - pyproject-api==1.5.3
      - pyramid==2.0.2
      - pytest==7.4.4
      - pytest-aiohttp==1.0.5
      - pytest-asyncio==0.21.2
      - python-dateutil==2.8.1
      - pytz==2025.2
      - pyyaml==6.0.1
      - six==1.17.0
      - soupsieve==2.4.1
      - sqlparse==0.4.4
      - tomli==2.0.1
      - tornado==6.2
      - tox==4.8.0
      - translationstring==1.4
      - typed-ast==1.4.3
      - typing-extensions==4.7.1
      - venusian==3.1.1
      - virtualenv==20.26.6
      - waitress==2.1.2
      - webapp2==3.0.0b1
      - webob==1.8.9
      - webtest==2.0.34
      - webtest-aiohttp==2.0.0
      - werkzeug==2.2.3
      - yarl==1.9.4
      - zipp==3.15.0
      - zope-deprecation==5.0
      - zope-interface==6.4.post2
prefix: /opt/conda/envs/webargs

tests/test_core.py::test_delimited_list_default_delimiter tests/test_core.py::test_delimited_list_as_string_v2 tests/test_core.py::test_delimited_list_load_list_errors tests/test_core.py::test_delimited_list_passed_invalid_type
tests/test_core.py::test_load_json_called_by_parse_default tests/test_core.py::test_load_nondefault_called_by_parse_with_location[querystring] tests/test_core.py::test_load_nondefault_called_by_parse_with_location[form] tests/test_core.py::test_load_nondefault_called_by_parse_with_location[headers] tests/test_core.py::test_load_nondefault_called_by_parse_with_location[cookies] tests/test_core.py::test_load_nondefault_called_by_parse_with_location[files] tests/test_core.py::test_parse tests/test_core.py::test_parse_with_unknown_behavior_specified tests/test_core.py::test_parse_required_arg_raises_validation_error tests/test_core.py::test_arg_not_required_excluded_in_parsed_output tests/test_core.py::test_arg_allow_none tests/test_core.py::test_parse_required_arg tests/test_core.py::test_parse_required_list tests/test_core.py::test_parse_list_allow_none tests/test_core.py::test_parse_list_dont_allow_none tests/test_core.py::test_parse_empty_list tests/test_core.py::test_parse_missing_list tests/test_core.py::test_default_location tests/test_core.py::test_missing_with_default tests/test_core.py::test_default_can_be_none tests/test_core.py::test_arg_with_default_and_location tests/test_core.py::test_value_error_raised_if_parse_called_with_invalid_location tests/test_core.py::test_handle_error_called_when_parsing_raises_error tests/test_core.py::test_handle_error_reraises_errors tests/test_core.py::test_location_as_init_argument tests/test_core.py::test_custom_error_handler tests/test_core.py::test_custom_error_handler_decorator tests/test_core.py::test_custom_location_loader tests/test_core.py::test_custom_location_loader_with_data_key tests/test_core.py::test_full_input_validation tests/test_core.py::test_full_input_validation_with_multiple_validators tests/test_core.py::test_required_with_custom_error tests/test_core.py::test_required_with_custom_error_and_validation_error tests/test_core.py::test_full_input_validator_receives_nonascii_input tests/test_core.py::test_invalid_argument_for_validate tests/test_core.py::test_multidict_proxy[input_dict0] tests/test_core.py::test_multidict_proxy[input_dict1] tests/test_core.py::test_multidict_proxy[input_dict2] tests/test_core.py::test_parse_with_data_key tests/test_core.py::test_parse_with_data_key_retains_field_name_in_error tests/test_core.py::test_parse_nested_with_data_key tests/test_core.py::test_parse_nested_with_missing_key_and_data_key tests/test_core.py::test_parse_nested_with_default tests/test_core.py::test_nested_many tests/test_core.py::test_use_args tests/test_core.py::test_use_args_stacked tests/test_core.py::test_use_kwargs_stacked tests/test_core.py::test_decorators_dont_change_docstring[use_args] tests/test_core.py::test_decorators_dont_change_docstring[use_kwargs] tests/test_core.py::test_list_allowed_missing tests/test_core.py::test_int_list_allowed_missing tests/test_core.py::test_multiple_arg_required_with_int_conversion tests/test_core.py::test_parse_with_callable tests/test_core.py::test_use_args_callable tests/test_core.py::TestPassingSchema::test_passing_schema_to_parse tests/test_core.py::TestPassingSchema::test_use_args_can_be_passed_a_schema tests/test_core.py::TestPassingSchema::test_passing_schema_factory_to_parse tests/test_core.py::TestPassingSchema::test_use_args_can_be_passed_a_schema_factory tests/test_core.py::TestPassingSchema::test_use_kwargs_can_be_passed_a_schema tests/test_core.py::TestPassingSchema::test_use_kwargs_can_be_passed_a_schema_factory tests/test_core.py::TestPassingSchema::test_use_kwargs_stacked tests/test_core.py::TestPassingSchema::test_parse_does_not_add_missing_values_to_schema_validator tests/test_core.py::test_use_args_with_custom_location_in_parser tests/test_core.py::test_use_kwargs tests/test_core.py::test_use_kwargs_with_arg_missing tests/test_core.py::test_delimited_list_custom_delimiter tests/test_core.py::test_missing_list_argument_not_in_parsed_result tests/test_core.py::test_type_conversion_with_multiple_required tests/test_core.py::test_validation_errors_in_validator_are_passed_to_handle_error tests/test_core.py::test_parse_basic tests/test_core.py::test_parse_raises_validation_error_if_data_invalid tests/test_core.py::test_dict2schema tests/test_core.py::test_dict2schema_doesnt_add_to_class_registry tests/test_core.py::test_dict2schema_with_nesting tests/test_core.py::test_is_json tests/test_core.py::test_get_mimetype tests/test_core.py::test_parse_with_error_status_code_and_headers tests/test_core.py::test_custom_schema_class tests/test_core.py::test_custom_default_schema_class
CHANGELOG.rst src/webargs/fields.py
CHANGELOG.rst src/webargs/fields.py