diff --git a/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj
index 6c5d6dd1308bf38fbe3718c966c3aef082a496b2..7a76f64d7b0307feaf4c8af8d64e3909a6f2947d 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/v2/extract_test.clj
@@ -1410,7 +1410,13 @@
 
       (testing "Fields that reference parents are properly exported as Field references"
         (is (= ["My Database" "PUBLIC" "Schema'd Table" "Other Field"]
-               (:parent_id (ts/extract-one "Field" nested-id))))))))
+               (:parent_id (ts/extract-one "Field" nested-id))))
+        (is (= [{:model "Database", :id "My Database"}
+                {:model "Schema", :id "PUBLIC"}
+                {:model "Table", :id "Schema'd Table"}
+                {:model "Field", :id "Other Field"}
+                {:model "Field", :id "Nested Field"}]
+               (:serdes/meta (ts/extract-one "Field" nested-id))))))))
 
 (deftest escape-report-test
   (mt/with-empty-h2-app-db
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/v2/load_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/v2/load_test.clj
index 53e52cf74585e5269280a7f82b02648bc3835b25..1ca78c9cb56fef646874b1fc348db39fe697e91e 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/v2/load_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/v2/load_test.clj
@@ -1336,3 +1336,37 @@
                     (testing (format "%s has identity hash in the db" model)
                       (is (= (serdes/identity-hash e)
                              (serdes/entity-id (name model) e))))))))))))))
+
+(deftest identically-named-fields-test
+  (mt/with-empty-h2-app-db
+    (let [db (ts/create! Database :name "mydb")
+          t  (ts/create! Table :name "table" :db_id (:id db))
+          f1 (ts/create! Field :name "field" :table_id (:id t))
+          ;; name is the same, but parent_id is different
+          f2 (ts/create! Field :name "field" :table_id (:id t) :parent_id (:id f1))
+          f3 (ts/create! Field :name "field" :table_id (:id t) :parent_id (:id f2)
+                         :description "desc")
+
+          ser (into [] (serdes.extract/extract {}))]
+
+      (is (=? {:parent_id   ["mydb" nil "table" "field"]
+               :serdes/meta [{:model "Database" :id "mydb"}
+                             {:model "Table" :id "table"}
+                             {:model "Field" :id "field"}
+                             {:model "Field" :id "field"}]}
+              (ts/extract-one "Field" (:id f2))))
+
+      (is (=? {:parent_id   ["mydb" nil "table" "field" "field"],
+               :serdes/meta [{:model "Database" :id "mydb"}
+                             {:model "Table" :id "table"}
+                             {:model "Field" :id "field"}
+                             {:model "Field" :id "field"}
+                             {:model "Field" :id "field"}]}
+              (ts/extract-one "Field" (:id f3))))
+
+      (t2/update! :model/Field (:id f3) {:description "some new one"})
+
+      (is (serdes.load/load-metabase! (ingestion-in-memory ser)))
+
+      (is (= "desc"
+             (t2/select-one-fn :description :model/Field (:id f3)))))))
diff --git a/enterprise/backend/test/metabase_enterprise/serialization/v2/storage_test.clj b/enterprise/backend/test/metabase_enterprise/serialization/v2/storage_test.clj
index d468cb3e7a348e0ce18759af65682f9995686c49..a03a94e5905da22e349d20ea17b625dd743836b0 100644
--- a/enterprise/backend/test/metabase_enterprise/serialization/v2/storage_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/serialization/v2/storage_test.clj
@@ -10,6 +10,7 @@
    [metabase-enterprise.serialization.v2.storage :as storage]
    [metabase.models :refer [Card Collection Dashboard DashboardCard Database Field FieldValues NativeQuerySnippet
                             Table]]
+   [metabase.models.serialization :as serdes]
    [metabase.test :as mt]
    [metabase.util.yaml :as yaml]
    [toucan2.core :as t2]))
