diff --git a/src/metabase/api/search.clj b/src/metabase/api/search.clj
index 9bbe121174537e2c9b6829ed0d5fe843ee6eb110..7d9b87bd001654699db4e0ab09b0b7ebd5826848 100644
--- a/src/metabase/api/search.clj
+++ b/src/metabase/api/search.clj
@@ -332,7 +332,8 @@
                                (filter (fn [[_k v]] (= v :text)))
                                (map first)
                                (remove #{:collection_authority_level :moderated_status
-                                         :initial_sync_status :pk_ref :location}))
+                                         :initial_sync_status :pk_ref :location
+                                         :collection_location}))
         case-clauses      (as-> columns-to-search <>
                             (map (fn [col] [:like [:lower col] match]) <>)
                             (interleave <> (repeat [:inline 0]))
@@ -442,6 +443,47 @@
                    :last_editor_common_name (get user-id->common-name last_editor_id)))
           results)))
 
+(defn add-dataset-collection-hierarchy
+  "Adds `collection_effective_ancestors` to *datasets* in the search results."
+  [search-results]
+  (let [;; this helper function takes a search result (with `collection_id` and `collection_location`) and returns the
+        ;; effective location of the result.
+        result->loc (fn [{:keys [collection_id collection_location]}]
+                      (:effective_location
+                       (t2/hydrate
+                        (if (nil? collection_id)
+                          collection/root-collection
+                          {:location collection_location})
+                        :effective_location)))
+        ;; a map of collection-ids to collection info
+        col-id->name&loc (into {}
+                               (for [item search-results
+                                     :when (= (:model item) "dataset")]
+                                 [(:collection_id item)
+                                  {:name (:collection_name item)
+                                   :effective_location (result->loc item)}]))
+        ;; the set of all collection IDs where we *don't* know the collection name. For example, if `col-id->name&loc`
+        ;; contained `{1 {:effective_location "/2/" :name "Foo"}}`, we need to look up the name of collection `2`.
+        to-fetch         (into #{} (comp (keep :effective_location)
+                                         (mapcat collection/location-path->ids)
+                                         ;; already have these names
+                                         (remove col-id->name&loc))
+                               (vals col-id->name&loc))
+        ;; the complete map of collection IDs to names
+        id->name         (merge (if (seq to-fetch)
+                                  (t2/select-pk->fn :name :model/Collection :id [:in to-fetch])
+                                  {})
+                                (update-vals col-id->name&loc :name))
+        annotate         (fn [x]
+                           (cond-> x
+                             (= (:model x) "dataset")
+                             (assoc :collection_effective_ancestors
+                                    (if-let [loc (result->loc x)]
+                                      (->> (collection/location-path->ids loc)
+                                           (map (fn [id] {:id id :name (id->name id)})))
+                                      []))))]
+    (map annotate search-results)))
+
 (mu/defn ^:private search
   "Builds a search query that includes all the searchable entities and runs it"
   [search-ctx :- SearchContext]
@@ -467,8 +509,10 @@
                             (map #(update % :pk_ref json/parse-string))
                             (map (partial scoring/score-and-result (:search-string search-ctx)))
                             (filter #(pos? (:score %))))
-        total-results      (hydrate-user-metadata
-                            (scoring/top-results reducible-results search.config/max-filtered-results xf))
+        total-results       (cond->> (scoring/top-results reducible-results search.config/max-filtered-results xf)
+                              true hydrate-user-metadata
+                              (:model-ancestors? search-ctx) (add-dataset-collection-hierarchy)
+                              true (map scoring/serialize))
         add-perms-for-col  (fn [item]
                              (cond-> item
                                (mi/instance-of? :model/Collection item)
@@ -502,6 +546,7 @@
            filter-items-in-personal-collection
            offset
            search-string
+           model-ancestors?
            table-db-id
            search-native-query
            verified]}      :- [:map {:closed true}
@@ -517,6 +562,7 @@
                                [:offset                              {:optional true} [:maybe ms/Int]]
                                [:table-db-id                         {:optional true} [:maybe ms/PositiveInt]]
                                [:search-native-query                 {:optional true} [:maybe boolean?]]
+                               [:model-ancestors?                    {:optional true} [:maybe boolean?]]
                                [:verified                            {:optional true} [:maybe true?]]]] :- SearchContext
   (when (some? verified)
     (premium-features/assert-has-any-features
@@ -526,7 +572,8 @@
         ctx    (cond-> {:search-string      search-string
                         :current-user-perms @api/*current-user-permissions-set*
                         :archived?          (boolean archived)
-                        :models             models}
+                        :models             models
+                        :model-ancestors?   (boolean model-ancestors?)}
                  (some? created-at)                          (assoc :created-at created-at)
                  (seq created-by)                            (assoc :created-by created-by)
                  (some? filter-items-in-personal-collection) (assoc :filter-items-in-personal-collection filter-items-in-personal-collection)
@@ -587,7 +634,7 @@
 
   A search query that has both filters applied will only return models and cards."
   [q archived context created_at created_by table_db_id models last_edited_at last_edited_by
-   filter_items_in_personal_collection search_native_query verified]
+   filter_items_in_personal_collection model_ancestors search_native_query verified]
   {q                                   [:maybe ms/NonBlankString]
    archived                            [:maybe :boolean]
    table_db_id                         [:maybe ms/PositiveInt]
@@ -598,6 +645,7 @@
    created_by                          [:maybe (ms/QueryVectorOf ms/PositiveInt)]
    last_edited_at                      [:maybe ms/NonBlankString]
    last_edited_by                      [:maybe (ms/QueryVectorOf ms/PositiveInt)]
+   model_ancestors                     [:maybe :boolean]
    search_native_query                 [:maybe true?]
    verified                            [:maybe true?]}
   (api/check-valid-page-params mw.offset-paging/*limit* mw.offset-paging/*offset*)
@@ -617,6 +665,7 @@
                              :models              models-set
                              :limit               mw.offset-paging/*limit*
                              :offset              mw.offset-paging/*offset*
+                             :model-ancestors?    model_ancestors
                              :search-native-query search_native_query
                              :verified            verified}))
         duration   (- (System/currentTimeMillis) start-time)
diff --git a/src/metabase/search/config.clj b/src/metabase/search/config.clj
index 2f3a741f3d59a582c811064f8106ae80ad831078..d7f60bc8b54821b0b90caad8ecd3b1aab6f39ae5 100644
--- a/src/metabase/search/config.clj
+++ b/src/metabase/search/config.clj
@@ -99,6 +99,7 @@
    [:map {:closed true}
     [:search-string                                        [:maybe ms/NonBlankString]]
     [:archived?                                            :boolean]
+    [:model-ancestors?                                     :boolean]
     [:current-user-perms                                   [:set perms.u/PathSchema]]
     [:models                                               [:set SearchableModel]]
     [:filter-items-in-personal-collection {:optional true} [:enum "only" "exclude"]]
@@ -138,6 +139,7 @@
    :collection_id       :integer
    :collection_name     :text
    :collection_type     :text
+   :collection_location :text
    :collection_authority_level :text
    ;; returned for Card and Dashboard
    :collection_position :integer
@@ -270,6 +272,7 @@
   [_]
   (conj default-columns :collection_id :collection_position :dataset_query :display :creator_id
         [:collection.name :collection_name]
+        [:collection.location :collection_location]
         [:collection.authority_level :collection_authority_level]
         bookmark-col dashboardcard-count-col))
 
diff --git a/src/metabase/search/scoring.clj b/src/metabase/search/scoring.clj
index e8e2a79892344e8579da086b82dae69c973ac2f6..111316c9a5770dbfd3ea3d8802aecce2165035e5 100644
--- a/src/metabase/search/scoring.clj
+++ b/src/metabase/search/scoring.clj
@@ -301,12 +301,12 @@
      (max (- stale-time days-ago) 0)
      stale-time)))
 
-(defn- serialize
+(defn serialize
   "Massage the raw result from the DB and match data into something more useful for the client"
-  [result all-scores relevant-scores]
-  (let [{:keys [name display_name collection_id collection_name collection_authority_level collection_type]} result
-        matching-columns            (into #{} (remove nil? (map :column relevant-scores)))
-        match-context-thunk         (first (keep :match-context-thunk relevant-scores))]
+  [{:as result :keys [all-scores relevant-scores name display_name collection_id collection_name
+                      collection_authority_level collection_type collection_effective_ancestors]}]
+  (let [matching-columns    (into #{} (remove nil? (map :column relevant-scores)))
+        match-context-thunk (first (keep :match-context-thunk relevant-scores))]
     (-> result
         (assoc
          :name           (if (and (contains? matching-columns :display_name) display_name)
@@ -316,14 +316,20 @@
                                     (empty?
                                      (remove matching-columns search.config/displayed-columns)))
                            (match-context-thunk))
-         :collection     {:id              collection_id
-                          :name            collection_name
-                          :authority_level collection_authority_level
-                          :type            collection_type}
+         :collection     (merge {:id              collection_id
+                                 :name            collection_name
+                                 :authority_level collection_authority_level
+                                 :type            collection_type}
+                                (when collection_effective_ancestors
+                                  {:effective_ancestors collection_effective_ancestors}))
          :scores          all-scores)
         (update :dataset_query #(some-> % json/parse-string mbql.normalize/normalize))
         (dissoc
+         :all-scores
+         :relevant-scores
+         :collection_effective_ancestors
          :collection_id
+         :collection_location
          :collection_name
          :collection_type
          :display_name))))
@@ -397,7 +403,7 @@
                           0
                           text-matches)))
       {:score total-score
-       :result (serialize result all-scores relevant-scores)}
+       :result (assoc result :all-scores all-scores :relevant-scores relevant-scores)}
       {:score 0})))
 
 (defn compare-score
diff --git a/test/metabase/api/search_test.clj b/test/metabase/api/search_test.clj
index 32f5bc6a624b8cfd2514b5c0aad3a1cbdf9ae9a3..a2b84d4540a1622e454de6f7865fc85368e3c800 100644
--- a/test/metabase/api/search_test.clj
+++ b/test/metabase/api/search_test.clj
@@ -141,7 +141,10 @@
 
 (defn- default-results-with-collection []
   (on-search-types #{"dashboard" "pulse" "card" "dataset" "action"}
-                   #(assoc % :collection {:id true, :name (if (= (:model %) "action") nil true) :authority_level nil :type nil})
+                   #(assoc % :collection {:id true,
+                                          :name (if (= (:model %) "action") nil true)
+                                          :authority_level nil
+                                          :type nil})
                    (default-search-results)))
 
 (defn- do-with-search-items [search-string in-root-collection? f]
@@ -919,6 +922,7 @@
                                                 :archived?          false
                                                 :models             search.config/all-models
                                                 :current-user-perms #{"/"}
+                                                :model-ancestors?   false
                                                 :limit-int          100}))]
           ;; warm it up, in case the DB call depends on the order of test execution and it needs to
           ;; do some initialization
@@ -1658,3 +1662,43 @@
              (->> (mt/user-http-request :lucky :get 200 "search" :archived true :q "test")
                   :data
                   (map :name)))))))
+
+(deftest model-ancestors-gets-ancestor-collections
+  (testing "Collection names are correct"
+    (mt/with-temp [Collection {top-col-id :id} {:name "top level col" :location "/"}
+                   Collection {mid-col-id :id} {:name "middle level col" :location (str "/" top-col-id "/")}
+                   Card {leaf-card-id :id} {:type :model :collection_id mid-col-id :name "leaf model"}
+                   Card {top-card-id :id} {:type :model :collection_id top-col-id :name "top model"}]
+      (is (= #{[leaf-card-id [{:name "top level col" :id top-col-id}]]
+               [top-card-id []]}
+             (->> (mt/user-http-request :rasta :get 200 "search" :model_ancestors true :q "model" :models ["dataset"])
+                  :data
+                  (map (juxt :id #(get-in % [:collection :effective_ancestors])))
+                  (into #{}))))))
+  (testing "Models not in a collection work correctly"
+    (mt/with-temp [Card {card-id :id} {:type :model
+                                       :name "model"
+                                       :collection_id nil}]
+      (is (= #{[card-id []]}
+             (->> (mt/user-http-request :rasta :get 200 "search" :model_ancestors true :q "model" :models ["dataset"])
+                  :data
+                  (map (juxt :id #(get-in % [:collection :effective_ancestors])))
+                  (into #{}))))))
+  (testing "Non-models don't get collection_ancestors"
+    (mt/with-temp [Card _ {:name "question"
+                           :collection_id nil}]
+      (is (not (contains? (->> (mt/user-http-request :rasta :get 200 "search" :model_ancestors true :q "question" :models ["dataset" "card"])
+                               :data
+                               (filter #(= (:name %) "question"))
+                               first
+                               :collection)
+                          :effective_ancestors)))))
+  (testing "If `model_parents` is not passed, it doesn't get populated"
+    (mt/with-temp [Collection {top-col-id :id} {:name "top level col" :location "/"}
+                   Collection {mid-col-id :id} {:name "middle level col" :location (str "/" top-col-id "/")}
+                   Card _ {:type :model :collection_id mid-col-id :name "leaf model"}
+                   Card _ {:type :model :collection_id top-col-id :name "top model"}]
+      (is (= [nil nil]
+             (->> (mt/user-http-request :rasta :get 200 "search" :model_ancestors false :q "model" :models ["dataset"])
+                  :data
+                  (map #(get-in % [:collection :effective_ancestors]))))))))
diff --git a/test/metabase/search/filter_test.clj b/test/metabase/search/filter_test.clj
index ac449840ae4b90b19ffbfb408914ae392e7802e5..70bbb3ba3bec3cd5e6181df9a2c5afb434ddea35 100644
--- a/test/metabase/search/filter_test.clj
+++ b/test/metabase/search/filter_test.clj
@@ -11,6 +11,7 @@
   {:search-string       nil
    :archived?           false
    :models             search.config/all-models
+   :model-ancestors?   false
    :current-user-perms #{"/"}})
 
 (deftest ^:parallel ->applicable-models-test
diff --git a/test/metabase/search/scoring_test.clj b/test/metabase/search/scoring_test.clj
index aaf168aef4d9591f65c6f5e62dd5961ba1d30353..af80593f1745015123b4e101d652a6c88012a567 100644
--- a/test/metabase/search/scoring_test.clj
+++ b/test/metabase/search/scoring_test.clj
@@ -280,11 +280,13 @@
                   :database 1}
           result {:name          "card"
                   :model         "card"
-                  :dataset_query (json/generate-string query)}]
-      (is (= query (-> result (#'scoring/serialize {} {}) :dataset_query)))))
+                  :dataset_query (json/generate-string query)
+                  :all-scores {}
+                  :relevant-scores {}}]
+      (is (= query (-> result scoring/serialize :dataset_query)))))
   (testing "Doesn't error on other models without a query"
-    (is (nil? (-> {:name "dash" :model "dashboard"}
-                  (#'scoring/serialize {} {})
+    (is (nil? (-> {:name "dash" :model "dashboard" :all-scores {} :relevant-scores {}}
+                  (#'scoring/serialize)
                   :dataset_query)))))
 
 (deftest ^:parallel force-weight-test