diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-watched-word-test.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-watched-word-test.js
index 3ce120419c0..3ea2618acb2 100644
--- a/app/assets/javascripts/admin/addon/controllers/modals/admin-watched-word-test.js
+++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-watched-word-test.js
@@ -18,33 +18,45 @@ export default Controller.extend(ModalFunctionality, {
   )
   matches(value, regexpString, words, isReplace, isTag, isLink) {
     if (!value || !regexpString) {
-      return;
+      return [];
     }
 
-    const regexp = new RegExp(regexpString, "ig");
-    const matches = value.match(regexp) || [];
-
     if (isReplace || isLink) {
-      return matches.map((match) => ({
-        match,
-        replacement: words.find((word) =>
-          new RegExp(word.regexp, "ig").test(match)
-        ).replacement,
-      }));
-    } else if (isTag) {
-      return matches.map((match) => {
-        const tags = new Set();
-
-        words.forEach((word) => {
-          if (new RegExp(word.regexp, "ig").test(match)) {
-            word.replacement.split(",").forEach((tag) => tags.add(tag));
-          }
-        });
-
-        return { match, tags: Array.from(tags) };
+      const matches = [];
+      words.forEach((word) => {
+        const regexp = new RegExp(word.regexp, "gi");
+        let match;
+        while ((match = regexp.exec(value)) !== null) {
+          matches.push({
+            match: match[1],
+            replacement: word.replacement,
+          });
+        }
       });
-    }
+      return matches;
+    } else if (isTag) {
+      const matches = {};
+      words.forEach((word) => {
+        const regexp = new RegExp(word.regexp, "gi");
+        let match;
+        while ((match = regexp.exec(value)) !== null) {
+          if (!matches[match[1]]) {
+            matches[match[1]] = new Set();
+          }
 
-    return matches;
+          let tags = matches[match[1]];
+          word.replacement.split(",").forEach((tag) => {
+            tags.add(tag);
+          });
+        }
+      });
+
+      return Object.entries(matches).map((entry) => ({
+        match: entry[0],
+        tags: Array.from(entry[1]),
+      }));
+    } else {
+      return value.match(new RegExp(regexpString, "ig")) || [];
+    }
   },
 });
diff --git a/app/assets/javascripts/admin/addon/templates/watched-words-action.hbs b/app/assets/javascripts/admin/addon/templates/watched-words-action.hbs
index 005e4b759b0..6860ac16d3d 100644
--- a/app/assets/javascripts/admin/addon/templates/watched-words-action.hbs
+++ b/app/assets/javascripts/admin/addon/templates/watched-words-action.hbs
@@ -26,6 +26,10 @@
 
 <p class="about">{{actionDescription}}</p>
 
+{{#if siteSettings.watched_words_regular_expressions}}
+  <p>{{html-safe (i18n "admin.watched_words.regex_warning" basePath=(base-path))}}</p>
+{{/if}}
+
 {{watched-word-form
   actionKey=actionNameKey
   action=(action "recordAdded")
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index c4a18377be1..ca725af3940 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -4752,6 +4752,7 @@ en:
         clear_all: Clear All
         clear_all_confirm: "Are you sure you want to clear all watched words for the %{action} action?"
         invalid_regex: 'The watched word "%{word}" is an invalid regular expression.'
+        regex_warning: '<a href="%{basePath}/admin/site_settings/category/all_results?filter=watched%20words%20regular%20expressions%20">Watched words are regular expressions</a> and they do not automatically include word boundaries. If you want the regular expression to match whole words, include <code>\b</code> at the start and end of your regular expression.'
         actions:
           block: "Block"
           censor: "Censor"