@@ -221,3 +222,18 @@
             (is (thrown-with-msg? Exception #"Destination path is not writeable: "
                                   (storage/store! [{:serdes/meta [{:model "A" :id "B"}]}]
                                                   dump-dir)))))))))
+
+(deftest nested-fields-test
+  (ts/with-random-dump-dir [dump-dir "serdesv2-"]
+    (mt/with-empty-h2-app-db
+      (let [db  (ts/create! Database :name "mydb")
+            t   (ts/create! Table :name "table" :db_id (:id db))
+            f1  (ts/create! Field :name "parent" :table_id (:id t))
+            _f2 (ts/create! Field :name "child" :table_id (:id t) :parent_id (:id f1))]
+        (serdes/with-cache
+          (-> (extract/extract {:no-settings true})
+              (storage/store! dump-dir)))
+        (testing "we get correct names for nested fields"
+          (is (= #{["parent.yaml"]
+                   ["parent.child.yaml"]}
+                 (file-set (io/file dump-dir "databases" "mydb" "tables" "table" "fields")))))))))
diff --git a/src/metabase/models/field.clj b/src/metabase/models/field.clj
index c91b87559f05cadb09cd3a988b3665a12e134eb4..649f001f604c877cb40accde03615d34f52abf75 100644
--- a/src/metabase/models/field.clj
+++ b/src/metabase/models/field.clj
@@ -353,41 +353,38 @@
 
 ;; In order to retrieve the dependencies for a field its table_id needs to be serialized as [database schema table],
 ;; a trio of strings with schema maybe nil.
