diff --git a/apispec/core.py b/apispec/core.py
index d7d2a00..9d8ca93 100644
--- a/apispec/core.py
+++ b/apispec/core.py
@@ -139,6 +139,7 @@ class Components(object):
         if extra_fields:
             ret.update(extra_fields)
         self._schemas[name] = ret
+        return self
 
     def parameter(self, param_id, location, **kwargs):
         """ Add a parameter which can be referenced.
@@ -157,6 +158,7 @@ class Components(object):
             except PluginMethodNotImplementedError:
                 continue
         self._parameters[param_id] = ret
+        return self
 
     def response(self, ref_id, **kwargs):
         """Add a response which can be referenced.
@@ -172,6 +174,7 @@ class Components(object):
             except PluginMethodNotImplementedError:
                 continue
         self._responses[ref_id] = ret
+        return self
 
 
 class APISpec(object):
@@ -233,6 +236,7 @@ class APISpec(object):
         :param dict tag: the dictionary storing information about the tag.
         """
         self._tags.append(tag)
+        return self
 
     def path(self, path=None, operations=None, **kwargs):
         """Add a new path object to the spec.
@@ -273,3 +277,4 @@ class APISpec(object):
         clean_operations(operations, self.openapi_version.major)
 
         self._paths.setdefault(path, operations).update(operations)
+        return self
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index c3816a5..1c4c885 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -47,6 +47,19 @@ Add paths to your spec using `path <apispec.APISpec.path>`.
         )
     )
 
+
+The API is chainable, allowing you to combine multiple method calls in
+one statement:
+
+.. code-block:: python
+
+    spec.path(...)\
+        .path(...)\
+        .tag(...)
+
+    spec.components.schema(...)\
+                   .parameter(...)
+
 To output your OpenAPI spec, invoke the `to_dict <apispec.APISpec.to_dict>` method.
 
 .. code-block:: python

Make APISpec and Components methods chainable
See discussion in https://github.com/marshmallow-code/apispec/pull/305.

Just let `path`, `tag`, etc. return `self`.
diff --git a/tests/test_core.py b/tests/test_core.py
index 99ec31d..3c2067f 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -110,6 +110,10 @@ class TestTags:
         tags_json = spec.to_dict()['tags']
         assert self.tag in tags_json
 
+    def test_tag_is_chainable(self, spec):
+        spec.tag({'name': 'tag1'}).tag({'name': 'tag2'})
+        assert spec.to_dict()['tags'] == [{'name': 'tag1'}, {'name': 'tag2'}]
+
 
 class TestDefinitions:
 
@@ -127,6 +131,15 @@ class TestDefinitions:
         assert 'Pet' in defs
         assert defs['Pet']['properties'] == self.properties
 
+    def test_definition_is_chainable(self, spec):
+        spec.components.schema('Pet', properties={}).schema('Plant', properties={})
+        if spec.openapi_version.major < 3:
+            defs = spec.to_dict()['definitions']
+        else:
+            defs = spec.to_dict()['components']['schemas']
+        assert 'Pet' in defs
+        assert 'Plant' in defs
+
     def test_definition_description(self, spec):
         model_description = 'An animal which lives with humans.'
         spec.components.schema('Pet', properties=self.properties, description=model_description)
@@ -240,6 +253,10 @@ class TestPath:
         spec.path(path='/path4')
         assert list(spec.to_dict()['paths'].keys()) == ['/path1', '/path2', '/path3', '/path4']
 
+    def test_paths_is_chainable(self, spec):
+        spec.path(path='/path1').path('/path2')
+        assert list(spec.to_dict()['paths'].keys()) == ['/path1', '/path2']
+
     def test_methods_maintain_order(self, spec):
         methods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options']
         for method in methods:
@@ -323,6 +340,15 @@ class TestPath:
             assert p['parameters'][0] == {'$ref': '#/components/parameters/test_parameter'}
             assert route_spec['parameters'][0] == metadata['components']['parameters']['test_parameter']
 
+    def test_parameter_is_chainable(self, spec):
+        spec.components.parameter('param1', 'path').parameter('param2', 'path')
+        if spec.openapi_version.major < 3:
+            params = spec.to_dict()['parameters']
+        else:
+            params = spec.to_dict()['components']['parameters']
+        assert 'param1' in params
+        assert 'param2' in params
+
     def test_response(self, spec):
         route_spec = self.paths['/pet/{petId}']['get']
 
@@ -343,6 +369,15 @@ class TestPath:
             assert p['responses']['200'] == {'$ref': '#/components/responses/test_response'}
             assert route_spec['responses']['200'] == metadata['components']['responses']['test_response']
 
