diff --git a/src/metabase/lib/schema/expression/string.cljc b/src/metabase/lib/schema/expression/string.cljc
index 60bfb300d4e33f6d22551a9ae59f0030f58f5840..01d18491801bec8cbebcf39d723735204b676604 100644
--- a/src/metabase/lib/schema/expression/string.cljc
+++ b/src/metabase/lib/schema/expression/string.cljc
@@ -20,10 +20,10 @@
   #_find [:schema [:ref ::expression/string]]
   #_replace [:schema [:ref ::expression/string]])
 
-(mbql-clause/define-tuple-mbql-clause :substring :- :type/Text
-  #_str [:schema [:ref ::expression/string]]
-  #_start [:schema [:ref ::expression/integer]]
-  #_end [:schema [:ref ::expression/integer]])
+(mbql-clause/define-catn-mbql-clause :substring :- :type/Text
+  [:str [:schema [:ref ::expression/string]]]
+  [:start [:schema [:ref ::expression/integer]]]
+  [:length [:? [:schema [:ref ::expression/integer]]]])
 
 (mbql-clause/define-catn-mbql-clause :concat :- :type/Text
   [:args [:repeat {:min 2} [:schema [:ref ::expression/string]]]])
diff --git a/test/metabase/query_processor_test/test_mlv2.clj b/test/metabase/query_processor_test/test_mlv2.clj
index b91197c9237ed37d4fbf48b2323fda55e283d6aa..392168366c8ebca531a97b0c47a74664933bbd17 100644
--- a/test/metabase/query_processor_test/test_mlv2.clj
+++ b/test/metabase/query_processor_test/test_mlv2.clj
@@ -63,10 +63,6 @@
        (mbql.u/match-one &match
          :field
          "#29946")))
-   ;; #29948: `:substring` is broken
-   (mbql.u/match-one legacy-query
-     :substring
-     "#29948")
    ;; #29949: missing schema
    (mbql.u/match-one legacy-query
      :regex-match-first