diff --git a/src/libcamera/include/pipeline_handler.h b/src/libcamera/include/pipeline_handler.h
index 1fdef9cea40f1f0a..4400c7ed835f551d 100644
--- a/src/libcamera/include/pipeline_handler.h
+++ b/src/libcamera/include/pipeline_handler.h
@@ -14,6 +14,7 @@
 #include <string>
 #include <vector>
 
+#include <ipa/ipa_interface.h>
 #include <libcamera/controls.h>
 #include <libcamera/stream.h>
 
@@ -39,10 +40,13 @@ public:
 	}
 	virtual ~CameraData() {}
 
+	virtual int loadIPA() { return 0; };
+
 	Camera *camera_;
 	PipelineHandler *pipe_;
 	std::list<Request *> queuedRequests_;
 	ControlInfoMap controlInfo_;
+	std::unique_ptr<IPAInterface> ipa_;
 
 private:
 	CameraData(const CameraData &) = delete;
diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp
index f26a91f86ec1794c..2fa48586fea0b80e 100644
--- a/src/libcamera/pipeline/vimc.cpp
+++ b/src/libcamera/pipeline/vimc.cpp
@@ -52,6 +52,15 @@ public:
 		delete raw_;
 	}
 
+	int loadIPA() override
+	{
+		ipa_ = IPAManager::instance()->createIPA(pipe_, 0, 0);
+		if (!ipa_)
+			return -ENOENT;
+
+		return 0;
+	}
+
 	int init(MediaDevice *media);
 	void bufferReady(Buffer *buffer);
 
@@ -100,8 +109,6 @@ private:
 		return static_cast<VimcCameraData *>(
 			PipelineHandler::cameraData(camera));
 	}
-
-	std::unique_ptr<IPAInterface> ipa_;
 };
 
 VimcCameraConfiguration::VimcCameraConfiguration()
@@ -361,12 +368,6 @@ bool PipelineHandlerVimc::match(DeviceEnumerator *enumerator)
 	if (!media)
 		return false;
 
-	ipa_ = IPAManager::instance()->createIPA(this, 0, 0);
-	if (ipa_ == nullptr)
-		LOG(VIMC, Warning) << "no matching IPA found";
-	else
-		ipa_->init();
-
 	std::unique_ptr<VimcCameraData> data = utils::make_unique<VimcCameraData>(this);
 
 	/* Locate and open the capture video node. */
diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp
index 3e54aa23d92b9a36..b8a3787e10a587b5 100644
--- a/src/libcamera/pipeline_handler.cpp
+++ b/src/libcamera/pipeline_handler.cpp
@@ -12,6 +12,7 @@
 #include <libcamera/camera_manager.h>
 
 #include "device_enumerator.h"
+#include "ipa_manager.h"
 #include "log.h"
 #include "media_device.h"
 #include "utils.h"
@@ -58,6 +59,20 @@ LOG_DEFINE_CATEGORY(Pipeline)
  * exists.
  */
 
+/**
+ * \fn CameraData::loadIPA()
+ * \brief Load an IPA for the camera
+ *
+ * This function shall be implemented by pipeline handlers that wish to have an
+ * IPA. The function must locate and load an IPA, storing a pointer to it in
+ * the \a ipa_ or fail.
+ *
+ * If the pipeline handler do not wish to use an IPA this function shall not
+ * be implemented.
+ *
+ * \return True if a IPA could be loaded, false otherwise
+ */
+
 /**
  * \var CameraData::camera_
  * \brief The camera related to this CameraData instance
@@ -96,6 +111,14 @@ LOG_DEFINE_CATEGORY(Pipeline)
  * creating the camera, and shall not be modified afterwards.
  */
 
+/**
+ * \var CameraData::ipa_
+ * \brief The IPA module used by the camera
+ *
+ * Reference to the Image Processing Algorithms (IPA) operating on the camera's
+ * stream(s). If no IPAs are in operation this should be set to nullptr.
+ */
+
 /**
  * \class PipelineHandler
  * \brief Create and manage cameras based on a set of media devices
@@ -425,6 +448,16 @@ void PipelineHandler::registerCamera(std::shared_ptr<Camera> camera,
 				     std::unique_ptr<CameraData> data)
 {
 	data->camera_ = camera.get();
+
+	if (data->loadIPA()) {
+		LOG(Pipeline, Warning) << "Skipping " << camera->name()
+				       << " no IPA found";
+		return;
+	}
+
+	if (data->ipa_)
+		data->ipa_->init();
+
 	cameraData_[camera.get()] = std::move(data);
 	cameras_.push_back(camera);
 	manager_->addCamera(std::move(camera));
