diff --git a/.gitignore b/.gitignore
index 865f8a01c498463f28ac3fec92c40ff4af8578f0..fcc5e236b06d253c4e053db22d0bd7b3fe6baefc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,4 @@
 **/dist/*
 .env.local
 nbactions.xml
-
+.env.development.local
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 c7bb9c48f643964c4e6a1a890ff2d6a5229f4117..eecc18833c2513bfe27b7c74e7271d071c90ff8b 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
@@ -2,8 +2,8 @@ package it.inaf.ia2.vospace.ui.client;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import it.inaf.ia2.aa.data.User;
-import it.inaf.ia2.vospace.ui.VOSpaceException;
 import it.inaf.ia2.vospace.ui.VOSpaceUiApplication;
+import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringWriter;
@@ -40,6 +40,9 @@ public class VOSpaceClient {
     @Value("${use-json}")
     private boolean useJson;
 
+    @Value("${vospace-authority}")
+    private String authority;
+
     private static final ObjectMapper MAPPER = new ObjectMapper();
 
     private final HttpClient httpClient;
@@ -95,6 +98,19 @@ public class VOSpaceClient {
         return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Transfer.class)).getProtocols();
     }
 
+    public Node createNode(Node node) {
+
+        String path = node.getUri().substring(("vos://" + authority).length());
+
+        HttpRequest request = getRequest("/nodes" + path)
+                .header("Accept", useJson ? "application/json" : "text/xml")
+                .header("Content-Type", useJson ? "application/json" : "text/xml")
+                .PUT(HttpRequest.BodyPublishers.ofString(marshal(node)))
+                .build();
+
+        return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Node.class));
+    }
+
     private <T, U> U call(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler, int expectedStatusCode, Function<T, U> responseHandler) {
         try {
             return httpClient.sendAsync(request, responseBodyHandler)
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 4e2abc5b8c8e46638a9d31e7826832f70ec45187..71dfa9b7d1e017229b4dc99cb6f7061827488a03 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
@@ -1,8 +1,11 @@
 package it.inaf.ia2.vospace.ui.controller;
 
 import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
+import it.inaf.ia2.vospace.ui.exception.BadRequestException;
 import it.inaf.ia2.vospace.ui.service.NodesService;
+import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
+import net.ivoa.xml.vospace.v2.ContainerNode;
 import net.ivoa.xml.vospace.v2.Protocol;
 import net.ivoa.xml.vospace.v2.Transfer;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -12,6 +15,8 @@ import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 
 @RestController
@@ -64,6 +69,28 @@ public class NodesController {
         return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
     }
 
+    @PostMapping(value = "/folder")
+    public void newFolder(@RequestBody Map<String, String> params) {
+
+        String parentPath = getRequiredParam(params, "parentPath");
+        if (!parentPath.startsWith("/")) {
+            parentPath = "/" + parentPath;
+        }
+        String name = getRequiredParam(params, "name");
+
+        ContainerNode node = new ContainerNode();
+        node.setUri("vos://" + authority + parentPath + "/" + name);
+
+        client.createNode(node);
+    }
+
+    private String getRequiredParam(Map<String, String> params, String key) {
+        if (!params.containsKey(key)) {
+            throw new BadRequestException("Missing mandatory parameter " + key);
+        }
+        return params.get(key);
+    }
+
     /**
      * Slash is a special character in defining REST endpoints and trying to
      * define a PathVariable containing slashes doesn't work, so the endpoint
diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/exception/BadRequestException.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/exception/BadRequestException.java
new file mode 100644
index 0000000000000000000000000000000000000000..d686851e037a5ef0dce0d9e89ecad17b64d381d5
--- /dev/null
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/exception/BadRequestException.java
@@ -0,0 +1,12 @@
+package it.inaf.ia2.vospace.ui.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(value = HttpStatus.BAD_REQUEST)
+public class BadRequestException extends VOSpaceException {
+
+    public BadRequestException(String message) {
+        super(message);
+    }
+}
diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/exception/VOSpaceException.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/exception/VOSpaceException.java
index 2b227f5cd5b27b4bd956a6bb2690a0b5e7aebb80..41e5639ddf5316329da26cf9df821b3b89e0de14 100644
--- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/exception/VOSpaceException.java
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/exception/VOSpaceException.java
@@ -1,4 +1,4 @@
-package it.inaf.ia2.vospace.ui;
+package it.inaf.ia2.vospace.ui.exception;
 
 public class VOSpaceException extends RuntimeException {
 
diff --git a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodeInfo.java b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodeInfo.java
index 0919783aa1fc300efea6bb315bac64c3f6b7f017..74254a4f76914a9a8fa8daee13a00bedeab661ed 100644
--- a/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodeInfo.java
+++ b/vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/service/NodeInfo.java
@@ -1,6 +1,6 @@
 package it.inaf.ia2.vospace.ui.service;
 
-import it.inaf.ia2.vospace.ui.VOSpaceException;
+import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
 import java.util.Optional;
 import net.ivoa.xml.vospace.v2.Node;
 import net.ivoa.xml.vospace.v2.Property;
diff --git a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java
index 107d040e5670f38066f54559e01938eb32818b63..5cd931c097606454513fc6f4962ee319299a051a 100644
--- a/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java
+++ b/vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java
@@ -15,6 +15,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import static org.mockito.Mockito.mock;
@@ -40,6 +41,7 @@ public class VOSpaceClientTest {
         try ( MockedStatic<HttpClient> staticMock = Mockito.mockStatic(HttpClient.class)) {
             staticMock.when(HttpClient::newBuilder).thenReturn(builder);
             voSpaceClient = new VOSpaceClient("http://localhost/vospace");
+            ReflectionTestUtils.setField(voSpaceClient, "authority", "ia2.inaf.it!vospace");
         }
 
         voSpaceClient.servletRequest = mock(HttpServletRequest.class);
@@ -56,6 +58,24 @@ public class VOSpaceClientTest {
         assertEquals("vos://ia2.inaf.it!vospace/node1", node.getUri());
     }
 
+    @Test
+    public void testCreateNode() {
+
+        ReflectionTestUtils.setField(voSpaceClient, "useJson", false);
+
+        CompletableFuture response = getMockedStreamResponseFuture(200, getResourceFileContent("node-response.xml"));
+        when(mockedHttpClient.sendAsync(argThat(request -> {
+            assertEquals("/vospace/nodes/mynode/newnode", request.uri().getPath());
+            return true;
+        }), any())).thenReturn(response);
+
+        ContainerNode newNode = new ContainerNode();
+        newNode.setUri("vos://ia2.inaf.it!vospace/mynode/newnode");
+
+        ContainerNode responseNode = (ContainerNode) voSpaceClient.createNode(newNode);
+        assertEquals(newNode.getUri(), responseNode.getUri());
+    }
+
     protected static String getResourceFileContent(String fileName) {
         try ( InputStream in = VOSpaceClientTest.class.getClassLoader().getResourceAsStream(fileName)) {
             return new String(in.readAllBytes(), StandardCharsets.UTF_8);
diff --git a/vospace-ui-backend/src/test/resources/node-response.xml b/vospace-ui-backend/src/test/resources/node-response.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c87b4aecd461ccfe16d59fba45d3b24a7f573835
--- /dev/null
+++ b/vospace-ui-backend/src/test/resources/node-response.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<vos:node xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+          busy="false" xsi:type="vos:ContainerNode" uri="vos://ia2.inaf.it!vospace/mynode/newnode">
+    <vos:properties>
+        <vos:property uri="ivo://ivoa.net/vospace/core#btime">2021-01-15 18:24:03.749352</vos:property>
+        <vos:property uri="ivo://ivoa.net/vospace/core#groupwrite">group1</vos:property>
+    </vos:properties>
+</vos:node>
\ No newline at end of file
diff --git a/vospace-ui-frontend/src/api/mock/index.js b/vospace-ui-frontend/src/api/mock/index.js
index 7f031754583d083c5564d470f4767fc73124a6ff..d43470f1e50d93800bb77cdfda000d94c81dae61 100644
--- a/vospace-ui-frontend/src/api/mock/index.js
+++ b/vospace-ui-frontend/src/api/mock/index.js
@@ -45,5 +45,8 @@ export default {
   },
   getUserInfo() {
     return fetch(user, false);
+  },
+  createFolder() {
+    return fetch({});
   }
 }
diff --git a/vospace-ui-frontend/src/api/server/index.js b/vospace-ui-frontend/src/api/server/index.js
index 077a7c036464d6f746b794b21a3f71dc4e190274..a21e61fd14e8890fbcf78161a62459bec5142402 100644
--- a/vospace-ui-frontend/src/api/server/index.js
+++ b/vospace-ui-frontend/src/api/server/index.js
@@ -84,5 +84,20 @@ export default {
       },
       data: paths
     });
+  },
+  createFolder(path, newFolderName) {
+    let url = BASE_API_URL + 'folder';
+    return apiRequest({
+      method: 'POST',
+      url: url,
+      withCredentials: true,
+      headers: {
+        'Cache-Control': 'no-cache'
+      },
+      data: {
+        parentPath: path,
+        name: newFolderName
+      }
+    });
   }
 }
diff --git a/vospace-ui-frontend/src/components/Main.vue b/vospace-ui-frontend/src/components/Main.vue
index 56c9563dea312fabf8ccd2844fe2d634acde0ffc..a2e17aebf39cdc4e047742324ff53802dbed7cdc 100644
--- a/vospace-ui-frontend/src/components/Main.vue
+++ b/vospace-ui-frontend/src/components/Main.vue
@@ -2,7 +2,7 @@
 <div class="container">
   <b-breadcrumb :items="breadcrumbs"></b-breadcrumb>
   <div class="mb-3">
-    <b-button variant="success" class="mr-2" :disabled="true">New folder</b-button>
+    <b-button variant="success" class="mr-2" :disabled="false" v-b-modal.create-folder-modal>New folder</b-button>
     <b-button variant="success" class="mr-2" :disabled="true">Upload files</b-button>
     <b-button variant="primary" class="mr-2" v-if="tapeButtonEnabled" @click="startRecallFromTapeJob">Recall from tape</b-button>
   </div>
@@ -28,16 +28,19 @@
       <tbody id="nodes"></tbody>
     </table>
   </b-card>
+  <CreateFolderModal />
 </div>
 </template>
 
 <script>
 import { BIconCheckSquare, BIconSquare } from 'bootstrap-vue'
+import CreateFolderModal from './modal/CreateFolderModal.vue'
 
 export default {
   components: {
     BIconCheckSquare,
-    BIconSquare
+    BIconSquare,
+    CreateFolderModal
   },
   computed: {
     breadcrumbs() {
diff --git a/vospace-ui-frontend/src/components/modal/CreateFolderModal.vue b/vospace-ui-frontend/src/components/modal/CreateFolderModal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a3ff578a973b943bdb392fe0abb1568c2d309717
--- /dev/null
+++ b/vospace-ui-frontend/src/components/modal/CreateFolderModal.vue
@@ -0,0 +1,56 @@
+<template>
+<b-modal id="create-folder-modal" title="Create folder" okTitle="Create" @show="reset" @ok="createFolder">
+  <b-form inline>
+    <label class="w-25" for="new-folder-name-input">Folder name</label>
+    <b-form-input v-model.trim="newFolderName" id="new-folder-name-input" ref="newFolderNameInput" class="w-75" aria-describedby="new-folder-name-input-feedback" :state="newFolderNameState" v-on:input="resetError"
+      @keydown.native.enter="createFolder">
+    </b-form-input>
+    <b-form-invalid-feedback id="new-folder-name-input-feedback" class="text-right">{{newFolderNameError}}</b-form-invalid-feedback>
+  </b-form>
+</b-modal>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      newFolderName: null,
+      newFolderNameError: null
+    }
+  },
+  computed: {
+    newFolderNameState() {
+      if (this.newFolderNameError) {
+        return false;
+      }
+      return null;
+    }
+  },
+  methods: {
+    reset() {
+      this.newFolderName = null;
+      this.resetError();
+    },
+    resetError() {
+      this.newFolderNameError = null;
+    },
+    createFolder(event) {
+      // Prevent modal from closing
+      event.preventDefault();
+
+      if (!this.newFolderName) {
+        this.newFolderNameError = "Folder name is required";
+      } else {
+        this.$store.dispatch('createFolder', this.newFolderName)
+          .then(() => { //res
+            //this.$store.commit('updateGroupsPanel', res);
+            this.$bvModal.hide('create-folder-modal');
+          })
+          .catch(res => {
+            this.newFolderNameError = res.message;
+          });
+      }
+    }
+  }
+}
+</script>
diff --git a/vospace-ui-frontend/src/store.js b/vospace-ui-frontend/src/store.js
index a4cb6813b50103fcf8a361a9f1da25330d0a626a..cbf3bb95ccd9cbaae5a296f3dd0a192aea9aacc3 100644
--- a/vospace-ui-frontend/src/store.js
+++ b/vospace-ui-frontend/src/store.js
@@ -80,6 +80,13 @@ export default new Vuex.Store({
     loadUserInfo({ commit }) {
       client.getUserInfo()
         .then(res => commit('setUsername', res.username));
+    },
+    createFolder({ state, dispatch }, newFolderName) {
+      client.createFolder(state.path, newFolderName)
+        .then(() => {
+          // Reload current node
+          dispatch('setPath', state.path);
+        });
     }
   }
 });