diff --git a/common/src/app/common/svg/shapes_builder.cljc b/common/src/app/common/svg/shapes_builder.cljc
index 619a81b94..41f25e1e2 100644
--- a/common/src/app/common/svg/shapes_builder.cljc
+++ b/common/src/app/common/svg/shapes_builder.cljc
@@ -22,6 +22,7 @@
    [app.common.svg :as csvg]
    [app.common.svg.path :as path]
    [app.common.types.shape :as cts]
+   [app.common.uuid :as uuid]
    [cuerdas.core :as str]))
 
 (def default-rect
@@ -78,67 +79,68 @@
 (declare parse-svg-element)
 
 (defn create-svg-shapes
-  [svg-data {:keys [x y]} objects frame-id parent-id selected center?]
-  (let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data)
+  ([svg-data pos objects frame-id parent-id selected center?]
+   (create-svg-shapes (uuid/next) svg-data pos objects frame-id parent-id selected center?))
+  ([id svg-data {:keys [x y]} objects frame-id parent-id selected center?]
+   (let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data)
 
+         unames   (cfh/get-used-names objects)
+         svg-name (str/replace (:name svg-data) ".svg" "")
 
-        unames   (cfh/get-used-names objects)
-        svg-name (str/replace (:name svg-data) ".svg" "")
+         svg-data (-> svg-data
+                      (assoc :x (mth/round
+                                 (if center?
+                                   (- x vb-x (/ vb-width 2))
+                                   x)))
+                      (assoc :y (mth/round
+                                 (if center?
+                                   (- y vb-y (/ vb-height 2))
+                                   y)))
+                      (assoc :offset-x vb-x)
+                      (assoc :offset-y vb-y)
+                      (assoc :width vb-width)
+                      (assoc :height vb-height)
+                      (assoc :name svg-name))
 
-        svg-data (-> svg-data
-                     (assoc :x (mth/round
-                                (if center?
-                                  (- x vb-x (/ vb-width 2))
-                                  x)))
-                     (assoc :y (mth/round
-                                (if center?
-                                  (- y vb-y (/ vb-height 2))
-                                  y)))
-                     (assoc :offset-x vb-x)
-                     (assoc :offset-y vb-y)
-                     (assoc :width vb-width)
-                     (assoc :height vb-height)
-                     (assoc :name svg-name))
+         [def-nodes svg-data]
+         (-> svg-data
+             (csvg/fix-default-values)
+             (csvg/fix-percents)
+             (csvg/extract-defs))
 
-        [def-nodes svg-data]
-        (-> svg-data
-            (csvg/fix-default-values)
-            (csvg/fix-percents)
-            (csvg/extract-defs))
+         ;; In penpot groups have the size of their children. To
+         ;; respect the imported svg size and empty space let's create
+         ;; a transparent shape as background to respect the imported
+         ;; size
+         background
+         {:tag :rect
+          :attrs {:x      (dm/str vb-x)
+                  :y      (dm/str vb-y)
+                  :width  (dm/str vb-width)
+                  :height (dm/str vb-height)
+                  :fill   "none"
+                  :id     "base-background"}
+          :hidden true
+          :content []}
 
-        ;; In penpot groups have the size of their children. To
-        ;; respect the imported svg size and empty space let's create
-        ;; a transparent shape as background to respect the imported
-        ;; size
-        background
-        {:tag :rect
-         :attrs {:x      (dm/str vb-x)
-                 :y      (dm/str vb-y)
-                 :width  (dm/str vb-width)
-                 :height (dm/str vb-height)
-                 :fill   "none"
-                 :id     "base-background"}
-         :hidden true
-         :content []}
+         svg-data   (-> svg-data
+                        (assoc :defs def-nodes)
+                        (assoc :content (into [background] (:content svg-data))))
 
-        svg-data   (-> svg-data
-                       (assoc :defs def-nodes)
-                       (assoc :content (into [background] (:content svg-data))))
+         root-shape (create-svg-root id frame-id parent-id svg-data)
+         root-id    (:id root-shape)
 
-        root-shape (create-svg-root frame-id parent-id svg-data)
-        root-id    (:id root-shape)
+         ;; Create the root shape
+         root-attrs (-> (:attrs svg-data)
+                        (csvg/format-styles))
 
-        ;; Create the root shape
-        root-attrs (-> (:attrs svg-data)
-                       (csvg/format-styles))
+         [_ children]
+         (reduce (partial create-svg-children objects selected frame-id root-id svg-data)
+                 [unames []]
+                 (d/enumerate (->> (:content svg-data)
+                                   (mapv #(csvg/inherit-attributes root-attrs %)))))]
 
-        [_ children]
-        (reduce (partial create-svg-children objects selected frame-id root-id svg-data)
-                [unames []]
-                (d/enumerate (->> (:content svg-data)
-                                  (mapv #(csvg/inherit-attributes root-attrs %)))))]
-
-    [root-shape children]))
+     [root-shape children])))
 
 (defn create-raw-svg
   [name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}]
@@ -157,12 +159,13 @@
       :svg-viewbox vbox})))
 
 (defn create-svg-root
-  [frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}]
+  [id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}]
   (let [props (-> (dissoc attrs :viewBox :view-box :xmlns)
                   (d/without-keys csvg/inheritable-props)
                   (csvg/attrs->props))]
     (cts/setup-shape
-     {:type :group
+     {:id id
+      :type :group
       :name name
       :frame-id frame-id
       :parent-id parent-id
diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc
index d6c1178bd..7f5e6e83a 100644
--- a/common/src/app/common/types/shape/layout.cljc
+++ b/common/src/app/common/types/shape/layout.cljc
@@ -1281,6 +1281,21 @@
   (let [cells+index (d/enumerate cells)]
     (d/seek #(in-cell? (second %) row column) cells+index)))
 
+(defn free-cell-shapes
+  "Removes the shape-ids from the cells previously assigned."
+  [parent shape-ids]
+  (let [shape-ids (set shape-ids)]
+    (letfn [(free-cells
+              [cells]
+              (reduce-kv
+               (fn [m k v]
+                 (if (some shape-ids (:shapes v))
+                   (assoc-in m [k :shapes] [])
+                   m))
+               cells
+               cells))]
+      (update parent :layout-grid-cells free-cells))))
+
 (defn push-into-cell
   "Push the shapes into the row/column cell and moves the rest"
   [parent shape-ids row column]
@@ -1295,16 +1310,17 @@
         ;; Move shift the `shapes` attribute between cells
         (->> (range start-index (inc to-index))
              (map vector shape-ids)
-             (reduce (fn [[parent cells] [shape-id idx]]
-                       ;; If the shape to put in a cell is the same that is already in the cell we do nothing
-                       (if (= shape-id (get-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes 0]))
-                         [parent cells]
-                         (let [[parent cells] (free-cell-push parent cells idx)]
-                           [(update-in parent [:layout-grid-cells (get-in cells [idx :id])]
-                                       assoc :position :manual
-                                       :shapes [shape-id])
-                            cells])))
-                     [parent cells])
+             (reduce
+              (fn [[parent cells] [shape-id idx]]
+                ;; If the shape to put in a cell is the same that is already in the cell we do nothing
+                (if (= shape-id (get-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes 0]))
+                  [parent cells]
+                  (let [[parent cells] (free-cell-push parent cells idx)]
+                    [(update-in parent [:layout-grid-cells (get-in cells [idx :id])]
+                                assoc :position :manual
+                                :shapes [shape-id])
+                     cells])))
+              [parent cells])
              (first)))
       parent)))
 
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index 52a09c8c9..078f6c1a3 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -790,6 +790,7 @@
 (defn relocate-shapes
   [ids parent-id to-index & [ignore-parents?]]
   (dm/assert! (every? uuid? ids))
+  (dm/assert! (set? ids))
   (dm/assert! (uuid? parent-id))
   (dm/assert! (number? to-index))
 
diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs
index 30046ecef..43bed0489 100644
--- a/frontend/src/app/main/data/workspace/groups.cljs
+++ b/frontend/src/app/main/data/workspace/groups.cljs
@@ -15,6 +15,7 @@
    [app.common.types.container :as ctn]
    [app.common.types.shape :as cts]
    [app.common.types.shape.layout :as ctl]
+   [app.common.uuid :as uuid]
    [app.main.data.workspace.changes :as dch]
    [app.main.data.workspace.selection :as dws]
    [app.main.data.workspace.state-helpers :as wsh]
@@ -68,7 +69,7 @@
                    result)))))))
 
 (defn prepare-create-group
-  [changes objects page-id shapes base-name keep-name?]
+  [changes id objects page-id shapes base-name keep-name?]
   (let [frame-id  (:frame-id (first shapes))
         parent-id (:parent-id (first shapes))
         gname     (if (and keep-name?
@@ -84,7 +85,8 @@
                        (cfh/get-position-on-parent objects)
                        inc)
 
-        group     (cts/setup-shape {:type :group
+        group     (cts/setup-shape {:id id
+                                    :type :group
                                     :name gname
                                     :shapes (mapv :id shapes)
                                     :selrect selrect
@@ -173,30 +175,43 @@
 ;; GROUPS
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(def group-selected
-  (ptk/reify ::group-selected
+(defn group-shapes
+  [id ids & {:keys [change-selection?] :or {change-selection? false}}]
+  (ptk/reify ::group-shapes
     ptk/WatchEvent
     (watch [it state _]
-      (let [page-id  (:current-page-id state)
+      (let [id (d/nilv id (uuid/next))
+            page-id  (:current-page-id state)
             objects  (wsh/lookup-page-objects state page-id)
-            selected (->> (wsh/lookup-selected state)
-                          (cfh/clean-loops objects)
-                          (remove #(ctn/has-any-copy-parent? objects (get objects %))))
-            shapes   (shapes-for-grouping objects selected)
+
+            shapes
+            (->> ids
+                 (cfh/clean-loops objects)
+                 (remove #(ctn/has-any-copy-parent? objects (get objects %)))
+                 (shapes-for-grouping objects))
             parents  (into #{} (map :parent-id) shapes)]
         (when-not (empty? shapes)
           (let [[group changes]
-                (prepare-create-group (pcb/empty-changes it) objects page-id shapes "Group" false)]
+                (prepare-create-group (pcb/empty-changes it) id objects page-id shapes "Group" false)]
             (rx/of (dch/commit-changes changes)
-                   (dws/select-shapes (d/ordered-set (:id group)))
+                   (when change-selection?
+                     (dws/select-shapes (d/ordered-set (:id group))))
                    (ptk/data-event :layout/update {:ids parents}))))))))
 
-(def ungroup-selected
-  (ptk/reify ::ungroup-selected
+(def group-selected
+  (ptk/reify ::group-selected
+    ptk/WatchEvent
+    (watch [_ state _]
+      (let [selected (wsh/lookup-selected state)]
+        (rx/of (group-shapes nil selected))))))
+
+(defn ungroup-shapes
+  [ids & {:keys [change-selection?] :or {change-selection? false}}]
+  (ptk/reify ::ungroup-shapes
     ptk/WatchEvent
     (watch [it state _]
-      (let [page-id   (:current-page-id state)
-            objects   (wsh/lookup-page-objects state page-id)
+      (let [page-id (:current-page-id state)
+            objects (wsh/lookup-page-objects state page-id)
 
             prepare
             (fn [shape-id]
@@ -213,35 +228,42 @@
                   (ctl/grid-layout? objects (:parent-id shape))
                   (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells {:with-objects? true}))))
 
-            selected (->> (wsh/lookup-selected state)
-                          (remove #(ctn/has-any-copy-parent? objects (get objects %)))
-                          ;; components can't be ungrouped
-                          (remove #(ctk/instance-head? (get objects %))))
-            changes-list (sequence
-                          (keep prepare)
-                          selected)
+            ids (->> ids
+                     (remove #(ctn/has-any-copy-parent? objects (get objects %)))
+                     ;; components can't be ungrouped
+                     (remove #(ctk/instance-head? (get objects %))))
+
+            changes-list (sequence (keep prepare) ids)
 
             parents (into #{}
                           (comp (map #(cfh/get-parent objects %))
                                 (keep :id))
-                          selected)
+                          ids)
 
             child-ids
             (into (d/ordered-set)
                   (mapcat #(dm/get-in objects [% :shapes]))
-                  selected)
+                  ids)
 
             changes {:redo-changes (vec (mapcat :redo-changes changes-list))
                      :undo-changes (vec (mapcat :undo-changes changes-list))
                      :origin it}
             undo-id (js/Symbol)]
 
-        (when-not (empty? selected)
+        (when-not (empty? ids)
           (rx/of (dwu/start-undo-transaction undo-id)
                  (dch/commit-changes changes)
                  (ptk/data-event :layout/update {:ids parents})
                  (dwu/commit-undo-transaction undo-id)
-                 (dws/select-shapes child-ids)))))))
+                 (when change-selection?
+                   (dws/select-shapes child-ids))))))))
+
+(def ungroup-selected
+  (ptk/reify ::ungroup-selected
+    ptk/WatchEvent
+    (watch [_ state _]
+      (let [selected (wsh/lookup-selected state)]
+        (rx/of (ungroup-shapes selected :change-selection? true))))))
 
 (def mask-group
   (ptk/reify ::mask-group
@@ -262,7 +284,7 @@
                          (= (:type (first shapes)) :group))
                   [first-shape (-> (pcb/empty-changes it page-id)
                                    (pcb/with-objects objects))]
-                  (prepare-create-group (pcb/empty-changes it) objects page-id shapes "Mask" true))
+                  (prepare-create-group (pcb/empty-changes it) (uuid/next) objects page-id shapes "Mask" true))
 
                 changes  (-> changes
                              (pcb/update-shapes (:shapes group)
diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs
index 693207d87..50105e9b0 100644
--- a/frontend/src/app/main/data/workspace/media.cljs
+++ b/frontend/src/app/main/data/workspace/media.cljs
@@ -87,7 +87,17 @@
       (->> (svg/upload-images svg-data file-id)
            (rx/map #(svg/add-svg-shapes (assoc svg-data :image-data %) position))))))
 
-(defn- process-uris
+
+(defn upload-media-url
+  [name file-id url]
+  (rp/cmd!
+   :create-file-media-object-from-url
+   {:name name
+    :file-id file-id
+    :url url
+    :is-local true}))
+
+(defn process-uris
   [{:keys [file-id local? name uris mtype on-image on-svg]}]
   (letfn [(svg-url? [url]
             (or (and mtype (= mtype "image/svg+xml"))
@@ -449,3 +459,12 @@
               (rx/tap on-success)
               (rx/catch on-error)
               (rx/finalize #(st/emit! (msg/hide-tag :media-loading)))))))))
+
+(defn create-svg-shape
+  [id name svg-string position]
+  (ptk/reify ::create-svg-shape
+    ptk/WatchEvent
+    (watch [_ _ _]
+      (->> (svg->clj [name svg-string])
+           (rx/take 1)
+           (rx/map #(svg/add-svg-shapes id % position {:change-selection? false}))))))
diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs
index d13921687..b271f1ae6 100644
--- a/frontend/src/app/main/data/workspace/selection.cljs
+++ b/frontend/src/app/main/data/workspace/selection.cljs
@@ -723,62 +723,76 @@
 
         (gpt/subtract new-pos pt-obj)))))
 
+(defn duplicate-shapes
+  [ids & {:keys [move-delta? alt-duplication? change-selection? return-ref]
+          :or {move-delta? false alt-duplication? false change-selection? true return-ref nil}}]
+  (ptk/reify ::duplicate-shapes
+    ptk/WatchEvent
+    (watch [it state _]
+      (let [page     (wsh/lookup-page state)
+            objects  (:objects page)
+            ids (into #{}
+                      (comp (map (d/getf objects))
+                            (filter #(ctk/allow-duplicate? objects %))
+                            (map :id))
+                      ids)]
+        (when (seq ids)
+          (let [obj             (get objects (first ids))
+                delta           (if move-delta?
+                                  (calc-duplicate-delta obj state objects)
+                                  (gpt/point 0 0))
+
+                file-id         (:current-file-id state)
+                libraries       (wsh/get-libraries state)
+                library-data    (wsh/get-file state file-id)
+
+                changes         (->> (prepare-duplicate-changes objects page ids delta it libraries library-data file-id)
+                                     (duplicate-changes-update-indices objects ids))
+
+                tags            (or (:tags changes) #{})
+
+                changes         (cond-> changes alt-duplication? (assoc :tags (conj tags :alt-duplication)))
+
+                id-original     (first ids)
+
+                new-ids         (->> changes
+                                     :redo-changes
+                                     (filter #(= (:type %) :add-obj))
+                                     (filter #(ids (:old-id %)))
+                                     (map #(get-in % [:obj :id]))
+                                     (into (d/ordered-set)))
+
+                id-duplicated   (first new-ids)
+
+                frames (into #{}
+                             (map #(get-in objects [% :frame-id]))
+                             ids)
+                undo-id (js/Symbol)]
+
+            ;; Warning: This order is important for the focus mode.
+            (->> (rx/of
+                  (dwu/start-undo-transaction undo-id)
+                  (dch/commit-changes changes)
+                  (when change-selection?
+                    (select-shapes new-ids))
+                  (ptk/data-event :layout/update {:ids frames})
+                  (memorize-duplicated id-original id-duplicated)
+                  (dwu/commit-undo-transaction undo-id))
+                 (rx/tap #(when (some? return-ref)
+                            (reset! return-ref id-duplicated))))))))))
+
 (defn duplicate-selected
   ([move-delta?]
    (duplicate-selected move-delta? false))
   ([move-delta? alt-duplication?]
    (ptk/reify ::duplicate-selected
      ptk/WatchEvent
-     (watch [it state _]
+     (watch [_ state _]
        (when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform])))
-         (let [page     (wsh/lookup-page state)
-               objects  (:objects page)
-               selected (->> (wsh/lookup-selected state)
-                             (map (d/getf objects))
-                             (filter #(ctk/allow-duplicate? objects %))
-                             (map :id)
-                             set)]
-           (when (seq selected)
-             (let [obj             (get objects (first selected))
-                   delta           (if move-delta?
-                                     (calc-duplicate-delta obj state objects)
-                                     (gpt/point 0 0))
-
-                   file-id         (:current-file-id state)
-                   libraries       (wsh/get-libraries state)
-                   library-data    (wsh/get-file state file-id)
-
-                   changes         (->> (prepare-duplicate-changes objects page selected delta it libraries library-data file-id)
-                                        (duplicate-changes-update-indices objects selected))
-
-                   tags            (or (:tags changes) #{})
-
-                   changes         (cond-> changes alt-duplication? (assoc :tags (conj tags :alt-duplication)))
-
-                   id-original     (first selected)
-
-                   new-selected    (->> changes
-                                        :redo-changes
-                                        (filter #(= (:type %) :add-obj))
-                                        (filter #(selected (:old-id %)))
-                                        (map #(get-in % [:obj :id]))
-                                        (into (d/ordered-set)))
-
-                   id-duplicated   (first new-selected)
-
-                   frames (into #{}
-                                (map #(get-in objects [% :frame-id]))
-                                selected)
-                   undo-id (js/Symbol)]
-
-               ;; Warning: This order is important for the focus mode.
-               (rx/of
-                (dwu/start-undo-transaction undo-id)
-                (dch/commit-changes changes)
-                (select-shapes new-selected)
-                (ptk/data-event :layout/update {:ids frames})
-                (memorize-duplicated id-original id-duplicated)
-                (dwu/commit-undo-transaction undo-id))))))))))
+         (let [selected (wsh/lookup-selected state)]
+           (rx/of (duplicate-shapes selected
+                                    :move-delta? move-delta?
+                                    :alt-duplication? alt-duplication?))))))))
 
 (defn change-hover-state
   [id value]
diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs
index 37e40cf91..b3c0d513f 100644
--- a/frontend/src/app/main/data/workspace/shape_layout.cljs
+++ b/frontend/src/app/main/data/workspace/shape_layout.cljs
@@ -72,7 +72,7 @@
    :layout-grid-columns    []})
 
 (defn get-layout-initializer
-  [type from-frame?]
+  [type from-frame? calculate-params?]
   (let [[initial-layout-data calculate-params]
         (case type
           :flex [initial-flex-layout flex/calculate-params]
@@ -87,9 +87,11 @@
                 (cond-> (not from-frame?)
                   (assoc :show-content true :hide-in-viewer true)))
 
-            params (calculate-params objects (cfh/get-immediate-children objects (:id shape)) shape)]
+            params (when calculate-params?
+                     (calculate-params objects (cfh/get-immediate-children objects (:id shape)) shape))]
         (cond-> (merge shape params)
-          (= type :grid) (-> (ctl/assign-cells objects) ctl/reorder-grid-children))))))
+          (= type :grid)
+          (-> (ctl/assign-cells objects) ctl/reorder-grid-children))))))
 
 ;; Never call this directly but through the data-event `:layout/update`
 ;; Otherwise a lot of cycle dependencies could be generated
@@ -124,7 +126,7 @@
   (ptk/reify ::finalize))
 
 (defn create-layout-from-id
-  [id type from-frame?]
+  [id type & {:keys [from-frame? calculate-params?] :or {from-frame? false calculate-params? true}}]
   (dm/assert!
    "expected uuid for `id`"
    (uuid? id))
@@ -135,7 +137,7 @@
       (let [objects            (wsh/lookup-page-objects state)
             parent             (get objects id)
             undo-id            (js/Symbol)
-            layout-initializer (get-layout-initializer type from-frame?)]
+            layout-initializer (get-layout-initializer type from-frame? calculate-params?)]
 
         (rx/of (dwu/start-undo-transaction undo-id)
                (dch/update-shapes [id] layout-initializer {:with-objects? true})
@@ -177,7 +179,7 @@
               (dwse/select-shapes ordered-ids)
               (dwsh/create-artboard-from-selection new-shape-id parent-id group-index (:name (first selected-shapes)))
               (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
-              (create-layout-from-id new-shape-id type false)
+              (create-layout-from-id new-shape-id type)
               (dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
               (dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))
               (dwsh/delete-shapes page-id selected)
@@ -188,7 +190,7 @@
            (rx/of
             (dwsh/create-artboard-from-selection new-shape-id)
             (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1})
-            (create-layout-from-id new-shape-id type false)
+            (create-layout-from-id new-shape-id type)
             (dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto))
             (dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix))))
 
@@ -227,7 +229,7 @@
         (rx/of
          (dwu/start-undo-transaction undo-id)
          (if (and single? is-frame?)
-           (create-layout-from-id (first selected) type true)
+           (create-layout-from-id (first selected) type :from-frame? true)
            (create-layout-from-selection type))
          (dwu/commit-undo-transaction undo-id))))))
 
diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs
index fa159cf04..d81d8ecb3 100644
--- a/frontend/src/app/main/data/workspace/svg_upload.cljs
+++ b/frontend/src/app/main/data/workspace/svg_upload.cljs
@@ -13,6 +13,7 @@
    [app.common.svg :as csvg]
    [app.common.svg.shapes-builder :as csvg.shapes-builder]
    [app.common.types.shape-tree :as ctst]
+   [app.common.uuid :as uuid]
    [app.main.data.workspace.changes :as dch]
    [app.main.data.workspace.selection :as dws]
    [app.main.data.workspace.state-helpers :as wsh]
@@ -60,52 +61,57 @@
        (rx/reduce conj {})))
 
 (defn add-svg-shapes
-  [svg-data position]
-  (ptk/reify ::add-svg-shapes
-    ptk/WatchEvent
-    (watch [it state _]
-      (try
-        (let [page-id         (:current-page-id state)
-              objects         (wsh/lookup-page-objects state page-id)
-              frame-id        (ctst/top-nested-frame objects position)
-              selected        (wsh/lookup-selected state)
-              base            (cfh/get-base-shape objects selected)
+  ([svg-data position]
+   (add-svg-shapes nil svg-data position nil))
 
-              selected-id     (first selected)
-              selected-frame? (and (= 1 (count selected))
-                                   (= :frame (dm/get-in objects [selected-id :type])))
+  ([id svg-data position {:keys [change-selection?] :or {change-selection? false}}]
+   (ptk/reify ::add-svg-shapes
+     ptk/WatchEvent
+     (watch [it state _]
+       (try
+         (let [id              (d/nilv id (uuid/next))
+               page-id         (:current-page-id state)
+               objects         (wsh/lookup-page-objects state page-id)
+               frame-id        (ctst/top-nested-frame objects position)
+               selected        (wsh/lookup-selected state)
+               base            (cfh/get-base-shape objects selected)
 
-              parent-id       (if (or selected-frame? (empty? selected))
-                                frame-id
-                                (:parent-id base))
+               selected-id     (first selected)
+               selected-frame? (and (= 1 (count selected))
+                                    (= :frame (dm/get-in objects [selected-id :type])))
 
-              [new-shape new-children]
-              (csvg.shapes-builder/create-svg-shapes svg-data position objects frame-id parent-id selected true)
+               parent-id       (if (or selected-frame? (empty? selected))
+                                 frame-id
+                                 (:parent-id base))
 
-              changes         (-> (pcb/empty-changes it page-id)
-                                  (pcb/with-objects objects)
-                                  (pcb/add-object new-shape))
+               [new-shape new-children]
+               (csvg.shapes-builder/create-svg-shapes id svg-data position objects frame-id parent-id selected true)
 
-              changes         (reduce (fn [changes new-child]
-                                        (pcb/add-object changes new-child))
-                                      changes
-                                      new-children)
+               changes         (-> (pcb/empty-changes it page-id)
+                                   (pcb/with-objects objects)
+                                   (pcb/add-object new-shape))
 
-              changes         (pcb/resize-parents changes
-                                                  (->> (:redo-changes changes)
-                                                       (filter #(= :add-obj (:type %)))
-                                                       (map :id)
-                                                       (reverse)
-                                                       (vec)))
-              undo-id         (js/Symbol)]
+               changes         (reduce (fn [changes new-child]
+                                         (pcb/add-object changes new-child))
+                                       changes
+                                       new-children)
 
-          (rx/of (dwu/start-undo-transaction undo-id)
-                 (dch/commit-changes changes)
-                 (dws/select-shapes (d/ordered-set (:id new-shape)))
-                 (ptk/data-event :layout/update {:ids [(:id new-shape)]})
-                 (dwu/commit-undo-transaction undo-id)))
+               changes         (pcb/resize-parents changes
+                                                   (->> (:redo-changes changes)
+                                                        (filter #(= :add-obj (:type %)))
+                                                        (map :id)
+                                                        (reverse)
+                                                        (vec)))
+               undo-id         (js/Symbol)]
+
+           (rx/of (dwu/start-undo-transaction undo-id)
+                  (dch/commit-changes changes)
+                  (when change-selection?
+                    (dws/select-shapes (d/ordered-set (:id new-shape))))
+                  (ptk/data-event :layout/update {:ids [(:id new-shape)]})
+                  (dwu/commit-undo-transaction undo-id)))
+
+         (catch :default cause
+           (rx/throw {:type :svg-parser
+                      :data cause})))))))
 
-        (catch :default cause
-          (js/console.log (.-stack cause))
-          (rx/throw {:type :svg-parser
-                     :data cause}))))))
diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs
index 05289294d..4fc56d2ce 100644
--- a/frontend/src/app/main/data/workspace/transforms.cljs
+++ b/frontend/src/app/main/data/workspace/transforms.cljs
@@ -831,7 +831,7 @@
                                      :ignore-constraints false
                                      :ignore-snap-pixel true}))))))
 
-(defn- move-shapes-to-frame
+(defn move-shapes-to-frame
   [ids frame-id drop-index [row column :as cell]]
   (ptk/reify ::move-shapes-to-frame
     ptk/WatchEvent
@@ -923,24 +923,32 @@
             changes
             (-> (pcb/empty-changes it page-id)
                 (pcb/with-objects objects)
+
                 ;; Remove layout-item properties when moving a shape outside a layout
                 (cond-> (not (ctl/any-layout? objects frame-id))
                   (pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data))
+
                 ;; Remove the swap slots if it is moving to a different component
-                (pcb/update-shapes child-heads
-                                   (fn [shape]
-                                     (cond-> shape
-                                       (not= component-main-frame (ctn/find-component-main objects shape false))
-                                       (ctk/remove-swap-slot))))
+                (pcb/update-shapes
+                 child-heads
+                 (fn [shape]
+                   (cond-> shape
+                     (not= component-main-frame (ctn/find-component-main objects shape false))
+                     (ctk/remove-swap-slot))))
+
                 ;; Remove component-root property when moving a shape inside a component
                 (cond-> (ctn/get-instance-root objects frame)
                   (pcb/update-shapes moving-shapes-children-ids #(dissoc % :component-root)))
+
                 ;; Add component-root property when moving a component outside a component
                 (cond-> (not (ctn/get-instance-root objects frame))
                   (pcb/update-shapes child-heads #(assoc % :component-root true)))
+
                 (pcb/update-shapes moving-shapes-ids #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true)))
                 (pcb/update-shapes shape-ids-to-detach ctk/detach-shape)
                 (pcb/change-parent frame-id moving-shapes drop-index)
+
+                ;; Change the grid cell in a grid layout
                 (cond-> (ctl/grid-layout? objects frame-id)
                   (-> (pcb/update-shapes
                        [frame-id]
@@ -948,7 +956,8 @@
                          (-> frame
                              ;; Assign the cell when pushing into a specific grid cell
                              (cond-> (some? cell)
-                               (-> (ctl/push-into-cell moving-shapes-ids row column)
+                               (-> (ctl/free-cell-shapes moving-shapes-ids)
+                                   (ctl/push-into-cell moving-shapes-ids row column)
                                    (ctl/assign-cells objects)))
                              (ctl/assign-cell-positions objects)))
                        {:with-objects? true})
diff --git a/frontend/src/app/main/data/workspace/zoom.cljs b/frontend/src/app/main/data/workspace/zoom.cljs
index 379776ede..6499f93a2 100644
--- a/frontend/src/app/main/data/workspace/zoom.cljs
+++ b/frontend/src/app/main/data/workspace/zoom.cljs
@@ -6,6 +6,8 @@
 
 (ns app.main.data.workspace.zoom
   (:require
+   [app.common.data :as d]
+   [app.common.data.macros :as dm]
    [app.common.files.helpers :as cfh]
    [app.common.geom.align :as gal]
    [app.common.geom.matrix :as gmt]
@@ -54,14 +56,20 @@
                  #(impl-update-zoom % center (fn [z] (max (/ z 1.3) 0.01)))))))))
 
 (defn set-zoom
-  [center scale]
-  (ptk/reify ::set-zoom
-    ptk/UpdateEvent
-    (update [_ state]
-      (update state :workspace-local
-              #(impl-update-zoom % center (fn [z] (-> (* z scale)
-                                                      (max 0.01)
-                                                      (min 200))))))))
+  ([scale]
+   (set-zoom nil scale))
+  ([center scale]
+   (ptk/reify ::set-zoom
+     ptk/UpdateEvent
+     (update [_ state]
+       (let [vp (dm/get-in state [:workspace-local :vbox])
+             x (+ (:x vp) (/ (:width vp) 2))
+             y (+ (:y vp) (/ (:height vp) 2))
+             center (d/nilv center (gpt/point x y))]
+         (update state :workspace-local
+                 #(impl-update-zoom % center (fn [z] (-> (* z scale)
+                                                         (max 0.01)
+                                                         (min 200))))))))))
 
 (def reset-zoom
   (ptk/reify ::reset-zoom
@@ -110,6 +118,31 @@
                             (assoc :zoom-inverse (/ 1 zoom))
                             (update :vbox merge srect)))))))))))
 
+(defn fit-to-shapes
+  [ids]
+  (ptk/reify ::fit-to-shapes
+    ptk/UpdateEvent
+    (update [_ state]
+      (if (empty? ids)
+        state
+        (let [page-id (:current-page-id state)
+              objects (wsh/lookup-page-objects state page-id)
+              srect   (->> ids
+                           (map #(get objects %))
+                           (gsh/shapes->rect))]
+
+          (update state :workspace-local
+                  (fn [{:keys [vport] :as local}]
+                    (let [srect (gal/adjust-to-viewport
+                                 vport srect
+                                 {:padding 40})
+                          zoom  (/ (:width vport)
+                                   (:width srect))]
+                      (-> local
+                          (assoc :zoom zoom)
+                          (assoc :zoom-inverse (/ 1 zoom))
+                          (update :vbox merge srect))))))))))
+
 (defn start-zooming [pt]
   (ptk/reify ::start-zooming
     ptk/WatchEvent
diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs
index 51ed5155c..5c06da859 100644
--- a/frontend/src/app/plugins/api.cljs
+++ b/frontend/src/app/plugins/api.cljs
@@ -9,15 +9,23 @@
   (:require
    [app.common.data.macros :as dm]
    [app.common.files.changes-builder :as cb]
+   [app.common.geom.point :as gpt]
    [app.common.record :as cr]
    [app.common.types.shape :as cts]
    [app.common.uuid :as uuid]
    [app.main.data.workspace.changes :as ch]
+   [app.main.data.workspace.groups :as dwg]
+   [app.main.data.workspace.media :as dwm]
    [app.main.store :as st]
    [app.plugins.events :as events]
    [app.plugins.file :as file]
    [app.plugins.page :as page]
-   [app.plugins.shape :as shape]))
+   [app.plugins.shape :as shape]
+   [app.plugins.utils :as utils]
+   [app.plugins.viewport :as viewport]
+   [app.util.object :as obj]
+   [beicon.v2.core :as rx]
+   [promesa.core :as p]))
 
 ;;
 ;; PLUGINS PUBLIC API - The plugins will able to access this functions
@@ -28,12 +36,30 @@
    (map val)
    (map shape/data->shape-proxy)))
 
+(defn create-shape
+  [type]
+  (let [page-id (:current-page-id @st/state)
+        page (dm/get-in @st/state [:workspace-data :pages-index page-id])
+        shape (cts/setup-shape {:type type
+                                :x 0 :y 0 :width 100 :height 100})
+        changes
+        (-> (cb/empty-changes)
+            (cb/with-page page)
+            (cb/with-objects (:objects page))
+            (cb/add-object shape))]
+    (st/emit! (ch/commit-changes changes))
+    (shape/data->shape-proxy shape)))
+
 (deftype PenpotContext []
   Object
   (addListener
     [_ type callback]
     (events/add-listener type callback))
 
+  (getViewport
+    [_]
+    (viewport/create-proxy))
+
   (getFile
     [_]
     (file/data->file-proxy (:workspace-file @st/state) (:workspace-data @st/state)))
@@ -70,19 +96,46 @@
         "dark"
         (get-in @st/state [:profile :theme]))))
 
+  (uploadMediaUrl
+    [_ name url]
+    (let [file-id (get-in @st/state [:workspace-file :id])]
+      (p/create
+       (fn [resolve reject]
+         (->> (dwm/upload-media-url name file-id url)
+              (rx/map utils/to-js)
+              (rx/take 1)
+              (rx/subs! resolve reject))))))
+
+  (group
+    [_ shapes]
+    (let [page-id (:current-page-id @st/state)
+          id (uuid/next)
+          ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)]
+      (st/emit! (dwg/group-shapes id ids))
+      (shape/data->shape-proxy
+       (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id]))))
+
+  (ungroup
+    [_ group & rest]
+    (let [shapes (concat [group] rest)
+          ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)]
+      (st/emit! (dwg/ungroup-shapes ids))))
+
+  (createFrame
+    [_]
+    (create-shape :frame))
+
   (createRectangle
     [_]
-    (let [page-id (:current-page-id @st/state)
-          page (dm/get-in @st/state [:workspace-data :pages-index page-id])
-          shape (cts/setup-shape {:type :rect
-                                  :x 0 :y 0 :width 100 :height 100})
-          changes
-          (-> (cb/empty-changes)
-              (cb/with-page page)
-              (cb/with-objects (:objects page))
-              (cb/add-object shape))]
-      (st/emit! (ch/commit-changes changes))
-      (shape/data->shape-proxy shape))))
+    (create-shape :rect))
+
+  (createShapeFromSvg
+    [_ svg-string]
+    (let [id (uuid/next)
+          page-id (:current-page-id @st/state)]
+      (st/emit! (dwm/create-svg-shape id "svg" svg-string (gpt/point 0 0)))
+      (shape/data->shape-proxy
+       (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id])))))
 
 (defn create-context
   []
@@ -90,4 +143,5 @@
    (PenpotContext.)
    {:name "root" :get #(.getRoot ^js %)}
    {:name "currentPage" :get #(.getPage ^js %)}
-   {:name "selection" :get #(.getSelectedShapes ^js %)}))
+   {:name "selection" :get #(.getSelectedShapes ^js %)}
+   {:name "viewport" :get #(.getViewport ^js %)}))
diff --git a/frontend/src/app/plugins/grid.cljs b/frontend/src/app/plugins/grid.cljs
new file mode 100644
index 000000000..07cb39c9a
--- /dev/null
+++ b/frontend/src/app/plugins/grid.cljs
@@ -0,0 +1,173 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns app.plugins.grid
+  (:require
+   [app.common.data :as d]
+   [app.common.record :as crc]
+   [app.common.spec :as us]
+   [app.common.types.shape.layout :as ctl]
+   [app.common.uuid :as uuid]
+   [app.main.data.workspace.shape-layout :as dwsl]
+   [app.main.data.workspace.transforms :as dwt]
+   [app.main.store :as st]
+   [app.plugins.utils :as utils :refer [get-data get-state]]
+   [app.util.object :as obj]
+   [potok.v2.core :as ptk]))
+
+(defn- make-tracks
+  [tracks]
+  (.freeze
+   js/Object
+   (apply array (->> tracks (map utils/to-js)))))
+
+(deftype GridLayout [_data]
+  Object
+
+  (addRow
+    [self type value]
+    (let [id (get-data self :id)
+          type (keyword type)]
+      (st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value}))))
+
+  (addRowAtIndex
+    [self type value index]
+    (let [id (get-data self :id)
+          type (keyword type)]
+      (st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value} index))))
+
+  (addColumn
+    [self type value]
+    (let [id (get-data self :id)
+          type (keyword type)]
+      (st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value}))))
+
+  (addColumnAtIndex
+    [self type value index]
+    (let [id (get-data self :id)
+          type (keyword type)]
+      (st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value} index))))
+
+  (removeRow
+    [self index]
+    (let [id (get-data self :id)]
+      (st/emit! (dwsl/remove-layout-track #{id} :row index))))
+
+  (removeColumn
+    [self index]
+    (let [id (get-data self :id)]
+      (st/emit! (dwsl/remove-layout-track #{id} :column index))))
+
+  (setColumn
+    [self index type value]
+    (let [id (get-data self :id)
+          type (keyword type)]
+      (st/emit! (dwsl/change-layout-track #{id} :column index (d/without-nils {:type type :value value})))))
+
+  (setRow
+    [self index type value]
+    (let [id (get-data self :id)
+          type (keyword type)]
+      (st/emit! (dwsl/change-layout-track #{id} :row index (d/without-nils {:type type :value value})))))
+
+  (remove
+    [self]
+    (let [id (get-data self :id)]
+      (st/emit! (dwsl/remove-layout #{id}))))
+
+  (appendChild
+    [self child row column]
+    (let [parent-id (get-data self :id)
+          child-id (uuid/uuid (obj/get child "id"))]
+      (st/emit! (dwt/move-shapes-to-frame #{child-id} parent-id nil [row column])
+                (ptk/data-event :layout/update {:ids [parent-id]})))))
+
+(defn grid-layout-proxy
+  [data]
+  (-> (GridLayout. data)
+      (crc/add-properties!
+       {:name "dir"
+        :get #(get-state % :layout-grid-dir d/name)
+        :set
+        (fn [self value]
+          (let [id (get-data self :id)
+                value (keyword value)]
+            (when (contains? ctl/grid-direction-types value)
+              (st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value})))))}
+
+       {:name "rows"
+        :get #(get-state % :layout-grid-rows make-tracks)}
+
+       {:name "columns"
+        :get #(get-state % :layout-grid-columns make-tracks)}
+
+       {:name "alignItems"
+        :get #(get-state % :layout-align-items d/name)
+        :set
+        (fn [self value]
+          (let [id (get-data self :id)
+                value (keyword value)]
+            (when (contains? ctl/align-items-types value)
+              (st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))}
+
+       {:name "alignContent"
+        :get #(get-state % :layout-align-content d/name)
+        :set
+        (fn [self value]
+          (let [id (get-data self :id)
+                value (keyword value)]
+            (when (contains? ctl/align-content-types value)
+              (st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))}
+
+       {:name "justifyItems"
+        :get #(get-state % :layout-justify-items d/name)
+        :set
+        (fn [self value]
+          (let [id (get-data self :id)
+                value (keyword value)]
+            (when (contains? ctl/justify-items-types value)
+              (st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))}
+
+       {:name "justifyContent"
+        :get #(get-state % :layout-justify-content d/name)
+        :set
+        (fn [self value]
+          (let [id (get-data self :id)
+                value (keyword value)]
+            (when (contains? ctl/justify-content-types value)
+              (st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))}
+
+       {:name "rowGap"
+        :get #(:row-gap (get-state % :layout-gap))
+        :set
+        (fn [self value]
+          (let [id (get-data self :id)]
+            (when (us/safe-int? value)
+              (st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))}
+
+       {:name "columnGap"
+        :get #(:column-gap (get-state % :layout-gap))
+        :set
+        (fn [self value]
+          (let [id (get-data self :id)]
+            (when (us/safe-int? value)
+              (st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))}
+
+       {:name "verticalPadding"
+        :get #(:p1 (get-state % :layout-padding))
+        :set
+        (fn [self value]
+          (let [id (get-data self :id)]
+            (when (us/safe-int? value)
+              (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))}
+
+       {:name "horizontalPadding"
+        :get #(:p2 (get-state % :layout-padding))
+        :set
+        (fn [self value]
+          (let [id (get-data self :id)]
+            (when (us/safe-int? value)
+              (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))})))
diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs
index 8a7aea836..e4b466b2e 100644
--- a/frontend/src/app/plugins/shape.cljs
+++ b/frontend/src/app/plugins/shape.cljs
@@ -7,16 +7,20 @@
 (ns app.plugins.shape
   "RPC for plugins runtime."
   (:require
-   [app.common.data :as d]
    [app.common.data.macros :as dm]
    [app.common.files.helpers :as cfh]
    [app.common.record :as crc]
    [app.common.text :as txt]
+   [app.common.uuid :as uuid]
    [app.main.data.workspace :as udw]
    [app.main.data.workspace.changes :as dwc]
+   [app.main.data.workspace.selection :as dws]
+   [app.main.data.workspace.shape-layout :as dwsl]
+   [app.main.data.workspace.shapes :as dwsh]
    [app.main.store :as st]
-   [app.plugins.utils :refer [get-data get-data-fn]]
-   [cuerdas.core :as str]))
+   [app.plugins.grid :as grid]
+   [app.plugins.utils :as utils :refer [get-data get-data-fn get-state]]
+   [app.util.object :as obj]))
 
 (declare data->shape-proxy)
 
@@ -24,52 +28,65 @@
   [fills]
   (.freeze
    js/Object
-   (apply array
-          (->> fills
-               ;; TODO: Transform explicitly instead of cljs->js?
-               (map #(clj->js % {:keyword-fn (fn [k] (str/camel (name k)))}))))))
+   (apply array (->> fills (map utils/to-js)))))
 
 (defn- make-strokes
   [strokes]
   (.freeze
    js/Object
-   (apply array
-          (->> strokes
-               ;; TODO: Transform explicitly instead of cljs->js?
-               (map #(clj->js % {:keyword-fn (fn [k] (str/camel (name k)))}))))))
+   (apply array (->> strokes (map utils/to-js)))))
 
 (defn- locate-shape
   [shape-id]
   (let [page-id (:current-page-id @st/state)]
     (dm/get-in @st/state [:workspace-data :pages-index page-id :objects shape-id])))
 
-(defn- get-state
-  ([self attr]
-   (let [id (get-data self :id)
-         page-id (d/nilv (get-data self :page-id) (:current-page-id @st/state))]
-     (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id attr])))
-  ([self attr mapfn]
-   (-> (get-state self attr)
-       (mapfn))))
-
-(deftype ShapeProxy [^:mutable #_:clj-kondo/ignore _data]
+(deftype ShapeProxy [#_:clj-kondo/ignore _data]
   Object
+  (resize
+    [self width height]
+    (let [id (get-data self :id)]
+      (st/emit! (udw/update-dimensions [id] :width width)
+                (udw/update-dimensions [id] :height height))))
+
+  (clone [self]
+    (let [id (get-data self :id)
+          page-id (:current-page-id @st/state)
+          ret-v (atom nil)]
+      (st/emit! (dws/duplicate-shapes #{id} :change-selection? false :return-ref ret-v))
+      (let [new-id (deref ret-v)
+            shape (dm/get-in @st/state [:workspace-data :pages-index page-id :objects new-id])]
+        (data->shape-proxy shape))))
+
+  (remove [self]
+    (let [id (get-data self :id)]
+      (st/emit! (dwsh/delete-shapes #{id}))))
+
+  ;; Only for frames + groups + booleans
   (getChildren
     [self]
     (apply array (->> (get-state self :shapes)
                       (map locate-shape)
                       (map data->shape-proxy))))
 
-  (resize
-    [self width height]
+  (appendChild [self child]
+    (let [parent-id (get-data self :id)
+          child-id (uuid/uuid (obj/get child "id"))]
+      (st/emit! (udw/relocate-shapes #{child-id} parent-id 0))))
 
+  (insertChild [self index child]
+    (let [parent-id (get-data self :id)
+          child-id (uuid/uuid (obj/get child "id"))]
+      (st/emit! (udw/relocate-shapes #{child-id} parent-id index))))
+
+  ;; Only for frames
+  (addFlexLayout [self]
     (let [id (get-data self :id)]
-      (st/emit! (udw/update-dimensions [id] :width width)
-                (udw/update-dimensions [id] :height height))))
+      (st/emit! (dwsl/create-layout-from-id id :flex :from-frame? true :calculate-params? false))))
 
-  (clone [_] (.log js/console (clj->js _data)))
-  (delete [_] (.log js/console (clj->js _data)))
-  (appendChild [_] (.log js/console (clj->js _data))))
+  (addGridLayout [self]
+    (let [id (get-data self :id)]
+      (st/emit! (dwsl/create-layout-from-id id :grid :from-frame? true :calculate-params? false)))))
 
 (crc/define-properties!
   ShapeProxy
@@ -88,7 +105,7 @@
         :get (get-data-fn :id str)}
 
        {:name "type"
-        :get (get-data-fn :type)}
+        :get (get-data-fn :type name)}
 
        {:name "x"
         :get #(get-state % :x)
@@ -104,6 +121,62 @@
           (let [id (get-data self :id)]
             (st/emit! (udw/update-position id {:y value}))))}
 
+       {:name "parentX"
+        :get (fn [self]
+               (let [page-id (:current-page-id @st/state)
+                     parent-id (get-state self :parent-id)
+                     parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])]
+                 (- (get-state self :x) parent-x)))
+        :set
+        (fn [self value]
+          (let [page-id (:current-page-id @st/state)
+                id (get-data self :id)
+                parent-id (get-state self :parent-id)
+                parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])]
+            (st/emit! (udw/update-position id {:x (+ parent-x value)}))))}
+
+       {:name "parentY"
+        :get (fn [self]
+               (let [page-id (:current-page-id @st/state)
+                     parent-id (get-state self :parent-id)
+                     parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])]
+                 (- (get-state self :y) parent-y)))
+        :set
+        (fn [self value]
+          (let [page-id (:current-page-id @st/state)
+                id (get-data self :id)
+                parent-id (get-state self :parent-id)
+                parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])]
+            (st/emit! (udw/update-position id {:y (+ parent-y value)}))))}
+
+       {:name "frameX"
+        :get (fn [self]
+               (let [page-id (:current-page-id @st/state)
+                     frame-id (get-state self :frame-id)
+                     frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])]
+                 (- (get-state self :x) frame-x)))
+        :set
+        (fn [self value]
+          (let [page-id (:current-page-id @st/state)
+                id (get-data self :id)
+                frame-id (get-state self :frame-id)
+                frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])]
+            (st/emit! (udw/update-position id {:x (+ frame-x value)}))))}
+
+       {:name "frameY"
+        :get (fn [self]
+               (let [page-id (:current-page-id @st/state)
+                     frame-id (get-state self :frame-id)
+                     frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])]
+                 (- (get-state self :y) frame-y)))
+        :set
+        (fn [self value]
+          (let [page-id (:current-page-id @st/state)
+                id (get-data self :id)
+                frame-id (get-state self :frame-id)
+                frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])]
+            (st/emit! (udw/update-position id {:y (+ frame-y value)}))))}
+
        {:name "width"
         :get #(get-state % :width)}
 
@@ -116,18 +189,50 @@
                (let [id (get-data self :id)]
                  (st/emit! (dwc/update-shapes [id] #(assoc % :name value)))))}
 
-       {:name "children"
-        :get #(.getChildren ^js %)}
-
        {:name "fills"
         :get #(get-state % :fills make-fills)
-        ;;:set (fn [self value] (.log js/console self value))
-        }
+        :set (fn [self value]
+               (let [id (get-data self :id)
+                     value (mapv #(utils/from-js %) value)]
+                 (st/emit! (dwc/update-shapes [id] #(assoc % :fills value)))))}
 
        {:name "strokes"
         :get #(get-state % :strokes make-strokes)
-        ;;:set (fn [self value] (.log js/console self value))
-        })
+        :set (fn [self value]
+               (let [id (get-data self :id)
+                     value (mapv #(utils/from-js %) value)]
+                 (st/emit! (dwc/update-shapes [id] #(assoc % :strokes value)))))})
+
+      (cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data))
+        (crc/add-properties!
+         {:name "children"
+          :get #(.getChildren ^js %)}))
+
+      (cond-> (not (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)))
+        (-> (obj/unset! "appendChild")
+            (obj/unset! "insertChild")
+            (obj/unset! "getChildren")))
+
+      (cond-> (cfh/frame-shape? data)
+        (-> (crc/add-properties!
+             {:name "grid"
+              :get
+              (fn [self]
+                (let [layout (get-state self :layout)]
+                  (when (= :grid layout)
+                    (grid/grid-layout-proxy data))))})
+
+            #_(crc/add-properties!
+               {:name "flex"
+                :get
+                (fn [self]
+                  (let [layout (get-state self :layout)]
+                    (when (= :flex layout)
+                      (flex-layout-proxy data))))})))
+
+      (cond-> (not (cfh/frame-shape? data))
+        (-> (obj/unset! "addGridLayout")
+            (obj/unset! "addFlexLayout")))
 
       (cond-> (cfh/text-shape? data)
         (crc/add-properties!
@@ -136,4 +241,3 @@
           :set (fn [self value]
                  (let [id (get-data self :id)]
                    (st/emit! (dwc/update-shapes [id] #(txt/change-text % value)))))}))))
-
diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs
index 1b392d84c..35022c36e 100644
--- a/frontend/src/app/plugins/utils.cljs
+++ b/frontend/src/app/plugins/utils.cljs
@@ -7,7 +7,14 @@
 (ns app.plugins.utils
   "RPC for plugins runtime."
   (:require
-   [app.util.object :as obj]))
+   [app.common.data :as d]
+   [app.common.data.macros :as dm]
+   [app.common.spec :as us]
+   [app.common.uuid :as uuid]
+   [app.main.store :as st]
+   [app.util.object :as obj]
+   [cuerdas.core :as str]
+   [promesa.core :as p]))
 
 (defn get-data
   ([self attr]
@@ -27,4 +34,62 @@
    (fn [self]
      (get-data self attr transform-fn))))
 
+(defn get-state
+  ([self attr]
+   (let [id (get-data self :id)
+         page-id (d/nilv (get-data self :page-id) (:current-page-id @st/state))]
+     (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id attr])))
+  ([self attr mapfn]
+   (-> (get-state self attr)
+       (mapfn))))
 
+(defn from-js
+  "Converts the object back to js"
+  ([obj]
+   (from-js obj identity))
+  ([obj vfn]
+   (let [ret (js->clj obj {:keyword-fn (fn [k] (str/camel (name k)))})]
+     (reduce-kv
+      (fn [m k v]
+        (let [k (keyword (str/kebab k))
+              v (cond (map? v)
+                      (from-js v)
+
+                      (and (string? v) (re-matches us/uuid-rx v))
+                      (uuid/uuid v)
+
+                      :else (vfn k v))]
+          (assoc m k v)))
+      {}
+      ret))))
+
+(defn to-js
+  "Converts to javascript an camelize the keys"
+  [obj]
+  (let [result
+        (reduce-kv
+         (fn [m k v]
+           (let [v (cond (object? v) (to-js v)
+                         (uuid? v) (dm/str v)
+                         :else v)]
+             (assoc m (str/camel (name k)) v)))
+         {}
+         obj)]
+    (clj->js result)))
+
+(defn result-p
+  "Creates a pair of atom+promise. The promise will be resolved when the atom gets a value.
+  We use this to return the promise to the library clients and resolve its value when a value is passed
+  to the atom"
+  []
+  (let [ret-v (atom nil)
+        ret-p
+        (p/create
+         (fn [resolve _]
+           (add-watch
+            ret-v
+            ::watcher
+            (fn [_ _ _ value]
+              (remove-watch ret-v ::watcher)
+              (resolve value)))))]
+    [ret-v ret-p]))
diff --git a/frontend/src/app/plugins/viewport.cljs b/frontend/src/app/plugins/viewport.cljs
new file mode 100644
index 000000000..656a65674
--- /dev/null
+++ b/frontend/src/app/plugins/viewport.cljs
@@ -0,0 +1,78 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns app.plugins.viewport
+  "RPC for plugins runtime."
+  (:require
+   [app.common.data.macros :as dm]
+   [app.common.record :as crc]
+   [app.common.spec :as us]
+   [app.common.uuid :as uuid]
+   [app.main.data.workspace.viewport :as dwv]
+   [app.main.data.workspace.zoom :as dwz]
+   [app.main.store :as st]
+   [app.util.object :as obj]))
+
+(deftype ViewportProxy []
+  Object
+  (zoomIntoView [_ shapes]
+    (let [ids
+          (->> shapes
+               (map (fn [v]
+                      (if (string? v)
+                        (uuid/uuid v)
+                        (uuid/uuid (obj/get v "x"))))))]
+      (st/emit! (dwz/fit-to-shapes ids)))))
+
+(crc/define-properties!
+  ViewportProxy
+  {:name js/Symbol.toStringTag
+   :get (fn [] (str "ViewportProxy"))})
+
+(defn create-proxy
+  []
+  (crc/add-properties!
+   (ViewportProxy.)
+   {:name "center"
+    :get
+    (fn [_]
+      (let [vp (dm/get-in @st/state [:workspace-local :vbox])
+            x (+ (:x vp) (/ (:width vp) 2))
+            y (+ (:y vp) (/ (:height vp) 2))]
+        (.freeze js/Object #js {:x x :y y})))
+
+    :set
+    (fn [_ value]
+      (let [new-x (obj/get value "x")
+            new-y (obj/get value "y")]
+        (when (and (us/safe-number? new-x) (us/safe-number? new-y))
+          (let [vb (dm/get-in @st/state [:workspace-local :vbox])
+                old-x (+ (:x vb) (/ (:width vb) 2))
+                old-y (+ (:y vb) (/ (:height vb) 2))
+                delta-x (- new-x old-x)
+                delta-y (- new-y old-y)
+                to-position
+                {:x #(+ % delta-x)
+                 :y #(+ % delta-y)}]
+            (st/emit! (dwv/update-viewport-position to-position))))))}
+
+   {:name "zoom"
+    :get
+    (fn [_]
+      (dm/get-in @st/state [:workspace-local :zoom]))
+    :set
+    (fn [_ value]
+      (when (us/safe-number? value)
+        (let [z (dm/get-in @st/state [:workspace-local :zoom])]
+          (st/emit! (dwz/set-zoom (/ value z))))))}
+
+   {:name "bounds"
+    :get
+    (fn [_]
+      (let [vport (dm/get-in @st/state [:workspace-local :vport])]
+        (.freeze js/Object (clj->js vport))))}))
+
+