+    def test_response_is_chainable(self, spec):
+        spec.components.response('resp1').response('resp2')
+        if spec.openapi_version.major < 3:
+            responses = spec.to_dict()['responses']
+        else:
+            responses = spec.to_dict()['components']['responses']
+        assert 'resp1' in responses
+        assert 'resp2' in responses
+
     def test_path_check_invalid_http_method(self, spec):
         spec.path('/pet/{petId}', operations={'get': {}})
         spec.path('/pet/{petId}', operations={'x-dummy': {}})

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
3.9
docs/requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
-e git+https://github.com/marshmallow-code/apispec.git@3f25523d8373e67278017e0e8c593cc65ea2894d#egg=apispec
aspy.yaml==1.3.0
attrs==25.3.0
babel==2.17.0
cached-property==2.0.1
cachetools==5.5.2
certifi==2025.1.31
cfgv==3.4.0
chardet==5.2.0
charset-normalizer==3.4.1
colorama==0.4.6
distlib==0.3.9
docutils==0.21.2
exceptiongroup==1.2.2
filelock==3.18.0
flake8==3.6.0
identify==2.6.9
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
Jinja2==3.1.6
jsonschema==4.17.3
jsonschema-spec==0.1.6
lazy-object-proxy==1.10.0
MarkupSafe==3.0.2
marshmallow==2.16.3
mccabe==0.6.1
mock==5.2.0
nodeenv==1.9.1
openapi-schema-validator==0.4.4
openapi-spec-validator==0.5.7
packaging==24.2
pathable==0.4.4
platformdirs==4.3.7
pluggy==1.5.0
prance==23.6.21.0
pre-commit==1.12.0
pycodestyle==2.4.0
pyflakes==2.0.0
Pygments==2.19.1
pyproject-api==1.9.0
pyrsistent==0.20.0
pytest==8.3.5
PyYAML==6.0.2
requests==2.32.3
rfc3339-validator==0.1.4
ruamel.yaml==0.18.10
ruamel.yaml.clib==0.2.12
six==1.17.0
snowballstemmer==2.2.0
Sphinx==1.8.1
sphinx-issues==1.1.0
sphinx-rtd-theme==0.4.2
sphinxcontrib-serializinghtml==2.0.0
sphinxcontrib-websupport==1.2.4
toml==0.10.2
tomli==2.2.1
tox==4.25.0
typing_extensions==4.13.0
urllib3==2.3.0
virtualenv==20.29.3
zipp==3.21.0

name: apispec
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:
      - alabaster==0.7.16
      - aspy-yaml==1.3.0
      - attrs==25.3.0
      - babel==2.17.0
      - cached-property==2.0.1
      - cachetools==5.5.2
      - certifi==2025.1.31
      - cfgv==3.4.0
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - colorama==0.4.6
      - distlib==0.3.9
      - docutils==0.21.2
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - flake8==3.6.0
      - identify==2.6.9
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - jinja2==3.1.6
      - jsonschema==4.17.3
      - jsonschema-spec==0.1.6
      - lazy-object-proxy==1.10.0
      - markupsafe==3.0.2
      - marshmallow==2.16.3
      - mccabe==0.6.1
      - mock==5.2.0
      - nodeenv==1.9.1
      - openapi-schema-validator==0.4.4
      - openapi-spec-validator==0.5.7
      - packaging==24.2
      - pathable==0.4.4
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - prance==23.6.21.0
      - pre-commit==1.12.0
      - pycodestyle==2.4.0
      - pyflakes==2.0.0
      - pygments==2.19.1
      - pyproject-api==1.9.0
      - pyrsistent==0.20.0
      - pytest==8.3.5
      - pyyaml==6.0.2
      - requests==2.32.3
      - rfc3339-validator==0.1.4
      - ruamel-yaml==0.18.10
      - ruamel-yaml-clib==0.2.12
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==1.8.1
      - sphinx-issues==1.1.0
      - sphinx-rtd-theme==0.4.2
      - sphinxcontrib-serializinghtml==2.0.0
      - sphinxcontrib-websupport==1.2.4
      - toml==0.10.2
      - tomli==2.2.1
      - tox==4.25.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - virtualenv==20.29.3
      - zipp==3.21.0
prefix: /opt/conda/envs/apispec

