diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java
index 34e53b4140049258dfe1faf50427639f4c1f2428..b1088d392b89050f0e6933d0034dbba9d103c378 100644
--- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java
@@ -24,7 +24,6 @@ import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandlers;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import java.util.Scanner;
 import java.util.concurrent.CompletionException;
 import java.util.concurrent.ForkJoinPool;
diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java
index 05992e8769e2a0a68c9c5d36944cbc81cf01d167..f7450ae8cb8e5018251b4a7769f05a94cd80522d 100644
--- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/controller/NodesController.java
@@ -16,6 +16,7 @@ import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 import javax.servlet.http.HttpServletRequest;
+import net.ivoa.xml.uws.v1.JobSummary;
 import net.ivoa.xml.vospace.v2.ContainerNode;
 import net.ivoa.xml.vospace.v2.Node;
 import net.ivoa.xml.vospace.v2.Property;
@@ -134,6 +135,21 @@ public class NodesController extends BaseController {
         return ResponseEntity.noContent().build();
     }
 
+    @PostMapping(value = "/move")
+    public void moveNode(@RequestBody Map<String, Object> params) {
+
+        String target = getRequiredParam(params, "target");
+        String direction = getRequiredParam(params, "direction");
+
+        Transfer transfer = new Transfer();
+        transfer.setTarget(target);
+        transfer.setDirection(direction);
+        
+        JobSummary job = client.startTransferJob(transfer);
+        
+        // TODO: polling
+    }
+
     protected String getPath(String prefix) {
         String requestURL = servletRequest.getRequestURL().toString();
         return NodeUtils.getPathFromRequestURLString(requestURL, prefix);
diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesHtmlGenerator.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesHtmlGenerator.java
index b087e5fbd942befc8217bd1c05119baa61e96752..2d2d1200bd65aed593876a73e24f41e37a188492 100644
--- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesHtmlGenerator.java
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodesHtmlGenerator.java
@@ -130,18 +130,36 @@ public class NodesHtmlGenerator {
 
     private void addActionsCell(NodeInfo nodeInfo, Element row) {
         Element cell = row.appendElement("td");
+
+        Element dotsMenu = cell.appendElement("span");
+        dotsMenu.attr("class", "dots-menu icon dots-menu-icon pointer");
+
+        Element dropdown = dotsMenu.appendElement("span");
+        dropdown.attr("class", "dots-menu-content dropdown-menu");
+
+        String nodePathJs = makeJsArg(nodeInfo.getPath());
+
         if (nodeInfo.isWritable()) {
-            Element shareIcon = cell.appendElement("span");
-            shareIcon.attr("class", "icon share-icon pointer");
-            shareIcon.attr("onclick", "shareNode(" + makeJsArg(nodeInfo.getPath())
+            Element shareBtn = dropdown.appendElement("button");
+            shareBtn.text("Share");
+            shareBtn.attr("type", "button");
+            shareBtn.attr("class", "dropdown-item");
+            shareBtn.attr("onclick", "shareNode(" + nodePathJs
                     + "," + makeJsArg(nodeInfo.getGroupRead())
                     + "," + makeJsArg(nodeInfo.getGroupWrite()) + ")");
         }
         if (nodeInfo.isDeletable()) {
-            cell.append("&nbsp;");
-            Element deleteIcon = cell.appendElement("span");
-            deleteIcon.attr("class", "icon trash-icon pointer");
-            deleteIcon.attr("onclick", "deleteNode(" + makeJsArg(nodeInfo.getPath()) + ")");
+            Element renameBtn = dropdown.appendElement("button");
+            renameBtn.text("Rename");
+            renameBtn.attr("type", "button");
+            renameBtn.attr("class", "dropdown-item");
+            renameBtn.attr("onclick", "renameNode(" + nodePathJs + ")");
+
+            Element deleteBtn = dropdown.appendElement("button");
+            deleteBtn.text("Delete");
+            deleteBtn.attr("type", "button");
+            deleteBtn.attr("class", "dropdown-item");
+            deleteBtn.attr("onclick", "deleteNode(" + nodePathJs + ")");
         }
     }
 
diff --git a/vospace-ui-frontend/src/App.vue b/vospace-ui-frontend/src/App.vue
index 8e6f9c61b9cfd6733320ca13e37e8d959cad0cbb..12d71e245a0a955a955a454b5c75b11d41ef27a3 100644
--- a/vospace-ui-frontend/src/App.vue
+++ b/vospace-ui-frontend/src/App.vue
@@ -47,6 +47,19 @@ export default {
         self.$store.dispatch('checkJobs');
       }
     }, 1000);
+
+    // Add event listener for dots menus
+    document.addEventListener('click', function(event) {
+      if (event.target.classList.contains('dots-menu')) {
+        event.target.classList.toggle('active')
+      } else {
+        if (event.target.closest('.dots-menu') === null) {
+          for (let menu of document.querySelectorAll('.dots-menu')) {
+            menu.classList.remove('active');
+          }
+        }
+      }
+    });
   }
 }
 </script>
