diff --git a/include/libcamera/internal/bayer_format.h b/include/libcamera/internal/bayer_format.h
index 5b8c1dc9..723382d4 100644
--- a/include/libcamera/internal/bayer_format.h
+++ b/include/libcamera/internal/bayer_format.h
@@ -23,7 +23,8 @@ public:
 		BGGR = 0,
 		GBRG = 1,
 		GRBG = 2,
-		RGGB = 3
+		RGGB = 3,
+		MONO = 4
 	};
 
 	enum Packing : uint16_t {
diff --git a/src/libcamera/bayer_format.cpp b/src/libcamera/bayer_format.cpp
index ed61202c..11355f14 100644
--- a/src/libcamera/bayer_format.cpp
+++ b/src/libcamera/bayer_format.cpp
@@ -45,6 +45,8 @@ namespace libcamera {
  * \brief G then R on the first row, B then G on the second row.
  * \var BayerFormat::RGGB
  * \brief R then G on the first row, G then B on the second row.
+ * \var BayerFormat::MONO
+ * \brief Monochrome image data, there is no colour filter array.
  */
 
 /**
@@ -111,6 +113,8 @@ const std::map<BayerFormat, V4L2PixelFormat, BayerFormatComparator> bayerToV4l2{
 	{ { BayerFormat::GBRG, 16, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SGBRG16) },
 	{ { BayerFormat::GRBG, 16, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG16) },
 	{ { BayerFormat::RGGB, 16, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB16) },
+	{ { BayerFormat::MONO, 8, BayerFormat::None }, V4L2PixelFormat(V4L2_PIX_FMT_GREY) },
+	{ { BayerFormat::MONO, 10, BayerFormat::CSI2Packed }, V4L2PixelFormat(V4L2_PIX_FMT_Y10P) },
 };
 
 const std::unordered_map<unsigned int, BayerFormat> mbusCodeToBayer{
@@ -146,6 +150,8 @@ const std::unordered_map<unsigned int, BayerFormat> mbusCodeToBayer{
 	{ MEDIA_BUS_FMT_SGBRG16_1X16, { BayerFormat::GBRG, 16, BayerFormat::None } },
 	{ MEDIA_BUS_FMT_SGRBG16_1X16, { BayerFormat::GRBG, 16, BayerFormat::None } },
 	{ MEDIA_BUS_FMT_SRGGB16_1X16, { BayerFormat::RGGB, 16, BayerFormat::None } },
+	{ MEDIA_BUS_FMT_Y8_1X8, { BayerFormat::MONO, 8, BayerFormat::None } },
+	{ MEDIA_BUS_FMT_Y10_1X10, { BayerFormat::MONO, 10, BayerFormat::None } },
 };
 
 } /* namespace */
@@ -198,9 +204,10 @@ std::string BayerFormat::toString() const
 		"BGGR",
 		"GBRG",
 		"GRBG",
-		"RGGB"
+		"RGGB",
+		"MONO"
 	};
-	if (isValid() && order <= RGGB)
+	if (isValid() && order <= MONO)
 		result = orderStrings[order];
 	else
 		return "INVALID";
@@ -280,6 +287,9 @@ BayerFormat BayerFormat::transform(Transform t) const
 {
 	BayerFormat result = *this;
 
+	if (order == MONO)
+		return result;
+
 	/*
 	 * Observe that flipping bit 0 of the Order enum performs a horizontal
 	 * mirror on the Bayer pattern (e.g. RGGB goes to GRBG). Similarly,
diff --git a/src/libcamera/camera_sensor.cpp b/src/libcamera/camera_sensor.cpp
index 3e135353..fb67c15a 100644
--- a/src/libcamera/camera_sensor.cpp
+++ b/src/libcamera/camera_sensor.cpp
@@ -427,6 +427,9 @@ int CameraSensor::initProperties()
 		case BayerFormat::RGGB:
 			cfa = properties::draft::RGGB;
 			break;
+		case BayerFormat::MONO:
+			cfa = properties::draft::MONO;
+			break;
 		}
 
 		properties_.set(properties::draft::ColorFilterArrangement, cfa);
diff --git a/src/libcamera/property_ids.yaml b/src/libcamera/property_ids.yaml
index 104e9aaf..12ecbce5 100644
--- a/src/libcamera/property_ids.yaml
+++ b/src/libcamera/property_ids.yaml
@@ -706,5 +706,9 @@ controls:
           description: |
             Sensor is not Bayer; output has 3 16-bit values for each pixel,
             instead of just 1 16-bit value per pixel.
+        - name: MONO
+          value: 5
+          description: |
+            Sensor is not Bayer; output consists of a single colour channel.
 
 ...
