diff --git a/chat-plugins/datasearch.js b/chat-plugins/datasearch.js
index 46ddbfd650f81..f579b15f5aba9 100644
--- a/chat-plugins/datasearch.js
+++ b/chat-plugins/datasearch.js
@@ -53,6 +53,7 @@ class DatasearchManager extends ProcessManager {
 			case 'dexsearch':
 				result = runDexsearch(data.target, data.cmd, data.canAll, data.message);
 				break;
+			case 'randmove':
 			case 'movesearch':
 				result = runMovesearch(data.target, data.cmd, data.canAll, data.message);
 				break;
@@ -121,6 +122,50 @@ exports.commands = {
 		"The order of the parameters does not matter.",
 	],
 
+	'!randommove': true,
+	rollmove: 'randommove',
+	randmove: 'randommove',
+	randommove: function (target, room, user, connection, cmd, message) {
+		if (!this.canBroadcast()) return;
+		let targets = target.split(",");
+		let targetsBuffer = [];
+		let qty;
+		for (let i = 0; i < targets.length; i++) {
+			if (!targets[i]) continue;
+			let num = Number(targets[i]);
+			if (Number.isInteger(num)) {
+				if (qty) return this.errorReply("Only specify the number of Pok\u00e9mon Moves once.");
+				qty = num;
+				if (qty < 1 || 15 < qty) return this.errorReply("Number of random Pok\u00e9mon  Movesmust be between 1 and 15.");
+				targetsBuffer.push("random" + qty);
+			} else {
+				targetsBuffer.push(targets[i]);
+			}
+		}
+		if (!qty) targetsBuffer.push("random1");
+
+		return runSearch({
+			target: targetsBuffer.join(","),
+			cmd: 'randmove',
+			canAll: (!this.broadcastMessage || (room && room.isPersonal)),
+			message: (this.broadcastMessage ? "" : message),
+		}).then(response => {
+			if (!this.runBroadcast()) return;
+			if (response.error) {
+				this.errorReply(response.error);
+			} else if (response.reply) {
+				this.sendReplyBox(response.reply);
+			} else if (response.dt) {
+				Chat.commands.data.call(this, response.dt, room, user, connection, 'dt');
+			}
+			this.update();
+		});
+	},
+	randommovehelp: [
+		"/randommove - Generates random Pok\u00e9mon Moves based on given search conditions.",
+		"/randommove uses the same parameters as /movesearch (see '/help ms').",
+		"Adding a number as a parameter returns that many random Pok\u00e9mon Moves, e.g., '/randmove 6' returns 6 random Pok\u00e9mon Moves.",
+	],
 	'!randompokemon': true,
 	rollpokemon: 'randompokemon',
 	randpoke: 'randompokemon',
@@ -707,7 +752,7 @@ function runMovesearch(target, cmd, canAll, message) {
 	let showAll = false;
 	let lsetData = {};
 	let targetMon = '';
-
+	let randomOutput = 0;
 	for (let i = 0; i < targets.length; i++) {
 		let isNotSearch = false;
 		target = targets[i].toLowerCase().trim();
@@ -784,7 +829,11 @@ function runMovesearch(target, cmd, canAll, message) {
 			}
 			continue;
 		}
-
+		if (target.substr(0, 6) === 'random' && cmd === 'randmove') {
+			//validation for this is in the /randmove command
+			randomOutput = parseInt(target.substr(6));
+			continue;
+		}
 		if (target === 'zrecovery') {
 			if (!searches['zrecovery']) {
 				searches['zrecovery'] = !isNotSearch;
@@ -1115,13 +1164,18 @@ function runMovesearch(target, cmd, canAll, message) {
 	} else {
 		resultsStr += (message === "" ? message : "<span style=\"color:#999999;\">" + escapeHTML(message) + ":</span><br />");
 	}
-	if (results.length > 0) {
+	if (randomOutput && randomOutput < results.length) {
+		results = Dex.shuffle(results).slice(0, randomOutput);
+	}
+	if (results.length > 1) {
 		if (showAll || results.length <= RESULTS_MAX_LENGTH + 5) {
 			results.sort();
 			resultsStr += results.join(", ");
 		} else {
 			resultsStr += results.slice(0, RESULTS_MAX_LENGTH).join(", ") + ", and " + (results.length - RESULTS_MAX_LENGTH) + " more. <span style=\"color:#999999;\">Redo the search with 'all' as a search parameter to show all results.</span>";
 		}
+	} else if (results.length === 1) {
+		return {dt: results[0]};
 	} else {
 		resultsStr += "No moves found.";
 	}