diff --git a/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/dto/operation/CreateOperation.java b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/dto/operation/CreateOperation.java
index 0985ee5c0c308ac536750a96249865d2abbd6ba0..9ab78216afcbe4ae152cc967222b67783b124adc 100644
--- a/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/dto/operation/CreateOperation.java
+++ b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/dto/operation/CreateOperation.java
@@ -45,7 +45,7 @@ public class CreateOperation implements Operation
     {
         this.owner = owner;
         this.locks = List.of(new Lock<>(owner, source, LockType.Exclusive));
-        this.name = OperationName.Write;
+        this.name = OperationName.Create;
         this.source = source;
         this.directory = directory;
     }
diff --git a/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionConnection.java b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionConnection.java
index 8ea39cd14e36c0ef8a97c65a8fcb70c8f8cd0102..0f6ddafc4140b848e762e2c5715ed799cccb52bf 100644
--- a/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionConnection.java
+++ b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionConnection.java
@@ -39,6 +39,7 @@ import ch.ethz.sis.afs.dto.operation.ReadOperation;
 import ch.ethz.sis.afs.dto.operation.WriteOperation;
 import ch.ethz.sis.afs.exception.AFSExceptions;
 import ch.ethz.sis.afs.manager.operation.CopyOperationExecutor;
+import ch.ethz.sis.afs.manager.operation.CreateOperationExecutor;
 import ch.ethz.sis.afs.manager.operation.DeleteOperationExecutor;
 import ch.ethz.sis.afs.manager.operation.ListOperationExecutor;
 import ch.ethz.sis.afs.manager.operation.MoveOperationExecutor;
@@ -63,7 +64,8 @@ public class TransactionConnection implements TransactionalFileSystem {
         operationExecutors = Map.of(OperationName.Copy, CopyOperationExecutor.getInstance(),
                 OperationName.Delete, DeleteOperationExecutor.getInstance(),
                 OperationName.Move, MoveOperationExecutor.getInstance(),
-                OperationName.Write, WriteOperationExecutor.getInstance());
+                OperationName.Write, WriteOperationExecutor.getInstance(),
+                OperationName.Create, CreateOperationExecutor.getInstance());
     }
 
     private LockManager<UUID, String> lockManager;
diff --git a/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/operation/CreateOperationExecutor.java b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/operation/CreateOperationExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..76c03ee18870961f3f4d91b71b7acc33397aef51
--- /dev/null
+++ b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/operation/CreateOperationExecutor.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright ETH 2022 - 2023 Zürich, Scientific IT Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.ethz.sis.afs.manager.operation;
+
+import static ch.ethz.sis.afs.exception.AFSExceptions.MD5NotMatch;
+import static ch.ethz.sis.afs.exception.AFSExceptions.PathInStore;
+import static ch.ethz.sis.afs.exception.AFSExceptions.PathIsDirectory;
+
+import java.util.Arrays;
+
+import ch.ethz.sis.afs.api.dto.File;
+import ch.ethz.sis.afs.dto.Transaction;
+import ch.ethz.sis.afs.dto.operation.CreateOperation;
+import ch.ethz.sis.afs.dto.operation.OperationName;
+import ch.ethz.sis.afs.dto.operation.WriteOperation;
+import ch.ethz.sis.afs.exception.AFSExceptions;
+import ch.ethz.sis.shared.io.IOUtils;
+import lombok.NonNull;
+
+public class CreateOperationExecutor implements OperationExecutor<CreateOperation> {
+
+    //
+    // Singleton
+    //
+
+    private static final CreateOperationExecutor INSTANCE;
+
+    static {
+        INSTANCE = new CreateOperationExecutor();
+    }
+
+    private CreateOperationExecutor() {
+    }
+
+    public static CreateOperationExecutor getInstance() {
+        return INSTANCE;
+    }
+
+    //
+    // Operation
+    //
+
+
+    @Override
+    public boolean prepare(final @NonNull Transaction transaction, final CreateOperation operation) throws Exception {
+        // Check that file/directory does not exist
+        if (IOUtils.exists(operation.getSource()))
+        {
+            AFSExceptions.throwInstance(PathInStore, OperationName.Create.name(), operation.getSource());
+        }
+        return true;
+    }
+
+    @Override
+    public boolean commit(final @NonNull Transaction transaction, final CreateOperation operation) throws Exception {
+        final String directoriesToCreate = operation.isDirectory() ? operation.getSource() : IOUtils.getParentPath(operation.getSource());
+        IOUtils.createDirectories(directoriesToCreate);
+        if (!operation.isDirectory())
+        {
+            IOUtils.createFile(operation.getSource());
+        }
+        return true;
+    }
+
+}
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java
index a56cd2d14c0bb8a574ddb54a7a43eeecebf15605..af2b47e5a2311ff7a36084ec844152c713be66ce 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java
@@ -65,6 +65,7 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler
             case "read":
             case "isSessionValid":
                 return GET; // all parameters from GET methods come on the query string
+            case "create":
             case "write":
             case "move":
             case "copy":
@@ -148,6 +149,7 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler
                         case "transactionId":
                             methodParameters.put(entry.getKey(), UUID.fromString(value));
                             break;
+                        case "directory":
                         case "recursively":
                             methodParameters.put(entry.getKey(), Boolean.valueOf(value));
                             break;
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java
index 562efe112419423c0a6d5fc72161ebb8e2faf19d..fe43032d200502959bc435efe92b6a18ffc74088 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java
@@ -109,6 +109,6 @@ public class ExecutorProxy extends AbstractProxy {
     @Override public @NonNull Boolean create(@NonNull final String owner, @NonNull final String source, @NonNull final Boolean directory)
             throws Exception
     {
-        return workerContext.getConnection().create(source, directory);
+        return workerContext.getConnection().create(getPath(owner, source), directory);
     }
 }
diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java
index db4169e3aadd5d42f3c452d18072b38b1ad97a0f..85068e6a6d1701c3aa5aebd40f09236755d27f19 100644
--- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java
+++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java
@@ -31,6 +31,7 @@ import java.util.UUID;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 public abstract class PublicApiTest extends AbstractTest
@@ -132,6 +133,34 @@ public abstract class PublicApiTest extends AbstractTest
         assertEquals(FILE_B, list.get(0).getName());
     }
 
+    @Test
+    public void create_directory() throws Exception
+    {
+        getPublicAPI().create(owner, FILE_B, Boolean.TRUE);
+
+        final List<File> list = getPublicAPI().list(owner, ROOT, Boolean.TRUE);
+        assertEquals(2, list.size());
+
+        final List<File> matchedFiles = list.stream().filter(file -> file.getName().equals(FILE_B)).toList();
+        assertEquals(1, matchedFiles.size());
+        assertTrue(matchedFiles.get(0).getDirectory());
+    }
+
+    @Test
+    public void create_file() throws Exception
+    {
+        getPublicAPI().create(owner, FILE_B, Boolean.FALSE);
+
+        final List<File> list = getPublicAPI().list(owner, ROOT, Boolean.TRUE);
+
+        final List<File> matchedFiles = list.stream().filter(file -> file.getName().equals(FILE_B)).toList();
+        assertEquals(1, matchedFiles.size());
+        assertFalse(matchedFiles.get(0).getDirectory());
+
+        byte[] bytes = getPublicAPI().read(owner, FILE_B, 0L, 0);
+        assertEquals(0, bytes.length);
+    }
+
 
     @Test
     public void operation_state_begin_succeed() throws Exception {