diff --git a/src/cam/camera_session.cpp b/src/cam/camera_session.cpp
index 0bb95c8bf682..76d552d98917 100644
--- a/src/cam/camera_session.cpp
+++ b/src/cam/camera_session.cpp
@@ -14,6 +14,7 @@
 #include <libcamera/property_ids.h>
 
 #include "camera_session.h"
+#include "capture_script.h"
 #include "event_loop.h"
 #include "file_sink.h"
 #ifdef HAVE_KMS
@@ -91,6 +92,16 @@ CameraSession::CameraSession(CameraManager *cm,
 	}
 #endif
 
+	if (options_.isSet(OptCaptureScript)) {
+		std::string scriptName = options_[OptCaptureScript].toString();
+		script_ = std::make_unique<CaptureScript>(camera_, scriptName);
+		if (!script_->valid()) {
+			std::cerr << "Invalid capture script '" << scriptName
+				  << "'" << std::endl;
+			return;
+		}
+	}
+
 	switch (config->validate()) {
 	case CameraConfiguration::Valid:
 		break;
@@ -322,6 +333,9 @@ int CameraSession::queueRequest(Request *request)
 	if (captureLimit_ && queueCount_ >= captureLimit_)
 		return 0;
 
+	if (script_)
+		request->controls() = script_->frameControls(queueCount_);
+
 	queueCount_++;
 
 	return camera_->queueRequest(request);
diff --git a/src/cam/camera_session.h b/src/cam/camera_session.h
index bf966bd15ab0..d562caae0794 100644
--- a/src/cam/camera_session.h
+++ b/src/cam/camera_session.h
@@ -23,6 +23,7 @@
 
 #include "options.h"
 
+class CaptureScript;
 class FrameSink;
 
 class CameraSession
@@ -60,6 +61,8 @@ private:
 	std::shared_ptr<libcamera::Camera> camera_;
 	std::unique_ptr<libcamera::CameraConfiguration> config_;
 
+	std::unique_ptr<CaptureScript> script_;
+
 	std::map<const libcamera::Stream *, std::string> streamNames_;
 	std::unique_ptr<FrameSink> sink_;
 	unsigned int cameraIndex_;
diff --git a/src/cam/main.cpp b/src/cam/main.cpp
index c7f664b903e0..9c3370d7951f 100644
--- a/src/cam/main.cpp
+++ b/src/cam/main.cpp
@@ -158,6 +158,10 @@ int CamApp::parseOptions(int argc, char *argv[])
 			 "Print the metadata for completed requests",
 			 "metadata", ArgumentNone, nullptr, false,
 			 OptCamera);
+	parser.addOption(OptCaptureScript, OptionString,
+			 "Load a capture session configuration script from a file",
+			 "script", ArgumentRequired, "script", false,
+			 OptCamera);
 
 	options_ = parser.parse(argc, argv);
 	if (!options_.valid())
diff --git a/src/cam/main.h b/src/cam/main.h
index 62f7bbc9d181..51b87927837f 100644
--- a/src/cam/main.h
+++ b/src/cam/main.h
@@ -21,4 +21,5 @@ enum {
 	OptListControls = 256,
 	OptStrictFormats = 257,
 	OptMetadata = 258,
+	OptCaptureScript = 259,
 };
