diff --git a/docs/release-notes/1.10.0.md b/docs/release-notes/1.10.0.md
index 7d48d4fd..1bb50c89 100644
--- a/docs/release-notes/1.10.0.md
+++ b/docs/release-notes/1.10.0.md
@@ -18,6 +18,7 @@
 * {func}`scanpy.tl.rank_genes_groups` no longer warns that it's default was changed from t-test_overestim_var to t-test {pr}`2798` {smaller}`L Heumos`
 * {func}`scanpy.pp.highly_variable_genes` has new flavor `seurat_v3_paper` that is in its implementation consistent with the paper description in Stuart et al 2018. {pr}`2792` {smaller}`E Roellin`
 * {func}`scanpy.pp.highly_variable_genes` supports dask for the default `seurat` and `cell_ranger` flavors {pr}`2809` {smaller}`P Angerer`
+* Auto conversion of strings to collections in `scanpy.pp.calculate_qc_metrics` {pr}`2859` {smaller}`N Teyssier`
 
 ```{rubric} Docs
 ```
diff --git a/scanpy/preprocessing/_qc.py b/scanpy/preprocessing/_qc.py
index 241afb32..4d1123f6 100644
--- a/scanpy/preprocessing/_qc.py
+++ b/scanpy/preprocessing/_qc.py
@@ -233,7 +233,7 @@ def calculate_qc_metrics(
     *,
     expr_type: str = "counts",
     var_type: str = "genes",
-    qc_vars: Collection[str] = (),
+    qc_vars: Collection[str] | str = (),
     percent_top: Collection[int] | None = (50, 100, 200, 500),
     layer: str | None = None,
     use_raw: bool = False,
@@ -308,6 +308,10 @@ def calculate_qc_metrics(
     if issparse(X):
         X.eliminate_zeros()
 
+    # Convert qc_vars to list if str
+    if isinstance(qc_vars, str):
+        qc_vars = [qc_vars]
+
     obs_metrics = describe_obs(
         adata,
         expr_type=expr_type,

diff --git a/scanpy/tests/test_qc_metrics.py b/scanpy/tests/test_qc_metrics.py
index 06a4d0ce..83971fa2 100644
--- a/scanpy/tests/test_qc_metrics.py
+++ b/scanpy/tests/test_qc_metrics.py
@@ -143,6 +143,16 @@ def test_qc_metrics_format(cls):
         assert np.allclose(adata.var[col], adata_dense.var[col])
 
 
+def test_qc_metrics_format_str_qc_vars():
+    adata_dense, init_var = adata_mito()
+    sc.pp.calculate_qc_metrics(adata_dense, qc_vars="mito", inplace=True)
+    adata = AnnData(X=adata_dense.X, var=init_var.copy())
+    sc.pp.calculate_qc_metrics(adata, qc_vars="mito", inplace=True)
+    assert np.allclose(adata.obs, adata_dense.obs)
+    for col in adata.var:  # np.allclose doesn't like mix of types
+        assert np.allclose(adata.var[col], adata_dense.var[col])
+
+
 def test_qc_metrics_percentage():  # In response to #421
     adata_dense, init_var = adata_mito()
     sc.pp.calculate_qc_metrics(adata_dense, percent_top=[])

String conversion of `qc_vars` into a `Collection` when a `str` input is provided in `calculate_qc_metrics`
### What kind of feature would you like to request?

Additional function parameters / changed functionality / changed defaults?

### Please describe your wishes

I often make the mistake of providing a single string input to the `qc_vars` argument in `calculate_qc_metrics` which always fails with a `KeyError` because it beings to iterate over the string. This could be a very simple addition to just convert the argument into a `list` if a `str` is provided as input. 
scanpy/tests/test_qc_metrics.py::test_qc_metrics_format_str_qc_vars
scanpy/tests/test_qc_metrics.py::test_proportions[dense] scanpy/tests/test_qc_metrics.py::test_proportions[sparse] scanpy/tests/test_qc_metrics.py::test_segments_binary scanpy/tests/test_qc_metrics.py::test_top_segments[asarray] scanpy/tests/test_qc_metrics.py::test_top_segments[csr_matrix] scanpy/tests/test_qc_metrics.py::test_top_segments[csc_matrix] scanpy/tests/test_qc_metrics.py::test_top_segments[coo_matrix] scanpy/tests/test_qc_metrics.py::test_qc_metrics scanpy/tests/test_qc_metrics.py::test_qc_metrics_format[asarray] scanpy/tests/test_qc_metrics.py::test_qc_metrics_format[csr_matrix] scanpy/tests/test_qc_metrics.py::test_qc_metrics_format[csc_matrix] scanpy/tests/test_qc_metrics.py::test_qc_metrics_format[coo_matrix] scanpy/tests/test_qc_metrics.py::test_qc_metrics_percentage scanpy/tests/test_qc_metrics.py::test_layer_raw scanpy/tests/test_qc_metrics.py::test_inner_methods