@@ -111,4 +124,22 @@ export default {
 .node-busy+.icon {
   margin-right: 3px;
 }
+
+.dots-menu {
+  position: relative;
+}
+
+.dots-menu-content {
+  display: none;
+  position: absolute;
+  /*background-color: #f9f9f9;
+  min-width: 160px;
+  box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
+  padding: 12px 16px;
+  z-index: 1;*/
+}
+
+.dots-menu.active .dots-menu-content {
+  display: block;
+}
 </style>
diff --git a/vospace-ui-frontend/src/api/server/index.js b/vospace-ui-frontend/src/api/server/index.js
index 92f6185a1e5206c16faf30fab098461d1e1ed611..5d9284030f262970c22f766aeda44f42d0425dcf 100644
--- a/vospace-ui-frontend/src/api/server/index.js
+++ b/vospace-ui-frontend/src/api/server/index.js
@@ -180,5 +180,17 @@ export default {
       },
       data
     }, true, true);
+  },
+  moveNode(data) {
+    let url = BASE_API_URL + 'move';
+    return apiRequest({
+      method: 'POST',
+      url: url,
+      withCredentials: true,
+      headers: {
+        'Cache-Control': 'no-cache'
+      },
+      data
+    }, true, true);
   }
 }
diff --git a/vospace-ui-frontend/src/assets/css/fonts.css b/vospace-ui-frontend/src/assets/css/fonts.css
index 29beec4331331351ab38c2bc0b8345933447f44d..bdd3cecc3715c30d026ea23f1f8115c78de4a96c 100644
--- a/vospace-ui-frontend/src/assets/css/fonts.css
+++ b/vospace-ui-frontend/src/assets/css/fonts.css
@@ -44,3 +44,7 @@
 .person-icon {
   background-image: url("data:image/svg+xml,%3Csvg data-v-41be6633='' viewBox='0 0 16 16' width='1em' height='1em' focusable='false' role='img' aria-label='person fill' xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='bi-person-fill mx-auto b-icon bi'%3E%3Cg data-v-41be6633=''%3E%3Cpath d='M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
 }
+
+.dots-menu-icon {
+  background-image: url("data:image/svg+xml,%3Csvg data-v-41be6633='' viewBox='0 0 16 16' width='1em' height='1em' focusable='false' role='img' aria-label='three dots vertical' xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='bi-three-dots-vertical mx-auto b-icon bi'%3E%3Cg data-v-41be6633=''%3E%3Cpath d='M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
+}
diff --git a/vospace-ui-frontend/src/components/Main.vue b/vospace-ui-frontend/src/components/Main.vue
index 9edb15f7c1c3a6959f3a8157007971c0b0fb299c..c2be3ae3bb5ce2f38cdd1c0947bf5d5f93dfd220 100644
--- a/vospace-ui-frontend/src/components/Main.vue
+++ b/vospace-ui-frontend/src/components/Main.vue
@@ -39,6 +39,7 @@
   <UploadFilesModal />
   <ConfirmDeleteModal />
   <ShareModal />
+  <RenameModal />
 </div>
 </template>
 
@@ -48,6 +49,7 @@ import CreateFolderModal from './modal/CreateFolderModal.vue'
 import UploadFilesModal from './modal/UploadFilesModal.vue'
 import ConfirmDeleteModal from './modal/ConfirmDeleteModal.vue'
 import ShareModal from './modal/ShareModal.vue'
+import RenameModal from './modal/RenameModal.vue'
 
 export default {
   components: {
@@ -56,7 +58,8 @@ export default {
     CreateFolderModal,
     UploadFilesModal,
     ConfirmDeleteModal,
-    ShareModal
+    ShareModal,
+    RenameModal
   },
   computed: {
     breadcrumbs() {
diff --git a/vospace-ui-frontend/src/components/modal/RenameModal.vue b/vospace-ui-frontend/src/components/modal/RenameModal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a24b4f6a05f262c35f19edba3a4fbef51c2c6efe
--- /dev/null
+++ b/vospace-ui-frontend/src/components/modal/RenameModal.vue
@@ -0,0 +1,71 @@
+<!--
+  This file is part of vospace-ui
+  Copyright (C) 2021 Istituto Nazionale di Astrofisica
+  SPDX-License-Identifier: GPL-3.0-or-later
+-->
+<template>
+<b-modal id="rename-modal" :title="'Rename ' + nodeToRename" okTitle="Rename" @show="reset" @shown="afterShow" @ok.prevent="renameNode">
+  <b-form inline>
+    <label class="w-25" for="new-name-input">New name</label>
+    <b-form-input v-model.trim="newName" id="new-name-input" ref="newNameInput" class="w-75" aria-describedby="new-name-input-feedback" :state="newNameState" v-on:input="resetError" @keydown.native.enter="renameNode">
+    </b-form-input>
+    <b-form-invalid-feedback id="new-folder-name-input-feedback" class="text-right">{{newNameError}}</b-form-invalid-feedback>
+  </b-form>
+</b-modal>
+</template>
+
+<script>
+export default {
+  name: 'RenameModal',
+  computed: {
+    nodeToRename() { return this.$store.state.nodeToRename },
+    oldName() { return this.nodeToRename.substring(this.nodeToRename.lastIndexOf("/") + 1) },
+    newNameState() {
+      if (this.newNameError) {
+        return false;
+      }
+      return null;
+    }
+  },
+  data() {
+    return {
+      newName: this.oldName,
+      newNameError: null
+    }
+  },
+  methods: {
+    afterShow: function() {
+      this.$refs.newNameInput.focus();
+    },
+    reset() {
+      this.newName = this.oldName;
+      this.resetError();
+    },
+    resetError() {
+      this.newNameError = null;
+    },
+    renameNode() {
+      if (this.newName === this.oldName) {
+        return;
+      }
+      if (!this.newName) {
+        this.newNameError = "Name is required";
+      } else if (/[<>?":\\/`|'*]/.test(this.newName)) {
+        this.newNameError = "Name contains an illegal character. Following characters are not allowed: < > ? \" : \\ / | ' * `";
+      } else {
+        let parentPath = this.oldName.substring(0, this.oldName.lastIndexOf('/') + 1);
+        this.$store.dispatch('moveNode', {
+            target: parentPath + this.oldName,
+            direction: parentPath + this.newName
+          })
+          .then(() => {
+            this.$bvModal.hide('rename-modal');
+          })
+          .catch(res => {
+            this.newFolderNameError = res.message;
+          });
+      }
+    }
+  }
+}
+</script>
diff --git a/vospace-ui-frontend/src/main.js b/vospace-ui-frontend/src/main.js
index 8736a519a5c913ae7fbe2503be2145443e9ec650..d1caac178c7a6d168d56bef1ed36cfab97495f9b 100644
--- a/vospace-ui-frontend/src/main.js
+++ b/vospace-ui-frontend/src/main.js
@@ -31,6 +31,10 @@ window.shareNode = function(path, groupRead, groupWrite) {
   store.commit('setNodeToShare', { path, groupRead, groupWrite });
   vm.$bvModal.show('share-modal');
 }
+window.renameNode = function(path) {
+  store.commit('setNodeToRename', path);
+  vm.$bvModal.show('rename-modal');
+}
 
 export default {
   showError(message) {
diff --git a/vospace-ui-frontend/src/store.js b/vospace-ui-frontend/src/store.js
index bae41d1ad2a569c037498580f6f9169d2a684f72..d10ef32385e3a008f64e289a856ee7b0653feca1 100644
--- a/vospace-ui-frontend/src/store.js
+++ b/vospace-ui-frontend/src/store.js
@@ -38,7 +38,8 @@ export default new Vuex.Store({
       path: null,
       groupRead: null,
       groupWrite: null
-    }
+    },
+    nodeToRename: null
   },
   mutations: {
     setLoading(state, loading) {
@@ -81,6 +82,9 @@ export default new Vuex.Store({
       state.nodeToShare.path = data.path;
       state.nodeToShare.groupRead = data.groupRead;
       state.nodeToShare.groupWrite = data.groupWrite;
+    },
+    setNodeToRename(state, path) {
+      state.nodeToRename = path;
     }
   },
   actions: {
@@ -190,6 +194,13 @@ export default new Vuex.Store({
           // Reload current node
           dispatch('setPath', state.path);
         });
+    },
+    moveNode({ state, dispatch }, data) {
+      client.moveNode(data)
+        .then(() => {
+          // Reload current node
+          dispatch('setPath', state.path);
+        });
     }
   }
 });