diff --git a/include/libcamera/internal/device_enumerator.h b/include/libcamera/internal/device_enumerator.h
index 5e1aec9924c5..14fd5750a0ab 100644
--- a/include/libcamera/internal/device_enumerator.h
+++ b/include/libcamera/internal/device_enumerator.h
@@ -45,6 +45,7 @@ public:
 	virtual int enumerate() = 0;
 
 	std::shared_ptr<MediaDevice> search(const DeviceMatch &dm);
+	std::vector<std::shared_ptr<MediaDevice>> searchAll(const DeviceMatch &dm) const;
 
 	Signal<> devicesAdded;
 
diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp
index 0ce65e89a67f..6eaa52604a50 100644
--- a/src/libcamera/device_enumerator.cpp
+++ b/src/libcamera/device_enumerator.cpp
@@ -362,4 +362,35 @@ std::shared_ptr<MediaDevice> DeviceEnumerator::search(const DeviceMatch &dm)
 	return nullptr;
 }
 
+/**
+ * \brief Search available media devices for all pattern matches
+ * \param[in] dm Search pattern
+ *
+ * Search in the enumerated media devices that are not already in use for
+ * matches described in \a dm. Unlike search(), all matching media devices are
+ * returned instead of the first match only. The caller shall not use the
+ * returned devices for capture without acquiring them first.
+ *
+ * \return A vector of matching MediaDevice instances, empty if no match is
+ * found
+ */
+std::vector<std::shared_ptr<MediaDevice>> DeviceEnumerator::searchAll(const DeviceMatch &dm) const
+{
+	std::vector<std::shared_ptr<MediaDevice>> matches;
+
+	for (const std::shared_ptr<MediaDevice> &media : devices_) {
+		if (media->busy())
+			continue;
+
+		if (dm.match(media.get())) {
+			LOG(DeviceEnumerator, Debug)
+				<< "Successful match for media device \""
+				<< media->driver() << "\"";
+			matches.push_back(media);
+		}
+	}
+
+	return matches;
+}
+
 } /* namespace libcamera */