tests/test_core.py::TestTags::test_tag_is_chainable[2.0] tests/test_core.py::TestTags::test_tag_is_chainable[3.0.0] tests/test_core.py::TestDefinitions::test_definition_is_chainable[2.0] tests/test_core.py::TestDefinitions::test_definition_is_chainable[3.0.0] tests/test_core.py::TestPath::test_paths_is_chainable[2.0] tests/test_core.py::TestPath::test_paths_is_chainable[3.0.0] tests/test_core.py::TestPath::test_parameter_is_chainable[2.0] tests/test_core.py::TestPath::test_parameter_is_chainable[3.0.0] tests/test_core.py::TestPath::test_response_is_chainable[2.0] tests/test_core.py::TestPath::test_response_is_chainable[3.0.0]
tests/test_core.py::TestAPISpecInit::test_raises_wrong_apispec_version tests/test_core.py::TestMetadata::test_openapi_metadata[2.0] tests/test_core.py::TestMetadata::test_openapi_metadata[3.0.0] tests/test_core.py::TestMetadata::test_openapi_metadata_merge_v3[3.0.0] tests/test_core.py::TestTags::test_tag[2.0] tests/test_core.py::TestTags::test_tag[3.0.0] tests/test_core.py::TestDefinitions::test_definition[2.0] tests/test_core.py::TestDefinitions::test_definition[3.0.0] tests/test_core.py::TestDefinitions::test_definition_description[2.0] tests/test_core.py::TestDefinitions::test_definition_description[3.0.0] tests/test_core.py::TestDefinitions::test_definition_stores_enum[2.0] tests/test_core.py::TestDefinitions::test_definition_stores_enum[3.0.0] tests/test_core.py::TestDefinitions::test_definition_extra_fields[2.0] tests/test_core.py::TestDefinitions::test_definition_extra_fields[3.0.0] tests/test_core.py::TestDefinitions::test_to_yaml[2.0] tests/test_core.py::TestDefinitions::test_to_yaml[3.0.0] tests/test_core.py::TestPath::test_path[2.0] tests/test_core.py::TestPath::test_path[3.0.0] tests/test_core.py::TestPath::test_paths_maintain_order[2.0] tests/test_core.py::TestPath::test_paths_maintain_order[3.0.0] tests/test_core.py::TestPath::test_methods_maintain_order[2.0] tests/test_core.py::TestPath::test_methods_maintain_order[3.0.0] tests/test_core.py::TestPath::test_path_merges_paths[2.0] tests/test_core.py::TestPath::test_path_merges_paths[3.0.0] tests/test_core.py::TestPath::test_path_ensures_path_parameters_required[2.0] tests/test_core.py::TestPath::test_path_ensures_path_parameters_required[3.0.0] tests/test_core.py::TestPath::test_path_with_no_path_raises_error[2.0] tests/test_core.py::TestPath::test_path_with_no_path_raises_error[3.0.0] tests/test_core.py::TestPath::test_path_strips_base_path[2.0] tests/test_core.py::TestPath::test_path_strips_base_path[3.0.0] tests/test_core.py::TestPath::test_parameter[2.0] tests/test_core.py::TestPath::test_parameter[3.0.0] tests/test_core.py::TestPath::test_response[2.0] tests/test_core.py::TestPath::test_response[3.0.0] tests/test_core.py::TestPath::test_path_check_invalid_http_method[2.0] tests/test_core.py::TestPath::test_path_check_invalid_http_method[3.0.0] tests/test_core.py::TestPlugins::test_plugin_factory tests/test_core.py::TestPlugins::test_plugin_schema_helper_is_used[True-2.0] tests/test_core.py::TestPlugins::test_plugin_schema_helper_is_used[True-3.0.0] tests/test_core.py::TestPlugins::test_plugin_schema_helper_is_used[False-2.0] tests/test_core.py::TestPlugins::test_plugin_schema_helper_is_used[False-3.0.0] tests/test_core.py::TestPlugins::test_plugin_parameter_helper_is_used[True-2.0] tests/test_core.py::TestPlugins::test_plugin_parameter_helper_is_used[True-3.0.0] tests/test_core.py::TestPlugins::test_plugin_parameter_helper_is_used[False-2.0] tests/test_core.py::TestPlugins::test_plugin_parameter_helper_is_used[False-3.0.0] tests/test_core.py::TestPlugins::test_plugin_response_helper_is_used[True-2.0] tests/test_core.py::TestPlugins::test_plugin_response_helper_is_used[True-3.0.0] tests/test_core.py::TestPlugins::test_plugin_response_helper_is_used[False-2.0] tests/test_core.py::TestPlugins::test_plugin_response_helper_is_used[False-3.0.0] tests/test_core.py::TestPlugins::test_plugin_path_helper_is_used[True-2.0] tests/test_core.py::TestPlugins::test_plugin_path_helper_is_used[True-3.0.0] tests/test_core.py::TestPlugins::test_plugin_path_helper_is_used[False-2.0] tests/test_core.py::TestPlugins::test_plugin_path_helper_is_used[False-3.0.0] tests/test_core.py::TestPlugins::test_plugin_operation_helper_is_used[2.0] tests/test_core.py::TestPlugins::test_plugin_operation_helper_is_used[3.0.0] tests/test_core.py::TestPluginsOrder::test_plugins_order
apispec/core.py docs/quickstart.rst
apispec/core.py docs/quickstart.rst