diff --git a/src/Utils.cpp b/src/Utils.cpp
index 11c55e9c..cd197519 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -875,3 +875,10 @@ QString Utils::nsToTimestamp(uint64_t ns)
 
 	return QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart);
 }
+
+void Utils::AddSourceHelper(void *_data, obs_scene_t *scene)
+{
+	auto *data = reinterpret_cast<AddSourceData*>(_data);
+	data->sceneItem = obs_scene_add(scene, data->source);
+	obs_sceneitem_set_visible(data->sceneItem, data->setVisible);
+}
diff --git a/src/Utils.h b/src/Utils.h
index 69b86fc5..0851a8ea 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -86,4 +86,10 @@ namespace Utils {
 	bool SetFilenameFormatting(const char* filenameFormatting);
 
 	QString nsToTimestamp(uint64_t ns);
+    struct AddSourceData {
+        obs_source_t *source;
+        obs_sceneitem_t *sceneItem;
+        bool setVisible;
+    };
+    void AddSourceHelper(void *_data, obs_scene_t *scene);
 };
diff --git a/src/WSRequestHandler.cpp b/src/WSRequestHandler.cpp
index 95741acd..42b39f7e 100644
--- a/src/WSRequestHandler.cpp
+++ b/src/WSRequestHandler.cpp
@@ -101,6 +101,7 @@ const QHash<QString, RpcMethodHandler> WSRequestHandler::messageMap{
 	{ "ReleaseTBar", &WSRequestHandler::ReleaseTBar	},
 	{ "SetTBarPosition", &WSRequestHandler::SetTBarPosition	},
 
+	{ "CreateSource", &WSRequestHandler::CreateSource },
 	{ "SetVolume", &WSRequestHandler::SetVolume },
 	{ "GetVolume", &WSRequestHandler::GetVolume },
 	{ "ToggleMute", &WSRequestHandler::ToggleMute },
diff --git a/src/WSRequestHandler.h b/src/WSRequestHandler.h
index e9c5b9e1..4986cd1c 100644
--- a/src/WSRequestHandler.h
+++ b/src/WSRequestHandler.h
@@ -118,6 +118,7 @@ class WSRequestHandler {
 		RpcResponse ReleaseTBar(const RpcRequest&);
 		RpcResponse SetTBarPosition(const RpcRequest&);
 
+		RpcResponse CreateSource(const RpcRequest&);
 		RpcResponse SetVolume(const RpcRequest&);
 		RpcResponse GetVolume(const RpcRequest&);
 		RpcResponse ToggleMute(const RpcRequest&);
diff --git a/src/WSRequestHandler_SceneItems.cpp b/src/WSRequestHandler_SceneItems.cpp
index 2d09780d..b5334d89 100644
--- a/src/WSRequestHandler_SceneItems.cpp
+++ b/src/WSRequestHandler_SceneItems.cpp
@@ -2,18 +2,6 @@
 
 #include "WSRequestHandler.h"
 
-struct AddSourceData {
-	obs_source_t *source;
-	obs_sceneitem_t *sceneItem;
-	bool setVisible;
-};
-
-void AddSourceHelper(void *_data, obs_scene_t *scene) {
-	auto *data = reinterpret_cast<AddSourceData*>(_data);
-	data->sceneItem = obs_scene_add(scene, data->source);
-	obs_sceneitem_set_visible(data->sceneItem, data->setVisible);
-}
-
 /**
 * Get a list of all scene items in a scene.
 *
@@ -663,7 +651,7 @@ RpcResponse WSRequestHandler::AddSceneItem(const RpcRequest& request) {
 		return request.failed("you cannot add a scene as a sceneitem to itself");
 	}
 
-	AddSourceData data;
+	Utils::AddSourceData data;
 	data.source = source;
 	data.setVisible = true;
 	if (request.hasField("setVisible")) {
@@ -671,7 +659,7 @@ RpcResponse WSRequestHandler::AddSceneItem(const RpcRequest& request) {
 	}
 
 	obs_enter_graphics();
-	obs_scene_atomic_update(scene, AddSourceHelper, &data);
+	obs_scene_atomic_update(scene, Utils::AddSourceHelper, &data);
 	obs_leave_graphics();
 
 	OBSDataAutoRelease responseData = obs_data_create();
diff --git a/src/WSRequestHandler_Sources.cpp b/src/WSRequestHandler_Sources.cpp
index ba98ecdb..9aa5c260 100644
--- a/src/WSRequestHandler_Sources.cpp
+++ b/src/WSRequestHandler_Sources.cpp
@@ -18,6 +18,75 @@ bool isTextFreeType2Source(const QString& sourceKind)
 	return (sourceKind == "text_ft2_source" || sourceKind == "text_ft2_source_v2");
 }
 
+/**
+ * Create a source and add it as a sceneitem to a scene.
+ *
+ * @param {String} `sourceName` Source name.
+ * @param {String} `sourceKind` Source kind, Eg. `vlc_source`.
+ * @param {String} `sceneName` Scene to add the new source to.
+ * @param {Object (optional)} `sourceSettings` Source settings data.
+ * @param {boolean (optional)} `setVisible` Set the created SceneItem as visible or not. Defaults to true
+ *
+ * @return {int} `itemId` ID of the SceneItem in the scene.
+ *
+ * @api requests
+ * @name CreateSource
+ * @category sources
+ * @since unreleased
+ */
+RpcResponse WSRequestHandler::CreateSource(const RpcRequest& request)
+{
+	if (!request.hasField("sourceName") || !request.hasField("sourceKind") || !request.hasField("sceneName")) {
+		return request.failed("missing request parameters");
+	}
+
+	QString sourceName = obs_data_get_string(request.parameters(), "sourceName");
+	QString sourceKind = obs_data_get_string(request.parameters(), "sourceKind");
+	if (sourceName.isEmpty() || sourceKind.isEmpty()) {
+		return request.failed("empty sourceKind or sourceName parameters");
+	}
+	
+	OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8());
+	if (source) {
+		return request.failed("a source with that name already exists");
+	}
+	
+	const char* sceneName = obs_data_get_string(request.parameters(), "sceneName");
+	OBSSourceAutoRelease sceneSource = obs_get_source_by_name(sceneName);
+	OBSScene scene = obs_scene_from_source(sceneSource);
+	if (!scene) {
+		return request.failed("requested scene is invalid or doesnt exist");
+	}
+
+	OBSDataAutoRelease sourceSettings = nullptr;
+	if (request.hasField("sourceSettings")) {
+		sourceSettings = obs_data_get_obj(request.parameters(), "sourceSettings");
+	}
+
+	OBSSourceAutoRelease newSource = obs_source_create(sourceKind.toUtf8(), sourceName.toUtf8(), sourceSettings, nullptr);
+
+	if (!newSource) {
+		return request.failed("failed to create the source");
+	}
+	obs_source_set_enabled(newSource, true);
+
+	Utils::AddSourceData data;
+	data.source = newSource;
+	data.setVisible = true;
+	if (request.hasField("setVisible")) {
+		data.setVisible = obs_data_get_bool(request.parameters(), "setVisible");
+	}
+	
+	obs_enter_graphics();
+	obs_scene_atomic_update(scene, Utils::AddSourceHelper, &data);
+	obs_leave_graphics();
+	
+	OBSDataAutoRelease responseData = obs_data_create();
+	obs_data_set_int(responseData, "itemId", obs_sceneitem_get_id(data.sceneItem));
+
+	return request.success(responseData);
+}
+
 /**
 * List all sources available in the running OBS instance
 *