diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index 927875586..cbfc06fb6 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -333,6 +333,7 @@ public:
 	};
 
 	std::vector<Stream> streams_;
+	Stream *rawStream_;
 
 	/*
 	 * All entities in the pipeline, from the camera sensor to the video
@@ -468,7 +469,7 @@ private:
 SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe,
 				   unsigned int numStreams,
 				   MediaEntity *sensor)
-	: Camera::Private(pipe), streams_(numStreams)
+	: Camera::Private(pipe), streams_(numStreams), rawStream_(nullptr)
 {
 	/*
 	 * Find the shortest path from the camera sensor to a video capture
@@ -884,10 +885,13 @@ void SimpleCameraData::imageBufferReady(FrameBuffer *buffer)
 	 * point converting an erroneous buffer.
 	 */
 	if (buffer->metadata().status != FrameMetadata::FrameSuccess) {
-		if (!useConversion_) {
+		if (!useConversion_ || rawStream_) {
 			/* No conversion, just complete the request. */
 			Request *request = buffer->request();
 			pipe->completeBuffer(request, buffer);
+			SimpleFrameInfo *info = frameInfo_.find(request->sequence());
+			if (info)
+				info->metadataRequired = false;
 			tryCompleteRequest(request);
 			return;
 		}
@@ -946,7 +950,8 @@ void SimpleCameraData::imageBufferReady(FrameBuffer *buffer)
 	 */
 	if (useConversion_) {
 		if (conversionQueue_.empty()) {
-			video_->queueBuffer(buffer);
+			if (!rawStream_)
+				video_->queueBuffer(buffer);
 			return;
 		}
 
@@ -998,8 +1003,15 @@ void SimpleCameraData::tryCompleteRequest(Request *request)
 
 void SimpleCameraData::conversionInputDone(FrameBuffer *buffer)
 {
-	/* Queue the input buffer back for capture. */
-	video_->queueBuffer(buffer);
+	if (rawStream_) {
+		/* Complete the input buffer as with raw-only processing. */
+		Request *request = buffer->request();
+		if (pipe()->completeBuffer(request, buffer))
+			tryCompleteRequest(request);
+	} else {
+		/* Queue the input buffer back for capture. */
+		video_->queueBuffer(buffer);
+	}
 }
 
 void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer)
@@ -1543,13 +1555,18 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
 	std::vector<std::reference_wrapper<StreamConfiguration>> outputCfgs;
 	data->useConversion_ = config->needConversion();
 
+	data->rawStream_ = nullptr;
 	for (unsigned int i = 0; i < config->size(); ++i) {
 		StreamConfiguration &cfg = config->at(i);
+		bool rawStream = isRaw(cfg);
 
 		cfg.setStream(&data->streams_[i]);
 
-		if (data->useConversion_ && !isRaw(cfg))
+		if (data->useConversion_ && !rawStream)
 			outputCfgs.push_back(cfg);
+
+		if (rawStream)
+			data->rawStream_ = &data->streams_[i];
 	}
 
 	if (outputCfgs.empty())
@@ -1580,7 +1597,7 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,
 	 * Export buffers on the converter or capture video node, depending on
 	 * whether the converter is used or not.
 	 */
-	if (data->useConversion_)
+	if (data->useConversion_ && stream != data->rawStream_)
 		return data->converter_
 			       ? data->converter_->exportBuffers(stream, count, buffers)
 			       : data->swIsp_->exportBuffers(stream, count, buffers);
@@ -1603,7 +1620,7 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL
 		return -EBUSY;
 	}
 
-	if (data->useConversion_) {
+	if (data->useConversion_ && !data->rawStream_) {
 		/*
 		 * When using the converter allocate a fixed number of internal
 		 * buffers.
@@ -1611,8 +1628,11 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL
 		ret = video->allocateBuffers(kNumInternalBuffers,
 					     &data->conversionBuffers_);
 	} else {
-		/* Otherwise, prepare for using buffers from the only stream. */
-		Stream *stream = &data->streams_[0];
+		/*
+		 * Otherwise, prepare for using buffers from either the raw stream, if
+		 * requested, or the only stream configured.
+		 */
+		Stream *stream = (data->rawStream_ ? data->rawStream_ : &data->streams_[0]);
 		ret = video->importBuffers(stream->configuration().bufferCount);
 	}
 	if (ret < 0) {
@@ -1653,8 +1673,9 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL
 		}
 
 		/* Queue all internal buffers for capture. */
-		for (std::unique_ptr<FrameBuffer> &buffer : data->conversionBuffers_)
-			video->queueBuffer(buffer.get());
+		if (!data->rawStream_)
+			for (std::unique_ptr<FrameBuffer> &buffer : data->conversionBuffers_)
+				video->queueBuffer(buffer.get());
 	}
 
 	return 0;
@@ -1705,7 +1726,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
 		 * queue, it will be handed to the converter in the capture
 		 * completion handler.
 		 */
-		if (data->useConversion_) {
+		if (data->useConversion_ && stream != data->rawStream_) {
 			buffers.emplace(stream, buffer);
 			metadataRequired = !!data->swIsp_;
 		} else {
