diff --git a/package.json b/package.json
index e9efa08..6e52cd3 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,7 @@
         "@aws-sdk/client-s3": "^3.511.0",
         "@aws-sdk/s3-request-presigner": "^3.511.0",
         "@jupyterlab/application": "^4.0.0",
+        "@jupyterlab/coreutils": "^6.2.2",
         "@lumino/coreutils": "2.1.2"
     },
     "devDependencies": {
diff --git a/src/icons.ts b/src/icons.ts
index d11af11..eeddd9c 100644
--- a/src/icons.ts
+++ b/src/icons.ts
@@ -3,11 +3,11 @@ import driveSvg from '../style/driveIconFileBrowser.svg';
 import newDriveSvg from '../style/newDriveIcon.svg';
 
 export const DriveIcon = new LabIcon({
-  name: '@jupyter/jupydrive-s3:drive',
+  name: 'jupydrive-s3:drive',
   svgstr: driveSvg
 });
 
 export const NewDriveIcon = new LabIcon({
-  name: '@jupyter/jupydrive-s3:new-drive',
+  name: 'jupydrive-s3:new-drive',
   svgstr: newDriveSvg
 });
diff --git a/src/s3.ts b/src/s3.ts
new file mode 100644
index 0000000..1a79867
--- /dev/null
+++ b/src/s3.ts
@@ -0,0 +1,720 @@
+import { Contents } from '@jupyterlab/services';
+import { PathExt } from '@jupyterlab/coreutils';
+import {
+  CopyObjectCommand,
+  DeleteObjectCommand,
+  ListObjectsV2Command,
+  GetObjectCommand,
+  PutObjectCommand,
+  HeadObjectCommand,
+  HeadObjectCommandOutput,
+  S3Client
+} from '@aws-sdk/client-s3';
+
+import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
+
+export interface IRegisteredFileTypes {
+  [fileExtension: string]: {
+    fileType: string;
+    fileMimeTypes: string[];
+    fileFormat: string;
+  };
+}
+
+interface IContentsList {
+  [fileName: string]: Contents.IModel;
+}
+
+let data: Contents.IModel = {
+  name: '',
+  path: '',
+  last_modified: '',
+  created: '',
+  content: null,
+  format: null,
+  mimetype: '',
+  size: 0,
+  writable: true,
+  type: ''
+};
+
+/**
+ * Get the presigned URL for an S3 object.
+ *
+ * @param s3Client: The S3Client used to send commands.
+ * @param bucketName: The name of bucket.
+ * @param path: The path to the object.
+ *
+ * @returns: A promise which resolves with presigned URL.
+ */
+export const presignedS3Url = async (
+  s3Client: S3Client,
+  bucketName: string,
+  path: string
+): Promise<string> => {
+  // retrieve object from S3 bucket
+  const getCommand = new GetObjectCommand({
+    Bucket: bucketName,
+    Key: path,
+    ResponseContentDisposition: 'attachment',
+    ResponseContentType: 'application/octet-stream'
+  });
+  await s3Client.send(getCommand);
+
+  // get pre-signed URL of S3 file
+  const presignedUrl = await getSignedUrl(s3Client, getCommand);
+  return presignedUrl;
+};
+
+/**
+ * Get list of contents of root or directory.
+ *
+ * @param s3Client: The S3Client used to send commands.
+ * @param bucketName: The bucket name.
+ * @param root: The path to the directory acting as root.
+ * @param registeredFileTypes: The list containing all registered file types.
+ * @param path: The path to the directory (optional).
+ *
+ * @returns: A promise which resolves with the contents model.
+ */
+export const listS3Contents = async (
+  s3Client: S3Client,
+  bucketName: string,
+  root: string,
+  registeredFileTypes: IRegisteredFileTypes,
+  path?: string
+): Promise<Contents.IModel> => {
+  const fileList: IContentsList = {};
+
+  // listing contents of folder
+  const command = new ListObjectsV2Command({
+    Bucket: bucketName,
+    Prefix: path ? PathExt.join(root, path) : root
+  });
+
+  let isTruncated: boolean | undefined = true;
+
+  while (isTruncated) {
+    const { Contents, IsTruncated, NextContinuationToken } =
+      await s3Client.send(command);
+
+    if (Contents) {
+      Contents.forEach(c => {
+        // check if we are dealing with the files inside a subfolder
+        if (
+          c.Key !== root + '/' &&
+          c.Key !== path + '/' &&
+          c.Key !== root + '/' + path + '/'
+        ) {
+          const fileName = c
+            .Key!.replace(
+              (root ? root + '/' : '') + (path ? path + '/' : ''),
+              ''
+            )
+            .split('/')[0];
+          const [fileType, fileMimeType, fileFormat] = Private.getFileType(
+            PathExt.extname(PathExt.basename(fileName)),
+            registeredFileTypes
+          );
+
+          fileList[fileName] = fileList[fileName] ?? {
+            name: fileName,
+            path: path ? PathExt.join(path, fileName) : fileName,
+            last_modified: c.LastModified!.toISOString(),
+            created: '',
+            content: !fileName.split('.')[1] ? [] : null,
+            format: fileFormat as Contents.FileFormat,
+            mimetype: fileMimeType,
+            size: c.Size!,
+            writable: true,
+            type: fileType
+          };
+        }
+      });
+    }
+    if (isTruncated) {
+      isTruncated = IsTruncated;
+    }
+    command.input.ContinuationToken = NextContinuationToken;
+  }
+
+  data = {
+    name: path ? PathExt.basename(path) : bucketName,
+    path: path ? path + '/' : bucketName,
+    last_modified: '',
+    created: '',
+    content: Object.values(fileList),
+    format: 'json',
+    mimetype: '',
+    size: undefined,
+    writable: true,
+    type: 'directory'
+  };
+
+  return data;
+};
+
+/**
+ * Retrieve contents of a file.
+ *
+ * @param s3Client: The S3Client used to send commands.
+ * @param bucketName: The bucket name.
+ * @param root: The path to the directory acting as root.
+ * @param path: The path to to file.
+ * @param registeredFileTypes: The list containing all registered file types.
+ *
+ * @returns: A promise which resolves with the file contents model.
+ */
+export const getS3FileContents = async (
+  s3Client: S3Client,
+  bucketName: string,
+  root: string,
+  path: string,
+  registeredFileTypes: IRegisteredFileTypes
+): Promise<Contents.IModel> => {
+  // retrieving contents and metadata of file
+  const command = new GetObjectCommand({
+    Bucket: bucketName,
+    Key: PathExt.join(root, path)
+  });
+
+  const response = await s3Client.send(command);
+
+  if (response) {
+    const date: string = response.LastModified!.toISOString();
+    const [fileType, fileMimeType, fileFormat] = Private.getFileType(
+      PathExt.extname(PathExt.basename(path)),
+      registeredFileTypes
+    );
+
+    let fileContents: string | Uint8Array;
+
+    // for certain media type files, extract content as byte array and decode to base64 to view in JupyterLab
+    if (fileFormat === 'base64' || fileType === 'PDF') {
+      fileContents = await response.Body!.transformToByteArray();
+      fileContents = btoa(
+        fileContents.reduce(
+          (data, byte) => data + String.fromCharCode(byte),
+          ''
+        )
+      );
+    } else {
+      fileContents = await response.Body!.transformToString();
+    }
+
+    data = {
+      name: PathExt.basename(path),
+      path: PathExt.join(root, path),
+      last_modified: date,
+      created: '',
+      content: fileContents,
+      format: fileFormat as Contents.FileFormat,
+      mimetype: fileMimeType,
+      size: response.ContentLength!,
+      writable: true,
+      type: fileType
+    };
+  }
+
+  return data;
+};
+
+/**
+ * Create a new file or directory or save a file.
+ *
+ * When saving a file, the options parameter is needed.
+ *
+ * @param s3Client: The S3Client used to send commands.
+ * @param bucketName: The bucket name.
+ * @param root: The path to the directory acting as root.
+ * @param name: The name of file or directory to be created or saved.
+ * @param path: The path to to file or directory.
+ * @param body: The new contents of the file.
+ * @param registeredFileTypes: The list containing all registered file types.
+ * @param options: The optional parameteres of saving a file or directory (optional).
+ *
+ * @returns A promise which resolves with the new file or directory contents model.
+ */
+export const createS3Object = async (
+  s3Client: S3Client,
+  bucketName: string,
+  root: string,
+  name: string,
+  path: string,
+  body: string | Blob,
+  registeredFileTypes: IRegisteredFileTypes,
+  options?: Partial<Contents.IModel>
+): Promise<Contents.IModel> => {
+  path = PathExt.join(root, path);
+
+  const [fileType, fileMimeType, fileFormat] = Private.getFileType(
+    PathExt.extname(PathExt.basename(name)),
+    registeredFileTypes
+  );
+
+  // checking if we are creating a new file or saving an existing one (overwrriting)
+  if (options) {
+    body = Private.formatBody(options, fileFormat, fileType, fileMimeType);
+  }
+
+  await s3Client.send(
+    new PutObjectCommand({
+      Bucket: bucketName,
+      Key: path + (PathExt.extname(name) === '' ? '/' : ''),
+      Body: body,
+      CacheControl: options ? 'no-cache' : undefined
+    })
+  );
+
+  data = {
+    name: name,
+    path: PathExt.join(path, name),
+    last_modified: new Date().toISOString(),
+    created: new Date().toISOString(),
+    content: path.split('.').length === 1 ? [] : body,
+    format: fileFormat as Contents.FileFormat,
+    mimetype: fileMimeType,
+    size: typeof body === 'string' ? body.length : body.size,
+    writable: true,
+    type: fileType
+  };
+
+  return data;
+};
+
+/**
+ * Deleting a file or directory.
+ *
+ * @param s3Client: The S3Client used to send commands.
+ * @param bucketName: The bucket name.
+ * @param root: The path to the directory acting as root.
+ * @param path: The path to to file or directory.
+ */
+export const deleteS3Objects = async (
+  s3Client: S3Client,
+  bucketName: string,
+  root: string,
+  path: string
+): Promise<void> => {
+  path = PathExt.join(root, path);
+
+  // get list of contents with given prefix (path)
+  const command = new ListObjectsV2Command({
+    Bucket: bucketName,
+    Prefix: PathExt.extname(path) === '' ? path + '/' : path
+  });
+
+  let isTruncated: boolean | undefined = true;
+
+  while (isTruncated) {
+    const { Contents, IsTruncated, NextContinuationToken } =
+      await s3Client.send(command);
+
+    if (Contents) {
+      await Promise.all(
+        Contents.map(c => {
+          // delete each file with given path
+          Private.deleteFile(s3Client, bucketName, c.Key!);
+        })
+      );
+    }
+    if (isTruncated) {
+      isTruncated = IsTruncated;
+    }
+    command.input.ContinuationToken = NextContinuationToken;
+  }
+};
+
+/**
+ * Check whether an object (file or directory) exists within given S3 bucket.
+ *
+ * Used before renaming a file to avoid overwriting and when setting the root.
+ *
+ * @param s3Client: The S3Client used to send commands.
+ * @param bucketName: The bucket name.
+ * @param root: The path to the directory acting as root.
+ * @param path: The path to to file or directory.
+ *
+ * @returns A promise which resolves or rejects depending on the existance of the object.
+ */
+export const checkS3Object = async (
+  s3Client: S3Client,
+  bucketName: string,
+  root: string,
+  path?: string
+): Promise<HeadObjectCommandOutput> => {
+  return await s3Client.send(
+    new HeadObjectCommand({
+      Bucket: bucketName,
+      Key: path ? PathExt.join(root, path) : root + '/' // check whether we are looking at an object or the root
+    })
+  );
+};
+
+/**
+ * Rename a file or directory.
+ *
+ * @param s3Client: The S3Client used to send commands.
+ * @param bucketName: The bucket name.
+ * @param root: The path to the directory acting as root.
+ * @param oldLocalPath: The old path of the object.
+ * @param newLocalPath: The new path of the object.
+ * @param newFileName: The new object name.
+ * @param registeredFileTypes: The list containing all registered file types.
+ *
+ * @returns A promise which resolves with the new object contents model.
+ */
+export const renameS3Objects = async (
+  s3Client: S3Client,
+  bucketName: string,
+  root: string,
+  oldLocalPath: string,
+  newLocalPath: string,
+  newFileName: string,
+  registeredFileTypes: IRegisteredFileTypes
+): Promise<Contents.IModel> => {
+  newLocalPath = PathExt.join(root, newLocalPath);
+  oldLocalPath = PathExt.join(root, oldLocalPath);
+
+  const isDir: boolean = PathExt.extname(oldLocalPath) === '';
+
+  if (isDir) {
+    newLocalPath = newLocalPath.substring(0, newLocalPath.length - 1);
+  }
+  newLocalPath =
+    newLocalPath.substring(0, newLocalPath.lastIndexOf('/') + 1) + newFileName;
+
+  const [fileType, fileMimeType, fileFormat] = Private.getFileType(
+    PathExt.extname(PathExt.basename(newFileName)),
+    registeredFileTypes
+  );
+
+  // list contents of path - contents of directory or one file
+  const command = new ListObjectsV2Command({
+    Bucket: bucketName,
+    Prefix: oldLocalPath
+  });
+
+  let isTruncated: boolean | undefined = true;
+
+  while (isTruncated) {
+    const { Contents, IsTruncated, NextContinuationToken } =
+      await s3Client.send(command);
+
+    if (Contents) {
+      // retrieve information of file or directory
+      const fileContents = await s3Client.send(
+        new GetObjectCommand({
+          Bucket: bucketName,
+          Key: Contents[0].Key!
+        })
+      );
+
+      const body = await fileContents.Body?.transformToString();
+
+      data = {
+        name: newFileName,
+        path: newLocalPath,
+        last_modified: fileContents.LastModified!.toISOString(),
+        created: '',
+        content: body ? body : [],
+        format: fileFormat as Contents.FileFormat,
+        mimetype: fileMimeType,
+        size: fileContents.ContentLength!,
+        writable: true,
+        type: fileType
+      };
+
+      const promises = Contents.map(async c => {
+        const remainingFilePath = c.Key!.substring(oldLocalPath.length);
+        // wait for copy action to resolve, delete original file only if it succeeds
+        await Private.copyFile(
+          s3Client,
+          bucketName,
+          remainingFilePath,
+          oldLocalPath,
+          newLocalPath
+        );
+        return Private.deleteFile(
+          s3Client,
+          bucketName,
+          oldLocalPath + remainingFilePath
+        );
+      });
+      await Promise.all(promises);
+    }
+    if (isTruncated) {
+      isTruncated = IsTruncated;
+    }
+    command.input.ContinuationToken = NextContinuationToken;
+  }
+
+  return data;
+};
+
+/**
+ * Copy a file or directory to a new location within the bucket or to another bucket.
+ *
+ * If no additional bucket name is provided, the content will be copied to the default bucket.
+ *
+ * @param s3Client: The S3Client used to send commands.
+ * @param bucketName: The bucket name.
+ * @param root: The path to the directory acting as root.
+ * @param name: The new object name.
+ * @param path: The original path to the object to be copied.
+ * @param toDir: The new path where object should be copied.
+ * @param registeredFileTypes: The list containing all registered file types.
+ * @param newBucketName: The name of the bucket where to copy the object (optional).
+ *
+ * @returns A promise which resolves with the new object contents model.
+ */
+export const copyS3Objects = async (
+  s3Client: S3Client,
+  bucketName: string,
+  root: string,
+  name: string,
+  path: string,
+  toDir: string,
+  registeredFileTypes: IRegisteredFileTypes,
+  newBucketName?: string
+): Promise<Contents.IModel> => {
+  const isDir: boolean = PathExt.extname(path) === '';
+
+  path = PathExt.join(root, path);
+  toDir = PathExt.join(root, toDir);
+
+  name = PathExt.join(toDir, name);
+  path = isDir ? path + '/' : path;
+
+  // list contents of path - contents of directory or one file
+  const command = new ListObjectsV2Command({
+    Bucket: bucketName,
+    Prefix: path
+  });
+
+  let isTruncated: boolean | undefined = true;
+
+  while (isTruncated) {
+    const { Contents, IsTruncated, NextContinuationToken } =
+      await s3Client.send(command);
+
+    if (Contents) {
+      const promises = Contents.map(c => {
+        const remainingFilePath = c.Key!.substring(path.length);
+        // copy each file from old directory to new location
+        return Private.copyFile(
+          s3Client,
+          bucketName,
+          remainingFilePath,
+          path,
+          name,
+          newBucketName
+        );
+      });
+      await Promise.all(promises);
+    }
+    if (isTruncated) {
+      isTruncated = IsTruncated;
+    }
+    command.input.ContinuationToken = NextContinuationToken;
+  }
+
+  const [fileType, fileMimeType, fileFormat] = Private.getFileType(
+    PathExt.extname(PathExt.basename(name)),
+    registeredFileTypes
+  );
+
+  // retrieve information of new file
+  const newFileContents = await s3Client.send(
+    new GetObjectCommand({
+      Bucket: newBucketName ?? bucketName,
+      Key: name
+    })
+  );
+
+  data = {
+    name: PathExt.basename(name),
+    path: name,
+    last_modified: newFileContents.LastModified!.toISOString(),
+    created: new Date().toISOString(),
+    content: await newFileContents.Body!.transformToString(),
+    format: fileFormat as Contents.FileFormat,
+    mimetype: fileMimeType,
+    size: newFileContents.ContentLength!,
+    writable: true,
+    type: fileType
+  };
+
+  return data;
+};
+
+/**
+ * Count number of appeareances of object name.
+ *
+ * @param s3Client: The S3Client used to send commands.
+ * @param bucketName: The bucket name.
+ * @param root: The path to the directory acting as root.
+ * @param path: The path to the object.
+ * @param originalName: The original name of the object (before it was incremented).
+ *
+ * @returns A promise which resolves with the number of appeareances of object.
+ */
+export const countS3ObjectNameAppearances = async (
+  s3Client: S3Client,
+  bucketName: string,
+  root: string,
+  path: string,
+  originalName: string
+): Promise<number> => {
+  let counter: number = 0;
+  path = PathExt.join(root, path);
+
+  // count number of name appearances
+  const command = new ListObjectsV2Command({
+    Bucket: bucketName,
+    Prefix: path.substring(0, path.lastIndexOf('/'))
+  });
+
+  let isTruncated: boolean | undefined = true;
+
+  while (isTruncated) {
+    const { Contents, IsTruncated, NextContinuationToken } =
+      await s3Client.send(command);
+
+    if (Contents) {
+      Contents.forEach(c => {
+        const fileName = c
+          .Key!.replace((root ? root + '/' : '') + (path ? path + '/' : ''), '')
+          .split('/')[0];
+        if (
+          fileName.substring(0, originalName.length + 1).includes(originalName)
+        ) {
+          counter += 1;
+        }
+      });
+    }
+    if (isTruncated) {
+      isTruncated = IsTruncated;
+    }
+    command.input.ContinuationToken = NextContinuationToken;
+  }
+
+  return counter;
+};
+
+namespace Private {
+  /**
+   * Helping function to define file type, mimetype and format based on file extension.
+   * @param extension file extension (e.g.: txt, ipynb, csv)
+   * @returns
+   */
+  export function getFileType(
+    extension: string,
+    registeredFileTypes: IRegisteredFileTypes
+  ) {
+    let fileType: string = 'text';
+    let fileMimetype: string = 'text/plain';
+    let fileFormat: string = 'text';
+
+    if (registeredFileTypes[extension]) {
+      fileType = registeredFileTypes[extension].fileType;
+      fileMimetype = registeredFileTypes[extension].fileMimeTypes[0];
+      fileFormat = registeredFileTypes[extension].fileFormat;
+    }
+
+    // the file format for notebooks appears as json, but should be text
+    if (extension === '.ipynb') {
+      fileFormat = 'text';
+    }
+
+    return [fileType, fileMimetype, fileFormat];
+  }
+
+  /**
+   * Helping function for deleting files inside
+   * a directory, in the case of deleting the directory.
+   *
+   * @param filePath complete path of file to delete
+   */
+  export async function deleteFile(
+    s3Client: S3Client,
+    bucketName: string,
+    filePath: string
+  ) {
+    await s3Client.send(
+      new DeleteObjectCommand({
+        Bucket: bucketName,
+        Key: filePath
+      })
+    );
+  }
+
+  /**
+   * Helping function for copying the files inside a directory
+   * to a new location, in the case of renaming or copying a directory.
+   *
+   * @param remainingFilePath remaining path of file to be copied
+   * @param oldPath old path of file
+   * @param newPath new path of file
+   */
+  export async function copyFile(
+    s3Client: S3Client,
+    bucketName: string,
+    remainingFilePath: string,
+    oldPath: string,
+    newPath: string,
+    newBucketName?: string
+  ) {
+    await s3Client.send(
+      new CopyObjectCommand({
+        Bucket: newBucketName ? newBucketName : bucketName,
+        CopySource: PathExt.join(bucketName, oldPath, remainingFilePath),
+        Key: PathExt.join(newPath, remainingFilePath)
+      })
+    );
+  }
+
+  /**
+   * Helping function used for formatting the body of files.
+   *
+   * @param options: The parameteres for saving a file.
+   * @param fileFormat: The registered file format.
+   * @param fileType: The registered file type.
+   * @param fileMimeType: The registered file mimetype.
+   *
+   * @returns The formatted content (body).
+   */
+  export function formatBody(
+    options: Partial<Contents.IModel>,
+    fileFormat: string,
+    fileType: string,
+    fileMimeType: string
+  ) {
+    let body: string | Blob;
+    if (options.format === 'json') {
+      body = JSON.stringify(options.content, null, 2);
+    } else if (
+      options.format === 'base64' &&
+      (fileFormat === 'base64' || fileType === 'PDF')
+    ) {
+      // transform base64 encoding to a utf-8 array for saving and storing in S3 bucket
+      const byteCharacters = atob(options.content);
+      const byteArrays = [];
+
+      for (let offset = 0; offset < byteCharacters.length; offset += 512) {
+        const slice = byteCharacters.slice(offset, offset + 512);
+        const byteNumbers = new Array(slice.length);
+        for (let i = 0; i < slice.length; i++) {
+          byteNumbers[i] = slice.charCodeAt(i);
+        }
+        const byteArray = new Uint8Array(byteNumbers);
+        byteArrays.push(byteArray);
+      }
+
+      body = new Blob(byteArrays, { type: fileMimeType });
+    } else {
+      body = options.content;
+    }
+    return body;
+  }
+}
diff --git a/src/s3contents.ts b/src/s3contents.ts
index f985bf4..b7d902e 100644
--- a/src/s3contents.ts
+++ b/src/s3contents.ts
@@ -1,21 +1,26 @@
 import { Signal, ISignal } from '@lumino/signaling';
 import { Contents, ServerConnection } from '@jupyterlab/services';
-import { URLExt } from '@jupyterlab/coreutils';
+import { URLExt, PathExt } from '@jupyterlab/coreutils';
 import { JupyterFrontEnd } from '@jupyterlab/application';
 
 import {
-  CopyObjectCommand,
-  DeleteObjectCommand,
   S3Client,
-  ListObjectsV2Command,
   GetBucketLocationCommand,
-  GetObjectCommand,
-  PutObjectCommand,
-  HeadObjectCommand,
   S3ClientConfig
 } from '@aws-sdk/client-s3';
 
-import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
+import {
+  checkS3Object,
+  createS3Object,
+  copyS3Objects,
+  countS3ObjectNameAppearances,
+  deleteS3Objects,
+  presignedS3Url,
+  renameS3Objects,
+  listS3Contents,
+  IRegisteredFileTypes,
+  getS3FileContents
+} from './s3';
 
 let data: Contents.IModel = {
   name: '',
@@ -30,18 +35,6 @@ let data: Contents.IModel = {
   type: ''
 };
 
-export interface IRegisteredFileTypes {
-  [fileExtension: string]: {
-    fileType: string;
-    fileMimeTypes: string[];
-    fileFormat: string;
-  };
-}
-
-interface IContentsList {
-  [fileName: string]: Contents.IModel;
-}
-
 export class Drive implements Contents.IDrive {
   /**
    * Construct a new drive object.
@@ -235,18 +228,8 @@ export class Drive implements Contents.IDrive {
    * path if necessary.
    */
   async getDownloadUrl(path: string): Promise<string> {
-    const getCommand = new GetObjectCommand({
-      Bucket: this._name,
-      Key: path,
-      ResponseContentDisposition: 'attachment',
-      ResponseContentType: 'application/octet-stream'
-    });
-
-    await this._s3Client.send(getCommand);
-
-    // get pre-signed URL of S3 file
-    const signedUrl = await getSignedUrl(this._s3Client, getCommand);
-    return signedUrl;
+    const url = await presignedS3Url(this._s3Client, this._name, path);
+    return url;
   }
 
   /**
@@ -266,179 +249,36 @@ export class Drive implements Contents.IDrive {
   ): Promise<Contents.IModel> {
     path = path.replace(this._name + '/', '');
 
-    // check if we are getting the list of files from the drive
+    // getting the list of files from the root
     if (!path) {
-      const fileList: IContentsList = {};
-
-      const command = new ListObjectsV2Command({
-        Bucket: this._name,
-        Prefix: this._root
-      });
-
-      let isTruncated: boolean | undefined = true;
-
-      while (isTruncated) {
-        const { Contents, IsTruncated, NextContinuationToken } =
-          await this._s3Client.send(command);
-
-        if (Contents) {
-          Contents.forEach(c => {
-            if (c.Key! !== this._root + '/') {
-              const fileName = (
-                this._root === ''
-                  ? c.Key!
-                  : c.Key!.replace(this._root + '/', '')
-              ).split('/')[0];
-              const [fileType, fileMimeType, fileFormat] = this.getFileType(
-                fileName.split('.')[1]
-              );
-
-              fileList[fileName] = fileList[fileName] ?? {
-                name: fileName,
-                path: fileName,
-                last_modified: c.LastModified!.toISOString(),
-                created: '',
-                content: !fileName.split('.')[1] ? [] : null,
-                format: fileFormat as Contents.FileFormat,
-                mimetype: fileMimeType,
-                size: c.Size!,
-                writable: true,
-                type: fileType
-              };
-            }
-          });
-        }
-        if (isTruncated) {
-          isTruncated = IsTruncated;
-        }
-        command.input.ContinuationToken = NextContinuationToken;
-      }
-
-      data = {
-        name: this._name,
-        path: this._name,
-        last_modified: '',
-        created: '',
-        content: Object.values(fileList),
-        format: 'json',
-        mimetype: '',
-        size: undefined,
-        writable: true,
-        type: 'directory'
-      };
+      data = await listS3Contents(
+        this._s3Client,
+        this._name,
+        this._root,
+        this._registeredFileTypes
+      );
     } else {
-      const splitPath = path.split('/');
-      const currentPath = splitPath[splitPath.length - 1];
+      const currentPath = PathExt.basename(path);
 
       // listing contents of a folder
-      if (currentPath.indexOf('.') === -1) {
-        const fileList: IContentsList = {};
-
-        const command = new ListObjectsV2Command({
-          Bucket: this._name,
-          Prefix: (this._root ? this._root + '/' : '') + path + '/'
-        });
-
-        let isTruncated: boolean | undefined = true;
-
-        while (isTruncated) {
-          const { Contents, IsTruncated, NextContinuationToken } =
-            await this._s3Client.send(command);
-
-          if (Contents) {
-            Contents.forEach(c => {
-              // checking if we are dealing with the file inside a folder
-              if (
-                c.Key !== path + '/' &&
-                c.Key !== this._root + '/' + path + '/'
-              ) {
-                const fileName = c
-                  .Key!.replace(
-                    (this.root ? this.root + '/' : '') + path + '/',
-                    ''
-                  )
-                  .split('/')[0];
-                const [fileType, fileMimeType, fileFormat] = this.getFileType(
-                  fileName.split('.')[1]
-                );
-
-                fileList[fileName] = fileList[fileName] ?? {
-                  name: fileName,
-                  path: path + '/' + fileName,
-                  last_modified: c.LastModified!.toISOString(),
-                  created: '',
-                  content: !fileName.split('.')[1] ? [] : null,
-                  format: fileFormat as Contents.FileFormat,
-                  mimetype: fileMimeType,
-                  size: c.Size!,
-                  writable: true,
-                  type: fileType
-                };
-              }
-            });
-          }
-          if (isTruncated) {
-            isTruncated = IsTruncated;
-          }
-          command.input.ContinuationToken = NextContinuationToken;
-        }
-
-        data = {
-          name: currentPath,
-          path: path + '/',
-          last_modified: '',
-          created: '',
-          content: Object.values(fileList),
-          format: 'json',
-          mimetype: '',
-          size: undefined,
-          writable: true,
-          type: 'directory'
-        };
+      if (PathExt.extname(currentPath) === '') {
+        data = await listS3Contents(
+          this._s3Client,
+          this._name,
+          this.root,
+          this.registeredFileTypes,
+          path
+        );
       }
       // getting the contents of a specific file
       else {
-        const command = new GetObjectCommand({
-          Bucket: this._name,
-          Key: this._root ? this._root + '/' + path : path
-        });
-
-        const response = await this._s3Client.send(command);
-
-        if (response) {
-          const date: string = response.LastModified!.toISOString();
-          const [fileType, fileMimeType, fileFormat] = this.getFileType(
-            currentPath.split('.')[1]
-          );
-
-          let fileContents: string | Uint8Array;
-
-          // for certain media type files, extract content as byte array and decode to base64 to view in JupyterLab
-          if (fileFormat === 'base64' || fileType === 'PDF') {
-            fileContents = await response.Body!.transformToByteArray();
-            fileContents = btoa(
-              fileContents.reduce(
-                (data, byte) => data + String.fromCharCode(byte),
-                ''
-              )
-            );
-          } else {
-            fileContents = await response.Body!.transformToString();
-          }
-
-          data = {
-            name: currentPath,
-            path: this._root ? this._root + '/' + path : path,
-            last_modified: date,
-            created: '',
-            content: fileContents,
-            format: fileFormat as Contents.FileFormat,
-            mimetype: fileMimeType,
-            size: response.ContentLength!,
-            writable: true,
-            type: fileType
-          };
-        }
+        data = await getS3FileContents(
+          this._s3Client,
+          this._name,
+          this._root,
+          path,
+          this.registeredFileTypes
+        );
       }
     }
 
@@ -457,112 +297,26 @@ export class Drive implements Contents.IDrive {
   async newUntitled(
     options: Contents.ICreateOptions = {}
   ): Promise<Contents.IModel> {
-    const body = '';
-    let { path } = options;
-    const { type, ext } = options;
-    path = this._root ? (path ? this._root + '/' + path : this._root) : path;
-
     // get current list of contents of drive
-    const content: Contents.IModel[] = [];
-
-    const command = new ListObjectsV2Command({
-      Bucket: this._name
-    });
-
-    let isTruncated: boolean | undefined = true;
-
-    while (isTruncated) {
-      const { Contents, IsTruncated, NextContinuationToken } =
-        await this._s3Client.send(command);
-
-      if (Contents) {
-        Contents.forEach(c => {
-          const [fileType, fileMimeType, fileFormat] = this.getFileType(
-            c.Key!.split('.')[1]
-          );
-
-          content.push({
-            name: c.Key!,
-            path: URLExt.join(this._name, c.Key!),
-            last_modified: c.LastModified!.toISOString(),
-            created: '',
-            content: !c.Key!.split('.')[1] ? [] : null,
-            format: fileFormat as Contents.FileFormat,
-            mimetype: fileMimeType,
-            size: c.Size!,
-            writable: true,
-            type: fileType
-          });
-        });
-      }
-      if (isTruncated) {
-        isTruncated = IsTruncated;
-      }
-      command.input.ContinuationToken = NextContinuationToken;
-    }
-
-    const old_data: Contents.IModel = {
-      name: this._name,
-      path: '',
-      last_modified: '',
-      created: '',
-      content: content,
-      format: 'json',
-      mimetype: '',
-      size: undefined,
-      writable: true,
-      type: 'directory'
-    };
-
-    if (type !== undefined) {
-      if (type !== 'directory') {
-        const name = this.incrementUntitledName(old_data, { path, type, ext });
-        await this.s3Client.send(
-          new PutObjectCommand({
-            Bucket: this._name,
-            Key: path ? path + '/' + name : name,
-            Body: body
-          })
-        );
-
-        const [fileType, fileMimeType, fileFormat] = this.getFileType(ext!);
-
-        data = {
-          name: name,
-          path: path + '/' + name,
-          last_modified: new Date().toISOString(),
-          created: new Date().toISOString(),
-          content: body,
-          format: fileFormat as Contents.FileFormat,
-          mimetype: fileMimeType,
-          size: body.length,
-          writable: true,
-          type: fileType
-        };
-      } else {
-        // creating a new directory
-        const name = this.incrementUntitledName(old_data, { path, type, ext });
-        await this.s3Client.send(
-          new PutObjectCommand({
-            Bucket: this._name,
-            Key: path ? path + '/' + name + '/' : name + '/',
-            Body: body
-          })
-        );
+    const old_data = await listS3Contents(
+      this._s3Client,
+      this._name,
+      '', // we consider the root undefined in order to retrieve the complete list of contents
+      this.registeredFileTypes
+    );
 
-        data = {
-          name: name,
-          path: path + '/' + name,
-          last_modified: new Date().toISOString(),
-          created: new Date().toISOString(),
-          content: [],
-          format: 'json',
-          mimetype: 'text/directory',
-          size: undefined,
-          writable: true,
-          type: type
-        };
-      }
+    if (options.type !== undefined) {
+      // get incremented untitled name
+      const name = this.incrementUntitledName(old_data, options);
+      data = await createS3Object(
+        this._s3Client,
+        this._name,
+        this._root,
+        name,
+        options.path ? PathExt.join(options.path, name) : name,
+        '', // create new file with empty body,
+        this.registeredFileTypes
+      );
     } else {
       console.warn('Type of new element is undefined');
     }
@@ -636,36 +390,7 @@ export class Drive implements Contents.IDrive {
    * @returns A promise which resolves when the file is deleted.
    */
   async delete(localPath: string): Promise<void> {
-    localPath = this._root
-      ? localPath
-        ? this._root + '/' + localPath
-        : this._root
-      : localPath;
-    // get list of contents with given prefix (path)
-    const command = new ListObjectsV2Command({
-      Bucket: this._name,
-      Prefix: localPath.split('.').length === 1 ? localPath + '/' : localPath
-    });
-
-    let isTruncated: boolean | undefined = true;
-
-    while (isTruncated) {
-      const { Contents, IsTruncated, NextContinuationToken } =
-        await this._s3Client.send(command);
-
-      if (Contents) {
-        await Promise.all(
-          Contents.map(c => {
-            // delete each file with given path
-            this.delete_file(c.Key!);
-          })
-        );
-      }
-      if (isTruncated) {
-        isTruncated = IsTruncated;
-      }
-      command.input.ContinuationToken = NextContinuationToken;
-    }
+    await deleteS3Objects(this._s3Client, this._name, this._root, localPath);
 
     this._fileChanged.emit({
       type: 'delete',
@@ -692,98 +417,30 @@ export class Drive implements Contents.IDrive {
     newLocalPath: string,
     options: Contents.ICreateOptions = {}
   ): Promise<Contents.IModel> {
-    let newFileName =
-      newLocalPath.indexOf('/') >= 0
-        ? newLocalPath.split('/')[newLocalPath.split('/').length - 1]
-        : newLocalPath;
-    newLocalPath = this._root ? this._root + '/' + newLocalPath : newLocalPath;
-    oldLocalPath = this._root ? this._root + '/' + oldLocalPath : oldLocalPath;
-
-    const [fileType, fileMimeType, fileFormat] = this.getFileType(
-      newFileName!.split('.')[1]
-    );
+    let newFileName = PathExt.basename(newLocalPath);
 
-    const isDir: boolean = oldLocalPath.split('.').length === 1;
-
-    // check if file with new name already exists
-    try {
-      await this._s3Client.send(
-        new HeadObjectCommand({
-          Bucket: this._name,
-          Key: newLocalPath
-        })
-      );
-      console.log('File name already exists!');
-      // construct new incremented name and it's corresponding path
-      newFileName = await this.incrementName(newLocalPath, isDir, this._name);
-      if (isDir) {
-        newLocalPath = newLocalPath.substring(0, newLocalPath.length - 1);
-      }
-      newLocalPath = isDir
-        ? newLocalPath.substring(0, newLocalPath.lastIndexOf('/') + 1) +
-          newFileName +
-          '/'
-        : newLocalPath.substring(0, newLocalPath.lastIndexOf('/') + 1) +
-          newFileName;
-    } catch (e) {
-      // function throws error as the file name doesn't exist
-      console.log("Name doesn't exist!");
-    }
-
-    // list contents of path - contents of directory or one file
-    const command = new ListObjectsV2Command({
-      Bucket: this._name,
-      Prefix: oldLocalPath
-    });
-
-    let isTruncated: boolean | undefined = true;
-
-    while (isTruncated) {
-      const { Contents, IsTruncated, NextContinuationToken } =
-        await this._s3Client.send(command);
-
-      if (Contents) {
-        // retrieve information of file or directory
-        const fileContents = await this._s3Client.send(
-          new GetObjectCommand({
-            Bucket: this._name,
-            Key: Contents[0].Key!
-          })
+    await checkS3Object(this._s3Client, this._name, this._root, newLocalPath)
+      .then(async () => {
+        console.log('File name already exists!');
+        // construct new incremented name
+        newFileName = await this.incrementName(newLocalPath, this._name);
+      })
+      .catch(() => {
+        // function throws error as the file name doesn't exist
+        console.log("Name doesn't exist!");
+      })
+      .finally(async () => {
+        // once the name has been incremented if needed, proceed with the renaming
+        data = await renameS3Objects(
+          this._s3Client,
+          this._name,
+          this._root,
+          oldLocalPath,
+          newLocalPath,
+          newFileName,
+          this._registeredFileTypes
         );
-
-        const body = await fileContents.Body?.transformToString();
-
-        data = {
-          name: newFileName,
-          path: newLocalPath,
-          last_modified: fileContents.LastModified!.toISOString(),
-          created: '',
-          content: body ? body : [],
-          format: fileFormat as Contents.FileFormat,
-          mimetype: fileMimeType,
-          size: fileContents.ContentLength!,
-          writable: true,
-          type: fileType
-        };
-
-        const promises = Contents.map(async c => {
-          const remainingFilePath = c.Key!.substring(oldLocalPath.length);
-          // wait for copy action to resolve, delete original file only if it succeeds
-          await this.copy_file(
-            remainingFilePath,
-            oldLocalPath,
-            newLocalPath,
-            this._name
-          );
-          return this.delete_file(oldLocalPath + remainingFilePath);
-        });
-        await Promise.all(promises);
-      }
-      if (isTruncated) {
-        isTruncated = IsTruncated;
-      }
-      command.input.ContinuationToken = NextContinuationToken;
-    }
+      });
 
     this._fileChanged.emit({
       type: 'rename',
@@ -799,70 +456,39 @@ export class Drive implements Contents.IDrive {
    *
    * @param localPath - Path to file.
    *
-   * @param isDir - Whether the content is a directory or a file.
-   *
    * @param bucketName - The name of the bucket where content is moved.
+   *
+   * @param root - The root of the bucket, if it exists.
    */
-  async incrementName(localPath: string, isDir: boolean, bucketName: string) {
-    let counter: number = 0;
+  async incrementName(localPath: string, bucketName: string) {
+    const isDir: boolean = PathExt.extname(localPath) === '';
     let fileExtension: string = '';
     let originalName: string = '';
 
     // check if we are dealing with a directory
     if (isDir) {
       localPath = localPath.substring(0, localPath.length - 1);
-      originalName = localPath.split('/')[localPath.split('/').length - 1];
+      originalName = PathExt.basename(localPath);
     }
     // dealing with a file
     else {
       // extract name from path
-      originalName = localPath.split('/')[localPath.split('/').length - 1];
+      originalName = PathExt.basename(localPath);
       // eliminate file extension
-      fileExtension =
-        originalName.split('.')[originalName.split('.').length - 1];
+      fileExtension = PathExt.extname(originalName);
       originalName =
         originalName.split('.')[originalName.split('.').length - 2];
     }
 
-    // count number of name appearances
-    const command = new ListObjectsV2Command({
-      Bucket: bucketName,
-      Prefix: localPath.substring(0, localPath.lastIndexOf('/'))
-    });
-
-    let isTruncated: boolean | undefined = true;
-
-    while (isTruncated) {
-      const { Contents, IsTruncated, NextContinuationToken } =
-        await this._s3Client.send(command);
-
-      if (Contents) {
-        Contents.forEach(c => {
-          // check if we are dealing with a directory
-          if (c.Key![c.Key!.length - 1] === '/') {
-            c.Key! = c.Key!.substring(0, c.Key!.length - 1);
-          }
-          // check if the name of the file or directory matches the original name
-          if (
-            c
-              .Key!.substring(
-                c.Key!.lastIndexOf('/') + 1,
-                c.Key!.lastIndexOf('/') + 1 + originalName.length
-              )
-              .includes(originalName)
-          ) {
-            counter += 1;
-          }
-        });
-      }
-      if (isTruncated) {
-        isTruncated = IsTruncated;
-      }
-      command.input.ContinuationToken = NextContinuationToken;
-    }
-
+    const counter = await countS3ObjectNameAppearances(
+      this._s3Client,
+      bucketName,
+      this._root,
+      localPath,
+      originalName
+    );
     let newName = counter ? originalName + counter : originalName;
-    newName = isDir ? newName + '/' : newName + '.' + fileExtension;
+    newName = isDir ? newName + '/' : newName + fileExtension;
 
     return newName;
   }
@@ -886,65 +512,19 @@ export class Drive implements Contents.IDrive {
     localPath: string,
     options: Partial<Contents.IModel> = {}
   ): Promise<Contents.IModel> {
-    const fileName =
-      localPath.indexOf('/') === -1
-        ? localPath
-        : localPath.split('/')[localPath.split.length - 1];
-    localPath = this._root ? this._root + '/' + localPath : localPath;
-
-    const [fileType, fileMimeType, fileFormat] = this.getFileType(
-      fileName.split('.')[1]
+    const fileName = PathExt.basename(localPath);
+
+    data = await createS3Object(
+      this._s3Client,
+      this._name,
+      this._root,
+      fileName,
+      localPath,
+      options.content,
+      this._registeredFileTypes,
+      options
     );
 
-    let body: string | Blob;
-    if (options.format === 'json') {
-      body = JSON.stringify(options?.content, null, 2);
-    } else if (
-      options.format === 'base64' &&
-      (fileFormat === 'base64' || fileType === 'PDF')
-    ) {
-      // transform base64 encoding to a utf-8 array for saving and storing in S3 bucket
-      const byteCharacters = atob(options.content);
-      const byteArrays = [];
-
-      for (let offset = 0; offset < byteCharacters.length; offset += 512) {
-        const slice = byteCharacters.slice(offset, offset + 512);
-        const byteNumbers = new Array(slice.length);
-        for (let i = 0; i < slice.length; i++) {
-          byteNumbers[i] = slice.charCodeAt(i);
-        }
-        const byteArray = new Uint8Array(byteNumbers);
-        byteArrays.push(byteArray);
-      }
-
-      body = new Blob(byteArrays, { type: fileMimeType });
-    } else {
-      body = options?.content;
-    }
-
-    // save file with new content by overwritting existing file
-    await this._s3Client.send(
-      new PutObjectCommand({
-        Bucket: this._name,
-        Key: localPath,
-        Body: body,
-        CacheControl: 'no-cache'
-      })
-    );
-
-    data = {
-      name: fileName,
-      path: localPath,
-      last_modified: new Date().toISOString(),
-      created: '',
-      content: body,
-      format: fileFormat as Contents.FileFormat,
-      mimetype: fileMimeType,
-      size: typeof body === 'string' ? body.length : body.size,
-      writable: true,
-      type: fileType
-    };
-
     this._fileChanged.emit({
       type: 'save',
       oldValue: null,
@@ -959,21 +539,16 @@ export class Drive implements Contents.IDrive {
    *
    * @param path - The original file path.
    *
-   * @param isDir - The boolean marking if we are dealing with a file or directory.
-   *
    * @param bucketName - The name of the bucket where content is moved.
    *
    * @returns A promise which resolves with the new name when the
    *  file is copied.
    */
-  async incrementCopyName(
-    copiedItemPath: string,
-    isDir: boolean,
-    bucketName: string
-  ) {
+  async incrementCopyName(copiedItemPath: string, bucketName: string) {
+    const isDir: boolean = PathExt.extname(copiedItemPath) === '';
+
     // extracting original file name
-    const originalFileName =
-      copiedItemPath.split('/')[copiedItemPath.split('/').length - 1];
+    const originalFileName = PathExt.basename(copiedItemPath);
 
     // constructing new file name and path with -Copy string
     const newFileName = isDir
@@ -982,19 +557,13 @@ export class Drive implements Contents.IDrive {
         '-Copy.' +
         originalFileName.split('.')[1];
 
-    const newFilePath = isDir
-      ? copiedItemPath.substring(0, copiedItemPath.lastIndexOf('/') + 1) +
-        newFileName +
-        '/'
-      : copiedItemPath.substring(0, copiedItemPath.lastIndexOf('/') + 1) +
-        newFileName;
+    const newFilePath =
+      copiedItemPath.substring(0, copiedItemPath.lastIndexOf('/') + 1) +
+      newFileName +
+      (isDir ? '/' : '');
 
     // getting incremented name of Copy in case of duplicates
-    const incrementedName = await this.incrementName(
-      newFilePath,
-      isDir,
-      bucketName
-    );
+    const incrementedName = await this.incrementName(newFilePath, bucketName);
 
     return incrementedName;
   }
@@ -1014,72 +583,19 @@ export class Drive implements Contents.IDrive {
     toDir: string,
     options: Contents.ICreateOptions = {}
   ): Promise<Contents.IModel> {
-    path = this._root ? this._root + '/' + path : path;
-    toDir = this._root ? this._root + (toDir ? '/' + toDir : toDir) : toDir;
-
-    const isDir: boolean = path.split('.').length === 1;
-
     // construct new file or directory name for the copy
-    let newFileName = await this.incrementCopyName(path, isDir, this._name);
-    newFileName = toDir !== '' ? toDir + '/' + newFileName : newFileName;
-    path = isDir ? path + '/' : path;
-
-    // list contents of path - contents of directory or one file
-    const command = new ListObjectsV2Command({
-      Bucket: this._name,
-      Prefix: path
-    });
-
-    let isTruncated: boolean | undefined = true;
-
-    while (isTruncated) {
-      const { Contents, IsTruncated, NextContinuationToken } =
-        await this._s3Client.send(command);
-
-      if (Contents) {
-        const promises = Contents.map(c => {
-          const remainingFilePath = c.Key!.substring(path.length);
-          // copy each file from old directory to new location
-          return this.copy_file(
-            remainingFilePath,
-            path,
-            newFileName,
-            this._name
-          );
-        });
-        await Promise.all(promises);
-      }
-      if (isTruncated) {
-        isTruncated = IsTruncated;
-      }
-      command.input.ContinuationToken = NextContinuationToken;
-    }
-
-    const [fileType, fileMimeType, fileFormat] = this.getFileType(
-      newFileName.split('.')[1]
+    const newFileName = await this.incrementCopyName(path, this._name);
+
+    data = await copyS3Objects(
+      this._s3Client,
+      this._name,
+      this._root,
+      newFileName,
+      path,
+      toDir,
+      this._registeredFileTypes
     );
 
-    // retrieve information of new file
-    const newFileContents = await this._s3Client.send(
-      new GetObjectCommand({
-        Bucket: this._name,
-        Key: newFileName
-      })
-    );
-
-    data = {
-      name: newFileName,
-      path: toDir + '/' + newFileName,
-      last_modified: newFileContents.LastModified!.toISOString(),
-      created: new Date().toISOString(),
-      content: await newFileContents.Body!.transformToString(),
-      format: fileFormat as Contents.FileFormat,
-      mimetype: fileMimeType,
-      size: newFileContents.ContentLength!,
-      writable: true,
-      type: fileType
-    };
-
     this._fileChanged.emit({
       type: 'new',
       oldValue: null,
@@ -1107,77 +623,19 @@ export class Drive implements Contents.IDrive {
     bucketName: string,
     options: Contents.ICreateOptions = {}
   ): Promise<Contents.IModel> {
-    path =
-      path[path.length - 1] === '/' ? path.substring(0, path.length - 1) : path;
-    path = this._root ? this._root + (path ? '/' + path : path) : path;
-
-    // list contents of path - contents of directory or one file
-    const command = new ListObjectsV2Command({
-      Bucket: this._name,
-      Prefix: path
-    });
-
-    let isTruncated: boolean | undefined = true;
-
-    while (isTruncated) {
-      const { Contents, IsTruncated, NextContinuationToken } =
-        await this._s3Client.send(command);
-
-      if (Contents) {
-        const isDir: boolean =
-          Contents.length > 1 ||
-          Contents![0].Key![Contents![0].Key!.length - 1] === '/'
-            ? true
-            : false;
-        const newFileName = await this.incrementCopyName(
-          path,
-          isDir,
-          bucketName
-        );
-        path = isDir ? path + '/' : path;
-
-        const promises = Contents.map(c => {
-          const remainingFilePath = c.Key!.substring(path.length);
-          // copy each file from old directory to new location
-          return this.copy_file(
-            remainingFilePath,
-            path,
-            newFileName,
-            bucketName
-          );
-        });
-        await Promise.all(promises);
-
-        const [fileType, fileMimeType, fileFormat] = this.getFileType(
-          newFileName.split('.')[1]
-        );
-
-        // retrieve information of new file
-        const newFileContents = await this._s3Client.send(
-          new GetObjectCommand({
-            Bucket: bucketName,
-            Key: toDir !== '' ? toDir + '/' + newFileName : newFileName
-          })
-        );
-
-        data = {
-          name: newFileName,
-          path: toDir + '/' + newFileName,
-          last_modified: newFileContents.LastModified!.toISOString(),
-          created: new Date().toISOString(),
-          content: await newFileContents.Body!.transformToString(),
-          format: fileFormat as Contents.FileFormat,
-          mimetype: fileMimeType,
-          size: newFileContents.ContentLength!,
-          writable: true,
-          type: fileType
-        };
-      }
-      if (isTruncated) {
-        isTruncated = IsTruncated;
-      }
-      command.input.ContinuationToken = NextContinuationToken;
-    }
+    // construct new file or directory name for the copy
+    const newFileName = await this.incrementCopyName(path, bucketName);
+
+    data = await copyS3Objects(
+      this._s3Client,
+      this._name,
+      this._root,
+      newFileName,
+      path,
+      toDir,
+      this._registeredFileTypes,
+      bucketName
+    );
 
     this._fileChanged.emit({
       type: 'new',
@@ -1254,43 +712,6 @@ export class Drive implements Contents.IDrive {
     );
     return (response?.LocationConstraint as string) ?? '';
   }
-  /**
-   * Helping function for copying the files inside a directory
-   * to a new location, in the case of renaming or copying a directory.
-   *
-   * @param remainingFilePath remaining path of file to be copied
-   * @param oldPath old path of file
-   * @param newPath new path of file
-   */
-  private async copy_file(
-    remainingFilePath: string,
-    oldPath: string,
-    newPath: string,
-    bucketName: string
-  ) {
-    await this._s3Client.send(
-      new CopyObjectCommand({
-        Bucket: bucketName,
-        CopySource: this._name + '/' + oldPath + remainingFilePath,
-        Key: newPath + remainingFilePath
-      })
-    );
-  }
-
-  /**
-   * Helping function for deleting files inside
-   * a directory, in the case of deleting or renaming a directory.
-   *
-   * @param filePath complete path of file to delete
-   */
-  private async delete_file(filePath: string) {
-    await this.s3Client.send(
-      new DeleteObjectCommand({
-        Bucket: this._name,
-        Key: filePath
-      })
-    );
-  }
 
   /**
    * Get all registered file types and store them accordingly with their file
@@ -1315,7 +736,6 @@ export class Drive implements Contents.IDrive {
 
       // store the mimetype and fileformat for each file extension
       fileType.extensions.forEach(extension => {
-        extension = extension.split('.')[1];
         if (!this._registeredFileTypes[extension]) {
           this._registeredFileTypes[extension] = {
             fileType: fileType.name,
@@ -1327,31 +747,6 @@ export class Drive implements Contents.IDrive {
     }
   }
 
-  /**
-   * Helping function to define file type, mimetype and format based on file extension.
-   * @param extension file extension (e.g.: txt, ipynb, csv)
-   * @returns
-   */
-  private getFileType(extension: string) {
-    let fileType: string = 'text';
-    let fileMimetype: string = 'text/plain';
-    let fileFormat: string = 'text';
-    extension = extension ?? '';
-
-    if (this._registeredFileTypes[extension]) {
-      fileType = this._registeredFileTypes[extension].fileType;
-      fileMimetype = this._registeredFileTypes[extension].fileMimeTypes[0];
-      fileFormat = this._registeredFileTypes[extension].fileFormat;
-    }
-
-    // the file format for notebooks appears as json, but should be text
-    if (extension === 'ipynb') {
-      fileFormat = 'text';
-    }
-
-    return [fileType, fileMimetype, fileFormat];
-  }
-
   /**
    * Helping function which formats root by removing all leading or trailing
    * backslashes and checking if given path to directory exists.
@@ -1366,16 +761,10 @@ export class Drive implements Contents.IDrive {
     }
 
     // reformat the path to arbitrary root so it has no leading or trailing /
-    root = root.replace(/^\/+|\/+$/g, '');
-
+    root = PathExt.removeSlash(PathExt.normalize(root));
     // check if directory exists within bucket
     try {
-      await this._s3Client.send(
-        new HeadObjectCommand({
-          Bucket: this._name,
-          Key: root + '/'
-        })
-      );
+      checkS3Object(this._s3Client, this._name, root);
       // the directory exists, root is formatted correctly
       return root;
     } catch (error) {
diff --git a/yarn.lock b/yarn.lock
index 21b9d43..f4a5aea 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8597,6 +8597,7 @@ __metadata:
     "@aws-sdk/s3-request-presigner": ^3.511.0
     "@jupyterlab/application": ^4.0.0
     "@jupyterlab/builder": ^4.0.0
+    "@jupyterlab/coreutils": ^6.2.2
     "@jupyterlab/testutils": ^4.0.0
     "@lumino/coreutils": 2.1.2
     "@types/jest": ^29.2.0