-(defmethod serdes/generate-path "Field" [_ {table_id :table_id field :name}]
-  (let [table (when (number? table_id)
-                (t2/select-one 'Table :id table_id))
-        db    (when table
-                (t2/select-one-fn :name 'Database :id (:db_id table)))
-        [db schema table] (if (number? table_id)
-                            [db (:schema table) (:name table)]
-                            ;; If table_id is not a number, it's already been exported as a [db schema table] triple.
-                            table_id)]
-    (filterv some? [{:model "Database" :id db}
-                    (when schema {:model "Schema" :id schema})
-                    {:model "Table"    :id table}
-                    {:model "Field"    :id field}])))
+(defmethod serdes/generate-path "Field" [_ field]
+  (let [[db schema table & fields] (serdes/*export-field-fk* (:id field))]
+    (->> (into (serdes/table->path [db schema table])
+               (map (fn [n] {:model "Field" :id n}) fields))
+         (filterv some?))))
 
 (defmethod serdes/entity-id "Field" [_ {:keys [name]}]
   name)
 
 (defmethod serdes/load-find-local "Field"
   [path]
-  (let [table (serdes/load-find-local (pop path))]
-    (t2/select-one Field :name (-> path last :id) :table_id (:id table))))
+  (let [[table-path fields] (split-with #(not= "Field" (:model %)) path)
+        table               (serdes/load-find-local table-path)
+        field-q             (serdes/recursively-find-field-q (:id table) (map :id (reverse fields)))]
+    (t2/select-one :model/Field field-q)))
 
 (defmethod serdes/dependencies "Field" [field]
   ;; Fields depend on their parent Table, plus any foreign Fields referenced by their Dimensions.
   ;; Take the path, but drop the Field section to get the parent Table's path instead.
   (let [this  (serdes/path field)
-        table (pop this)
+        table (remove #(= "Field" (:model %)) this)
         fks   (some->> field :fk_target_field_id serdes/field->path)
         human (->> (:dimensions field)
                    (keep :human_readable_field_id)
                    (map serdes/field->path)
                    set)]
-    (cond-> (set/union #{table} human)
-      fks   (set/union #{fks})
-      true  (disj this))))
+    (-> (set/union
+         #{table}
+         human
+         (when fks #{fks})
+         (when (:parent_id field) #{(butlast this)}))
+        (disj this))))
 
 (defmethod serdes/make-spec "Field" [_model-name opts]
   {:copy      [:active :base_type :caveats :coercion_strategy :custom_position :database_indexed
@@ -403,7 +400,6 @@
                :dimensions         (serdes/nested :model/Dimension :field_id opts)}})
 
 (defmethod serdes/storage-path "Field" [field _]
-  (-> (serdes/path field)
-      drop-last
-      serdes/storage-table-path-prefix
-      (concat ["fields" (:name field)])))
+  (let [[path fields] (split-with #(not= "Field" (:model %)) (serdes/path field))]
+    (concat (serdes/storage-path-prefixes path)
+            ["fields" (str/join "." (map :id fields))])))
diff --git a/src/metabase/models/field_values.clj b/src/metabase/models/field_values.clj
index 1fc54b81504e408823e31a922c40a0655f741cb7..ed218c6caf8e9f7c2fe8e5445b179b924030fc64 100644
--- a/src/metabase/models/field_values.clj
+++ b/src/metabase/models/field_values.clj
@@ -615,7 +615,5 @@
   ;; [path to table "fields" "field-name___fieldvalues"] since there's zero or one FieldValues per Field, and Fields
   ;; don't have their own directories.
   (let [hierarchy    (serdes/path fv)
-        field        (last (drop-last hierarchy))
-        table-prefix (serdes/storage-table-path-prefix (drop-last 2 hierarchy))]
-    (concat table-prefix
-            ["fields" (str (:id field) field-values-slug)])))
+        field-path   (serdes/storage-path-prefixes (drop-last hierarchy))]
+    (update field-path (dec (count field-path)) str field-values-slug)))
diff --git a/src/metabase/models/segment.clj b/src/metabase/models/segment.clj
index cc0cf4bd7c71eb9f279f29d703d2fbc44380b959..ab3355d9c59eb3659607437e404267c353c140a1 100644
--- a/src/metabase/models/segment.clj
+++ b/src/metabase/models/segment.clj
@@ -164,7 +164,7 @@
     (-> segment
         :table_id
         serdes/table->path
-        serdes/storage-table-path-prefix
+        serdes/storage-path-prefixes
         (concat ["segments" (serdes/storage-leaf-file-name id label)]))))
 
 (defmethod serdes/make-spec "Segment" [_model-name _opts]
diff --git a/src/metabase/models/serialization.clj b/src/metabase/models/serialization.clj
index 45d6db76f846b2a157b9c65009b08a10c1434949..0b16d5a90a98583d3ccb684d2c6ad5724a9bd8fd 100644
--- a/src/metabase/models/serialization.clj
+++ b/src/metabase/models/serialization.clj
@@ -14,6 +14,8 @@
 
   - Add it to the appropriate list in [[metabase-enterprise.serialization.v2.models]]
   - If it is in the `excluded-models` list, then your job is done
+  - If it's not, you probably want `entity_id` field autopopulated (that's a most common way to make model
+    transportable between instances), add `(derive :model/Model :hook/entity-id)` to your model
   - Define serialization multi-methods on a model (see `Card` and `Collection` for more complex examples or `Action`
     and `Segment` for less involved stuff)
     - `serdes/make-spec` - this is the main entry point. Should list every field in the model (this is checked in
@@ -941,7 +943,12 @@
                   (when schema {:model "Schema" :id schema})
                   {:model "Table" :id table-name}]))
 
-(defn storage-table-path-prefix
+(def ^:private STORAGE-DIRS {"Database" "databases"
+                             "Schema"   "schemas"
+                             "Table"    "tables"
+                             "Field"    "fields"})
+
+(defn storage-path-prefixes
   "The [[serdes/storage-path]] for Table is a bit tricky, and shared with Fields and FieldValues, so it's
   factored out here.
   Takes the :serdes/meta value for a `Table`!
@@ -950,32 +957,57 @@
   With a schema: `[\"databases\" \"db_name\" \"schemas\" \"public\" \"tables\" \"customers\"]`
   No schema:     `[\"databases\" \"db_name\" \"tables\" \"customers\"]`"
   [path]
-  (let [db-name    (-> path first :id)
-        schema     (when (= (count path) 3)
-                     (-> path second :id))
-        table-name (-> path last :id)]
-    (concat ["databases" db-name]
-            (when schema ["schemas" schema])
-            ["tables" table-name])))
+  (into [] cat
+        (for [entry path]
+          [(or (get STORAGE-DIRS (:model entry))
+               (throw (ex-info "Could not find dir name" {:entry entry})))
+           (:id entry)])))
 
 ;;; ## Fields
 
+(defn- field-hierarchy [id]
+  (reverse
+   (t2/select :model/Field
+              {:with-recursive [[[:parents {:columns [:id :name :parent_id :table_id]}]
+                                 {:union-all [{:from   [[:metabase_field :mf]]
+                                               :select [:mf.id :mf.name :mf.parent_id :mf.table_id]
+                                               :where  [:= :id id]}
+                                              {:from   [[:metabase_field :pf]]
+                                               :select [:pf.id :pf.name :pf.parent_id :pf.table_id]
+                                               :join   [[:parents :p] [:= :p.parent_id :pf.id]]}]}]]
+               :from           [:parents]
+               :select         [:name :table_id]})))
+
+(defn recursively-find-field-q
+  "Build a query to find a field among parents (should start with bottom-most field first), i.e.:
+
+  `(recursively-find-field-q 1 [\"inner\" \"outer\"])`"
+  [table-id [field & rest]]
+  (when field
+    {:from   [:metabase_field]
+     :select [:id]
+     :where  [:and
+              [:= :table_id table-id]
+              [:= :name field]
+              [:= :parent_id (recursively-find-field-q table-id rest)]]}))
+
 (defn ^:dynamic ^::cache *export-field-fk*
   "Given a numeric `field_id`, return a portable field reference.
   That has the form `[db-name schema table-name field-name]`, where the `schema` might be nil.
-  [[import-field-fk]] is the inverse."
+  [[*import-field-fk*]] is the inverse."
   [field-id]
   (when field-id
-    (let [{:keys [name table_id]}     (t2/select-one 'Field :id field-id)
-          [db-name schema field-name] (*export-table-fk* table_id)]
-      [db-name schema field-name name])))
+    (let [fields                      (field-hierarchy field-id)
+          [db-name schema field-name] (*export-table-fk* (:table_id (first fields)))]
+      (into [db-name schema field-name] (map :name fields)))))
 
 (defn ^:dynamic ^::cache *import-field-fk*
-  "Given a `field_id` as exported by [[export-field-fk]], resolve it back into a numeric `field_id`."
-  [[db-name schema table-name field-name :as field-id]]
+  "Given a `field_id` as exported by [[*export-field-fk*]], resolve it back into a numeric `field_id`."
+  [[db-name schema table-name & fields :as field-id]]
   (when field-id
-    (let [table_id (*import-table-fk* [db-name schema table-name])]
-      (t2/select-one-pk 'Field :table_id table_id :name field-name))))
+    (let [table-id (*import-table-fk* [db-name schema table-name])
+          field-q  (recursively-find-field-q table-id (reverse fields))]
+      (t2/select-one-pk :model/Field field-q))))
 
 (defn field->path
   "Given a `field_id` as exported by [[export-field-fk]], turn it into a `[{:model ...}]` path for the Field.
diff --git a/src/metabase/models/table.clj b/src/metabase/models/table.clj
index b0822a79e0d29b31c30ce73fff7c40ac241fb3ff..dabffabcd9df246668aa3101289cce1e5f5a3b8f 100644
--- a/src/metabase/models/table.clj
+++ b/src/metabase/models/table.clj
@@ -288,7 +288,7 @@
                :db_id      (serdes/fk :model/Database :name)}})
 
 (defmethod serdes/storage-path "Table" [table _ctx]
-  (concat (serdes/storage-table-path-prefix (serdes/path table))
+  (concat (serdes/storage-path-prefixes (serdes/path table))
           [(:name table)]))
 
 ;;; -------------------------------------------------- Audit Log Table -------------------------------------------------