diff --git a/CHANGES.md b/CHANGES.md
index 97c409534..a9a180abe 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -5,6 +5,9 @@
 ### :boom: Breaking changes
 ### :sparkles: New features
 ### :bug: Bugs fixed
+
+- Duplicate artboards create new flows if needed [Taiga #2221](https://tree.taiga.io/project/penpot/issue/2221)
+
 ### :arrow_up: Deps updates
 ### :heart: Community contributions by (Thank you!)
 
diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc
index e0c3b2715..8a0e5430b 100644
--- a/common/src/app/common/pages/changes_builder.cljc
+++ b/common/src/app/common/pages/changes_builder.cljc
@@ -32,18 +32,23 @@
   (vary-meta changes assoc ::objects objects))
 
 (defn add-obj
-  ([changes obj index]
-   (add-obj changes (assoc obj ::index index)))
-
   ([changes obj]
-   (let [add-change
-         {:type      :add-obj
-          :id        (:id obj)
-          :page-id   (::page-id (meta changes))
-          :parent-id (:parent-id obj)
-          :frame-id  (:frame-id obj)
-          :index     (::index obj)
-          :obj       (dissoc obj ::index :parent-id)}
+   (add-obj changes obj nil))
+
+  ([changes obj {:keys [index ignore-touched] :or {index ::undefined ignore-touched false}}]
+   (let [obj (cond-> obj
+               (not= index ::undefined)
+               (assoc :index index))
+
+         add-change
+         {:type           :add-obj
+          :id             (:id obj)
+          :page-id        (::page-id (meta changes))
+          :parent-id      (:parent-id obj)
+          :frame-id       (:frame-id obj)
+          :index          (::index obj)
+          :ignore-touched ignore-touched
+          :obj            (dissoc obj ::index :parent-id)}
 
          del-change
          {:type :del-obj
@@ -201,3 +206,22 @@
                                     :page-id page-id
                                     :option option-key
                                     :value old-val}))))
+
+(defn reg-objects
+  [chdata shape-ids]
+  (let [page-id (::page-id (meta chdata))]
+    (-> chdata
+        (update :redo-changes conj {:type :reg-objects :page-id page-id :shapes shape-ids}))))
+        ;; No need to do anything to undo
+
+(defn amend-last-change
+  "Modify the last redo-changes added with an update function."
+  [chdata f]
+  (update chdata :redo-changes
+          #(conj (pop %) (f (peek %)))))
+
+(defn amend-changes
+  "Modify all redo-changes with an update function."
+  [chdata f]
+  (update chdata :redo-changes #(mapv f %)))
+
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index ad4339d28..a4e34b373 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -1718,7 +1718,8 @@
           ;; Analyze the rchange and replace staled media and
           ;; references to the new uploaded media-objects.
           (process-rchange [media-idx item]
-            (if (= :image (get-in item [:obj :type]))
+            (if (and (= (:type item) :add-obj)
+                     (= :image (get-in item [:obj :type])))
               (update-in item [:obj :metadata]
                          (fn [{:keys [id] :as mdata}]
                            (if-let [mobj (get media-idx id)]
@@ -1812,57 +1813,49 @@
 
           ;; Proceed with the standard shape paste process.
           (do-paste [it state mouse-pos media]
-            (let [page-objects  (wsh/lookup-page-objects state)
-                  media-idx     (d/index-by :prev-id media)
+            (let [page         (wsh/lookup-page state)
+                  media-idx    (d/index-by :prev-id media)
 
                   ;; Calculate position for the pasted elements
                   [frame-id parent-id delta index] (calculate-paste-position state mouse-pos in-viewport?)
 
-                  paste-objects   (->> paste-objects
-                                       (d/mapm (fn [_ shape]
-                                                 (-> shape
-                                                     (assoc :frame-id frame-id)
-                                                     (assoc :parent-id parent-id)
+                  paste-objects (->> paste-objects
+                                     (d/mapm (fn [_ shape]
+                                               (-> shape
+                                                   (assoc :frame-id frame-id)
+                                                   (assoc :parent-id parent-id)
 
-                                                     (cond->
-                                                       ;; if foreign instance, detach the shape
-                                                      (foreign-instance? shape paste-objects state)
-                                                       (dissoc :component-id
-                                                               :component-file
-                                                               :component-root?
-                                                               :remote-synced?
-                                                               :shape-ref
-                                                               :touched))))))
+                                                   (cond->
+                                                     ;; if foreign instance, detach the shape
+                                                     (foreign-instance? shape paste-objects state)
+                                                     (dissoc :component-id
+                                                             :component-file
+                                                             :component-root?
+                                                             :remote-synced?
+                                                             :shape-ref
+                                                             :touched))))))
 
-                  all-objects   (merge page-objects paste-objects)
+                  all-objects (merge (:objects page) paste-objects)
 
-                  page-id   (:current-page-id state)
-                  unames    (-> (wsh/lookup-page-objects state page-id)
-                                (dwc/retrieve-used-names)) ;; TODO: move this calculation inside prepare-duplicate-changes?
-
-                  rchanges  (->> (dws/prepare-duplicate-changes all-objects page-id unames selected delta)
-                                 (mapv (partial process-rchange media-idx))
-                                 (mapv (partial change-add-obj-index paste-objects selected index)))
-
-                  uchanges  (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
-                                  (reverse rchanges))
+                  changes  (-> (dws/prepare-duplicate-changes all-objects page selected delta it)
+                               (pcb/amend-changes (partial process-rchange media-idx))
+                               (pcb/amend-changes (partial change-add-obj-index paste-objects selected index)))
 
                   ;; Adds a reg-objects operation so the groups are updated. We add all the new objects
-                  new-objects-ids (->> rchanges (filter #(= (:type %) :add-obj)) (mapv :id))
+                  new-objects-ids (->> changes :redo-changes (filter #(= (:type %) :add-obj)) (mapv :id))
 
-                  rchanges (conj rchanges {:type :reg-objects
-                                           :page-id page-id
-                                           :shapes new-objects-ids})
+                  changes (pcb/reg-objects changes new-objects-ids)
 
-                  selected  (->> rchanges
+                  selected  (->> changes
+                                 :redo-changes
+                                 (filter #(= (:type %) :add-obj))
                                  (filter #(selected (:old-id %)))
                                  (map #(get-in % [:obj :id]))
                                  (into (d/ordered-set)))]
 
-              (rx/of (dch/commit-changes {:redo-changes rchanges
-                                          :undo-changes uchanges
-                                          :origin it})
+              (rx/of (dch/commit-changes changes)
                      (dwc/select-shapes selected))))]
+
     (ptk/reify ::paste-shape
       ptk/WatchEvent
       (watch [it state _]
diff --git a/frontend/src/app/main/data/workspace/bool.cljs b/frontend/src/app/main/data/workspace/bool.cljs
index 6c705cb61..e67346103 100644
--- a/frontend/src/app/main/data/workspace/bool.cljs
+++ b/frontend/src/app/main/data/workspace/bool.cljs
@@ -99,7 +99,7 @@
                 shape-id (:id boolean-data)
                 changes (-> (cb/empty-changes it page-id)
                             (cb/with-objects objects)
-                            (cb/add-obj boolean-data index)
+                            (cb/add-obj boolean-data {:index index})
                             (cb/change-parent shape-id shapes))]
             (rx/of (dch/commit-changes changes)
                    (dwc/select-shapes (d/ordered-set shape-id)))))))))
diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs
index 08ec41784..4905c3916 100644
--- a/frontend/src/app/main/data/workspace/selection.cljs
+++ b/frontend/src/app/main/data/workspace/selection.cljs
@@ -10,9 +10,11 @@
    [app.common.geom.point :as gpt]
    [app.common.geom.shapes :as geom]
    [app.common.math :as mth]
+   [app.common.pages.changes-builder :as pcb]
    [app.common.pages.helpers :as cph]
    [app.common.spec :as us]
    [app.common.spec.interactions :as cti]
+   [app.common.spec.page :as ctp]
    [app.common.uuid :as uuid]
    [app.main.data.modal :as md]
    [app.main.data.workspace.changes :as dch]
@@ -263,14 +265,133 @@
 (declare prepare-duplicate-change)
 (declare prepare-duplicate-frame-change)
 (declare prepare-duplicate-shape-change)
+(declare prepare-duplicate-flows)
 
-(defn update-indices
-  "Fixes the indices for a set of changes after a duplication. We need to
-  fix the indices to take into the account the movement of indices.
+(defn prepare-duplicate-changes
+  "Prepare objects to duplicate: generate new id, give them unique names,
+  move to the desired position, and recalculate parents and frames as needed."
+  [all-objects page ids delta it]
+  (let [shapes         (map (d/getf all-objects) ids)
+        unames         (volatile! (dwc/retrieve-used-names (:objects page)))
+        update-unames! (fn [new-name] (vswap! unames conj new-name))
+        all-ids        (reduce #(into %1 (cons %2 (cph/get-children-ids all-objects %2))) #{} ids)
+        ids-map        (into {} (map #(vector % (uuid/next))) all-ids)]
+    (-> (reduce (fn [changes shape]
+                  (prepare-duplicate-change changes all-objects page unames update-unames! ids-map shape delta))
+                (-> (pcb/empty-changes it)
+                    (pcb/with-page page)
+                    (pcb/with-objects all-objects))
+                shapes)
+        (prepare-duplicate-flows shapes page ids-map))))
 
-  index-map is a map that goes from parent-id => vector([id index-in-parent])"
-  [changes index-map]
-  (let [inc-indices
+(defn- prepare-duplicate-change
+  [changes objects page unames update-unames! ids-map shape delta]
+  (if (cph/frame-shape? shape)
+    (prepare-duplicate-frame-change changes objects page unames update-unames! ids-map shape delta)
+    (prepare-duplicate-shape-change changes objects page unames update-unames! ids-map shape delta (:frame-id shape) (:parent-id shape))))
+
+(defn- prepare-duplicate-frame-change
+  [changes objects page unames update-unames! ids-map obj delta]
+  (let [new-id     (ids-map (:id obj))
+        frame-name (dwc/generate-unique-name @unames (:name obj))
+        _          (update-unames! frame-name)
+
+        new-frame  (-> obj
+                       (assoc :id new-id
+                              :name frame-name
+                              :frame-id uuid/zero
+                              :shapes [])
+                       (geom/move delta)
+                       (d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
+
+        changes (-> (pcb/add-obj changes new-frame)
+                    (pcb/amend-last-change #(assoc % :old-id (:id obj))))
+
+        changes (reduce (fn [changes child]
+                          (prepare-duplicate-shape-change changes
+                                                          objects
+                                                          page
+                                                          unames
+                                                          update-unames!
+                                                          ids-map
+                                                          child
+                                                          delta
+                                                          new-id
+                                                          new-id))
+                        changes
+                        (map (d/getf objects) (:shapes obj)))]
+    changes))
+
+(defn- prepare-duplicate-shape-change
+  [changes objects page unames update-unames! ids-map obj delta frame-id parent-id]
+  (if (some? obj)
+    (let [new-id      (ids-map (:id obj))
+          parent-id   (or parent-id frame-id)
+          name        (dwc/generate-unique-name @unames (:name obj))
+          _           (update-unames! name)
+
+          new-obj     (-> obj
+                          (assoc :id new-id
+                                 :name name
+                                 :parent-id parent-id
+                                 :frame-id frame-id)
+                          (dissoc :shapes)
+                          (geom/move delta)
+                          (d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
+
+          changes (pcb/add-obj changes new-obj {:ignore-touched true})
+          changes (-> (pcb/add-obj changes new-obj {:ignore-touched true})
+                      (pcb/amend-last-change #(assoc % :old-id (:id obj))))]
+
+          (reduce (fn [changes child]
+                    (prepare-duplicate-shape-change changes
+                                                    objects
+                                                    page
+                                                    unames
+                                                    update-unames!
+                                                    ids-map
+                                                    child
+                                                    delta
+                                                    frame-id
+                                                    new-id))
+                  changes
+                  (map (d/getf objects) (:shapes obj))))
+      changes))
+
+(defn- prepare-duplicate-flows
+  [changes shapes page ids-map]
+  (let [flows            (-> page :options :flows)
+        unames           (volatile! (into #{} (map :name flows)))
+        frames-with-flow (->> shapes
+                              (filter #(= (:type %) :frame))
+                              (filter #(some? (ctp/get-frame-flow flows (:id %)))))]
+    (if-not (empty? frames-with-flow)
+      (let [new-flows (reduce
+                        (fn [flows frame]
+                          (let [name     (dwc/generate-unique-name @unames "Flow-1")
+                                _        (vswap! unames conj name)
+                                new-flow {:id (uuid/next)
+                                          :name name
+                                          :starting-frame (get ids-map (:id frame))}]
+                            (ctp/add-flow flows new-flow)))
+                        flows
+                        frames-with-flow)]
+        (pcb/set-page-option changes :flows new-flows))
+      changes)))
+
+(defn duplicate-changes-update-indices
+  "Updates the changes to correctly set the indexes of the duplicated objects,
+  depending on the index of the original object respect their parent."
+  [objects ids changes]
+  (let [;; index-map is a map that goes from parent-id => vector([id index-in-parent])
+        index-map (reduce (fn [index-map id]
+                            (let [parent-id    (get-in objects [id :parent-id])
+                                  parent-index (cph/get-position-on-parent objects id)]
+                              (update index-map parent-id (fnil conj []) [id parent-index])))
+                          {}
+                          ids)
+
+        inc-indices
         (fn [[offset result] [id index]]
           [(inc offset) (conj result [id (+ index offset)])])
 
@@ -282,118 +403,12 @@
                (second)
                (into {})))
 
-        objects-indices (->> index-map (d/mapm fix-indices) (vals) (reduce merge))
+        objects-indices (->> index-map (d/mapm fix-indices) (vals) (reduce merge))]
 
-        update-change
-        (fn [change]
-          (let [index (get objects-indices (:old-id change))]
-            (-> change
-                (assoc :index index))))]
-    (mapv update-change changes)))
-
-(defn prepare-duplicate-changes
-  "Prepare objects to paste: generate new id, give them unique names,
-  move to the position of mouse pointer, and find in what frame they
-  fit."
-  [objects page-id unames ids delta]
-  (let [unames         (volatile! unames)
-        update-unames! (fn [new-name] (vswap! unames conj new-name))
-        all-ids        (reduce #(into %1 (cons %2 (cph/get-children-ids objects %2))) #{} ids)
-        ids-map        (into {} (map #(vector % (uuid/next))) all-ids)]
-    (loop [ids   (seq ids)
-           chgs  []]
-      (if ids
-        (let [id     (first ids)
-              result (prepare-duplicate-change objects page-id unames update-unames! ids-map id delta)
-              result (if (vector? result) result [result])]
-          (recur
-           (next ids)
-           (into chgs result)))
-        chgs))))
-
-(defn duplicate-changes-update-indices
-  "Parses the change set when duplicating to set-up the appropriate indices"
-  [objects ids changes]
-
-  (let [process-id
-        (fn [index-map id]
-          (let [parent-id    (get-in objects [id :parent-id])
-                parent-index (cph/get-position-on-parent objects id)]
-            (update index-map parent-id (fnil conj []) [id parent-index])))
-        index-map (reduce process-id {} ids)]
-    (-> changes (update-indices index-map))))
-
-(defn- prepare-duplicate-change
-  [objects page-id unames update-unames! ids-map id delta]
-  (let [obj (get objects id)]
-    (if (cph/frame-shape? obj)
-      (prepare-duplicate-frame-change objects page-id unames update-unames! ids-map obj delta)
-      (prepare-duplicate-shape-change objects page-id unames update-unames! ids-map obj delta (:frame-id obj) (:parent-id obj)))))
-
-(defn- prepare-duplicate-shape-change
-  [objects page-id unames update-unames! ids-map obj delta frame-id parent-id]
-  (when (some? obj)
-    (let [new-id      (ids-map (:id obj))
-          parent-id   (or parent-id frame-id)
-          name        (dwc/generate-unique-name @unames (:name obj))
-          _           (update-unames! name)
-
-          new-obj     (-> obj
-                          (assoc :id new-id
-                                 :name name
-                                 :frame-id frame-id)
-                          (dissoc :shapes)
-                          (geom/move delta)
-                          (d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
-
-          children-changes
-          (loop [result []
-                 cid  (first (:shapes obj))
-                 cids (rest (:shapes obj))]
-            (if (nil? cid)
-              result
-              (let [obj (get objects cid)
-                    changes (prepare-duplicate-shape-change objects page-id unames update-unames! ids-map obj delta frame-id new-id)]
-                (recur
-                 (into result changes)
-                 (first cids)
-                 (rest cids)))))]
-
-      (into [{:type :add-obj
-              :id new-id
-              :page-id page-id
-              :old-id (:id obj)
-              :frame-id frame-id
-              :parent-id parent-id
-              :ignore-touched true
-              :obj new-obj}]
-            children-changes))))
-
-(defn- prepare-duplicate-frame-change
-  [objects page-id unames update-unames! ids-map obj delta]
-  (let [new-id   (ids-map (:id obj))
-        frame-name (dwc/generate-unique-name @unames (:name obj))
-        _          (update-unames! frame-name)
-
-        sch        (->> (map #(get objects %) (:shapes obj))
-                        (mapcat #(prepare-duplicate-shape-change objects page-id unames update-unames! ids-map % delta new-id new-id)))
-
-        new-frame  (-> obj
-                       (assoc :id new-id
-                              :name frame-name
-                              :frame-id uuid/zero
-                              :shapes [])
-                       (geom/move delta)
-                       (d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
-
-        fch {:type :add-obj
-             :old-id (:id obj)
-             :page-id page-id
-             :id new-id
-             :frame-id uuid/zero
-             :obj new-frame}]
-
-    (into [fch] sch)))
+    (pcb/amend-changes
+      changes
+      (fn [change]
+        (assoc change :index (get objects-indices (:old-id change)))))))
 
 (defn clear-memorize-duplicated
   []
@@ -447,25 +462,22 @@
     ptk/WatchEvent
     (watch [it state _]
       (when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform])))
-        (let [page-id  (:current-page-id state)
-              objects  (wsh/lookup-page-objects state page-id)
+        (let [page     (wsh/lookup-page state)
+              objects  (:objects page)
               selected (wsh/lookup-selected state)
               delta    (if (and move-delta? (= (count selected) 1))
                          (let [obj (get objects (first selected))]
                            (calc-duplicate-delta obj state objects))
                          (gpt/point 0 0))
 
-              unames   (dwc/retrieve-used-names objects)
-
-              rchanges (->> (prepare-duplicate-changes objects page-id unames selected delta)
-                            (duplicate-changes-update-indices objects selected))
-
-              uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
-                             (reverse rchanges))
+              changes (->> (prepare-duplicate-changes objects page selected delta it)
+                           (duplicate-changes-update-indices objects selected))
 
               id-original (when (= (count selected) 1) (first selected))
 
-              selected (->> rchanges
+              selected (->> changes
+                            :redo-changes
+                            (filter #(= (:type %) :add-obj))
                             (filter #(selected (:old-id %)))
                             (map #(get-in % [:obj :id]))
                             (into (d/ordered-set)))
@@ -473,9 +485,7 @@
               id-duplicated (when (= (count selected) 1) (first selected))]
 
           (rx/of (select-shapes selected)
-                 (dch/commit-changes {:redo-changes rchanges
-                                      :undo-changes uchanges
-                                      :origin it})
+                 (dch/commit-changes changes)
                  (memorize-duplicated id-original id-duplicated)))))))
 
 (defn change-hover-state