diff --git a/frontend/src/metabase-lib/lib/queries/NativeQuery.ts b/frontend/src/metabase-lib/lib/queries/NativeQuery.ts
index c48208aded357b7bf95ae4b14202f487b4b20a7b..6e6c7d73d7fdae2962748aa4b4d8a70b2b736d24 100644
--- a/frontend/src/metabase-lib/lib/queries/NativeQuery.ts
+++ b/frontend/src/metabase-lib/lib/queries/NativeQuery.ts
@@ -25,6 +25,7 @@ import { DatabaseEngine, DatabaseId } from "metabase-types/types/Database";
 import AtomicQuery from "metabase-lib/lib/queries/AtomicQuery";
 import Dimension, { TemplateTagDimension, FieldDimension } from "../Dimension";
 import Variable, { TemplateTagVariable } from "../Variable";
+import { createTemplateTag } from "metabase-lib/lib/queries/TemplateTag";
 import DimensionOptions from "../DimensionOptions";
 import { ValidationError } from "metabase-lib/lib/ValidationError";
 
@@ -422,21 +423,7 @@ export default class NativeQuery extends AtomicQuery {
    */
   _getUpdatedTemplateTags(queryText: string): TemplateTags {
     if (queryText && this.supportsNativeParameters()) {
-      let tags = [];
-      // look for variable usage in the query (like '{{varname}}').  we only allow alphanumeric characters for the variable name
-      // a variable name can optionally end with :start or :end which is not considered part of the actual variable name
-      // expected pattern is like mustache templates, so we are looking for something like {{category}} or {{date:start}}
-      // anything that doesn't match our rule is ignored, so {{&foo!}} would simply be ignored
-      // variables referencing other questions, by their card ID, are also supported: {{#123}} references question with ID 123
-      let match;
-      const re = /\{\{\s*((snippet:\s*[^}]+)|[A-Za-z0-9_\.]+?|#[0-9]*)\s*\}\}/g;
-
-      while ((match = re.exec(queryText)) != null) {
-        tags.push(match[1]);
-      }
-
-      // eliminate any duplicates since it's allowed for a user to reference the same variable multiple times
-      tags = _.uniq(tags);
+      const tags = recognizeTemplateTags(queryText);
       const existingTemplateTags = this.templateTagsMap();
       const existingTags = Object.keys(existingTemplateTags);
 
@@ -476,12 +463,7 @@ export default class NativeQuery extends AtomicQuery {
 
           // create new vars
           for (const tagName of newTags) {
-            templateTags[tagName] = {
-              id: Utils.uuid(),
-              name: tagName,
-              "display-name": humanize(tagName),
-              type: "text",
-            };
+            templateTags[tagName] = createTemplateTag(tagName);
 
             // parse card ID from tag name for card query template tags
             if (isCardQueryName(tagName)) {
@@ -533,3 +515,21 @@ export default class NativeQuery extends AtomicQuery {
       });
   }
 }
+
+// look for variable usage in the query (like '{{varname}}').  we only allow alphanumeric characters for the variable name
+// a variable name can optionally end with :start or :end which is not considered part of the actual variable name
+// expected pattern is like mustache templates, so we are looking for something like {{category}} or {{date:start}}
+// anything that doesn't match our rule is ignored, so {{&foo!}} would simply be ignored
+// variables referencing other questions, by their card ID, are also supported: {{#123}} references question with ID 123
+export function recognizeTemplateTags(queryText: string): string[] {
+  const tagNames = [];
+  let match;
+  const re = /\{\{\s*((snippet:\s*[^}]+)|[A-Za-z0-9_\.]+?|#[0-9]*)\s*\}\}/g;
+
+  while ((match = re.exec(queryText)) != null) {
+    tagNames.push(match[1]);
+  }
+
+  // eliminate any duplicates since it's allowed for a user to reference the same variable multiple times
+  return _.uniq(tagNames);
+}
diff --git a/frontend/src/metabase-lib/lib/queries/TemplateTag.ts b/frontend/src/metabase-lib/lib/queries/TemplateTag.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eae0aeeecc7de76c7a2cb23f8997f65b46076c3d
--- /dev/null
+++ b/frontend/src/metabase-lib/lib/queries/TemplateTag.ts
@@ -0,0 +1,12 @@
+import Utils from "metabase/lib/utils";
+import { humanize } from "metabase/lib/formatting";
+import { TemplateTag } from "metabase-types/types/Query";
+
+export const createTemplateTag = (tagName: string): TemplateTag => {
+  return {
+    id: Utils.uuid(),
+    name: tagName,
+    "display-name": humanize(tagName),
+    type: "text",
+  };
+};
diff --git a/frontend/src/metabase-lib/lib/queries/TemplateTag.unit.spec.ts b/frontend/src/metabase-lib/lib/queries/TemplateTag.unit.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b516139fc6f2c082462ae487d95ebacf1fb08b29
--- /dev/null
+++ b/frontend/src/metabase-lib/lib/queries/TemplateTag.unit.spec.ts
@@ -0,0 +1,11 @@
+import { createTemplateTag } from "metabase-lib/lib/queries/TemplateTag";
+
+describe("createTemplateTag", () => {
+  it("should create a proper template tag", () => {
+    const tag = createTemplateTag("stars");
+    expect(tag.name).toEqual("stars");
+    expect(tag.type).toEqual("text");
+    expect(typeof tag.id).toEqual("string");
+    expect(tag["display-name"]).toEqual("Stars");
+  });
+});
diff --git a/frontend/test/metabase-lib/lib/queries/NativeQuery.unit.spec.js b/frontend/test/metabase-lib/lib/queries/NativeQuery.unit.spec.js
index 3ed07478ba6c74b97cfb7a3ec8d8e24c3485754d..187470ef19ef996901c939c7f00eb578eec4ccbb 100644
--- a/frontend/test/metabase-lib/lib/queries/NativeQuery.unit.spec.js
+++ b/frontend/test/metabase-lib/lib/queries/NativeQuery.unit.spec.js
@@ -6,7 +6,9 @@ import {
   MONGO_DATABASE,
 } from "__support__/sample_database_fixture";
 
-import NativeQuery from "metabase-lib/lib/queries/NativeQuery";
+import NativeQuery, {
+  recognizeTemplateTags,
+} from "metabase-lib/lib/queries/NativeQuery";
 
 function makeDatasetQuery(queryText, templateTags, databaseId) {
   return {
@@ -339,4 +341,24 @@ describe("NativeQuery", () => {
       ]);
     });
   });
+
+  describe("recognizeTemplateTags", () => {
+    it("should handle standard variable names", () => {
+      expect(recognizeTemplateTags("SELECT * from {{products}}")).toEqual([
+        "products",
+      ]);
+    });
+
+    it("should allow duplicated variables", () => {
+      expect(
+        recognizeTemplateTags("SELECT {{col}} FROM {{t}} ORDER BY {{col}} "),
+      ).toEqual(["col", "t"]);
+    });
+
+    it("should ignore non-alphanumeric markers", () => {
+      expect(recognizeTemplateTags("SELECT * from X -- {{&universe}}")).toEqual(
+        [],
+      );
+    });
+  });
 });