diff --git a/include/libcamera/buffer.h b/include/libcamera/buffer.h
index 3c430afbfe8e9a05..fe5195327b540f5c 100644
--- a/include/libcamera/buffer.h
+++ b/include/libcamera/buffer.h
@@ -171,6 +171,39 @@ private:
 	Stream *stream_;
 };
 
+class FrameBuffer final
+{
+public:
+	struct Plane {
+		int fd;
+		unsigned int length;
+	};
+
+	FrameBuffer(std::vector<Plane> planes, unsigned int cookie = 0);
+	~FrameBuffer();
+
+	Request *request() const { return request_; }
+	const BufferInfo &info() const { return info_; };
+	const std::vector<Dmabuf *> &dmabufs() { return dmabufs_; }
+
+	const std::vector<Plane> &planes() const { return planes_; }
+
+	unsigned int cookie() const { return cookie_; }
+	void setCookie(unsigned int cookie) { cookie_ = cookie; }
+
+private:
+	friend class Request; /* Needed to update request_. */
+	friend class V4L2VideoDevice; /* Needed to update info_. */
+
+	Request *request_;
+	BufferInfo info_;
+	std::vector<Dmabuf *> dmabufs_;
+
+	std::vector<Plane> planes_;
+
+	unsigned int cookie_;
+};
+
 } /* namespace libcamera */
 
 #endif /* __LIBCAMERA_BUFFER_H__ */
diff --git a/src/libcamera/buffer.cpp b/src/libcamera/buffer.cpp
index 5516055b2ea885c2..07647124a2cd9c62 100644
--- a/src/libcamera/buffer.cpp
+++ b/src/libcamera/buffer.cpp
@@ -412,7 +412,7 @@ void *Plane::mem()
 }
 
 /**
- * \fn Plane::length()
+ * \fn Plane::length() const
  * \brief Retrieve the length of the memory region
  * \return The length of the memory region
  */
@@ -645,4 +645,132 @@ void Buffer::cancel()
  * The intended callers are Request::prepare() and Request::completeBuffer().
  */
 
+/**
+ * \class FrameBuffer
+ * \brief A buffer handle and dynamic metadata
+ *
+ * The FrameBuffer class references a buffer memory and associates dynamic
+ * metadata related to the frame contained in the buffer. It allows referencing
+ * buffer memory.
+ *
+ * A FrameBuffer object can be created from both internal and externally
+ * allocated dmabuf.
+ */
+
+/**
+ * \struct FrameBuffer::Plane
+ * \brief Describe a DMA buffer using low level data
+ *
+ * The plane description contains a file descriptor and a length, together
+ * they represent a dmabuf.
+ */
+
+/**
+ * \var FrameBuffer::Plane::fd
+ * \brief The dmabuf file handle
+ */
+
+/**
+ * \var FrameBuffer::Plane::length
+ * \brief The dmabuf length
+ */
+
+/**
+ * \brief Create a libcamera FrameBuffer object from an array of dmabufs
+ * \param[in] planes dmabufs described as planes
+ * \param[in] cookie Opaque cookie for application use
+ *
+ * Buffers used by libcamera might be allocated externally or internally to
+ * libcamera, the FrameBuffer object handles both cases.
+ *
+ * The \a cookie is stored in the buffer and is accessible through the
+ * cookie() method at any time. It is typically used by user to map the
+ * buffer to an external resource, and is completely opaque to the FrameBuffer
+ * object.
+ */
+FrameBuffer::FrameBuffer(std::vector<Plane> planes, unsigned int cookie)
+	: request_(nullptr), cookie_(cookie)
+{
+	/* Clone all file descriptors and create dmabufs. */
+	for (Plane plane : planes)
+		dmabufs_.push_back(new Dmabuf(plane.fd, plane.length));
+
+	/* Cache the new plane description for this frame buffer. */
+	for (const Dmabuf *dmabuf : dmabufs_) {
+		Plane plane = {
+			.fd = dmabuf->fd(),
+			.length = dmabuf->length()
+		};
+
+		planes_.emplace_back(plane);
+	}
+}
+
+FrameBuffer::~FrameBuffer()
+{
+	for (Dmabuf *dmabuf : dmabufs_)
+		delete dmabuf;
+}
+
+/**
+ * \fn FrameBuffer::request()
+ * \brief Retrieve the request this buffer belongs to
+ *
+ * The intended callers of this method are buffer completion handlers that
+ * need to associate a buffer to the request it belongs to.
+ *
+ * A Buffer is associated to a request by Request::prepare() and the
+ * association is valid until the buffer completes. The returned request
+ * pointer is valid only during that interval.
+ *
+ * \return The Request the Buffer belongs to, or nullptr if the buffer is
+ * either completed or not associated with a request
+ */
+
+/**
+ * \fn FrameBuffer::info()
+ * \brief Retrieve the buffer metadata information
+ *
+ * The buffer metadata information is update every time the buffer contained
+ * are changed, for example when it is dequeued from hardware.
+ *
+ * \return Metadata of the buffer
+ */
+
+/**
+ * \fn FrameBuffer::dmabufs()
+ * \brief Retrieve the Dmabuf(s) from the buffer
+ *
+ * This is intended for applications who wish to access the frame buffer
+ * content. The array returned is one item for each plane in the frame buffer.
+ *
+ * \return Array of Dmabuf(s)
+ */
+
+/**
+ * \fn FrameBuffer::planes()
+ * \brief Retrieve the Dmabuf(s) as plane descriptions
+ *
+ * This is intended for internal libcamera use-cases where a frame buffer needs
+ * to be sent over an IPC barrier. Since IPC may be in the hot-path of capture
+ * a dedicated cached method is provided, the result might otherwise be
+ * constructed from information retrieved from dmabuf().
+ *
+ * The array returned is one item for each plane in the frame buffer.
+ *
+ * \return Array of Dmabuf(s) as plane descriptions
+ */
+
+/**
+ * \fn FrameBuffer::cookie()
+ * \brief Retrieve the cookie associated with the buffer
+ * \return The cookie
+ */
+
+/**
+ * \fn FrameBuffer::setCookie()
+ * \brief Set the cookie associated with the buffer
+ * \param[in] cookie The cookie to set on the request
+ */
+
 } /* namespace libcamera */
