diff --git a/client/constants.js b/client/constants.js
index fa6010aed1..6591c7637a 100644
--- a/client/constants.js
+++ b/client/constants.js
@@ -28,6 +28,7 @@ export const RENAME_PROJECT = 'RENAME_PROJECT';
 export const PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS';
 export const PROJECT_SAVE_FAIL = 'PROJECT_SAVE_FAIL';
 export const NEW_PROJECT = 'NEW_PROJECT';
+export const NEW_MODULE_PROJECT = 'NEW_MODULE_PROJECT';
 export const RESET_PROJECT = 'RESET_PROJECT';
 
 export const SET_PROJECT = 'SET_PROJECT';
diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js
index 1d8943336b..cd0efe7a46 100644
--- a/client/modules/IDE/actions/project.js
+++ b/client/modules/IDE/actions/project.js
@@ -273,6 +273,13 @@ export function newProject() {
   return resetProject();
 }
 
+export function newModuleProject() {
+  browserHistory.push('/', { confirmed: true, moduleProject: true });
+  return {
+    type: ActionTypes.NEW_MODULE_PROJECT
+  };
+}
+
 function generateNewIdsForChildren(file, files) {
   const newChildren = [];
   file.children.forEach((childId) => {
diff --git a/client/modules/IDE/components/Header/Nav.jsx b/client/modules/IDE/components/Header/Nav.jsx
index f40e9137bf..d41e3488b7 100644
--- a/client/modules/IDE/components/Header/Nav.jsx
+++ b/client/modules/IDE/components/Header/Nav.jsx
@@ -130,6 +130,7 @@ const ProjectMenu = () => {
   const { t } = useTranslation();
   const {
     newSketch,
+    newModuleSketch,
     saveSketch,
     downloadSketch,
     shareSketch
@@ -165,6 +166,9 @@ const ProjectMenu = () => {
       </li>
       <MenubarSubmenu id="file" title={t('Nav.File.Title')}>
         <MenubarItem onClick={newSketch}>{t('Nav.File.New')}</MenubarItem>
+        <MenubarItem onClick={newModuleSketch}>
+          {t('Nav.File.NewModule')}
+        </MenubarItem>
         <MenubarItem
           hideIf={
             !getConfig('LOGIN_ENABLED') || (project?.owner && !isUserOwner)
diff --git a/client/modules/IDE/components/Header/__snapshots__/Nav.unit.test.jsx.snap b/client/modules/IDE/components/Header/__snapshots__/Nav.unit.test.jsx.snap
index d9140653c1..bc0869afc5 100644
--- a/client/modules/IDE/components/Header/__snapshots__/Nav.unit.test.jsx.snap
+++ b/client/modules/IDE/components/Header/__snapshots__/Nav.unit.test.jsx.snap
@@ -540,6 +540,15 @@ exports[`Nav renders editor version for desktop 1`] = `
                 New
               </button>
             </li>
+            <li
+              class="nav__dropdown-item"
+            >
+              <button
+                role="menuitem"
+              >
+                New Module
+              </button>
+            </li>
           </ul>
         </li>
         <li
diff --git a/client/modules/IDE/hooks/useSketchActions.js b/client/modules/IDE/hooks/useSketchActions.js
index a4c5d70235..f5c6841a14 100644
--- a/client/modules/IDE/hooks/useSketchActions.js
+++ b/client/modules/IDE/hooks/useSketchActions.js
@@ -5,6 +5,7 @@ import {
   autosaveProject,
   exportProjectAsZip,
   newProject,
+  newModuleProject,
   saveProject,
   setProjectName
 } from '../actions/project';
@@ -32,6 +33,16 @@ const useSketchActions = () => {
     }
   }
 
+  function newModuleSketch() {
+    if (!unsavedChanges) {
+      dispatch(showToast('Toast.OpenedNewModuleSketch'));
+      dispatch(newModuleProject());
+    } else if (window.confirm(t('Nav.WarningUnsavedChanges'))) {
+      dispatch(showToast('Toast.OpenedNewModuleSketch'));
+      dispatch(newModuleProject());
+    }
+  }
+
   function saveSketch(cmController) {
     if (authenticated) {
       dispatch(saveProject(cmController?.getContent()));
@@ -62,6 +73,7 @@ const useSketchActions = () => {
 
   return {
     newSketch,
+    newModuleSketch,
     saveSketch,
     downloadSketch,
     shareSketch,
diff --git a/client/modules/IDE/reducers/files.js b/client/modules/IDE/reducers/files.js
index de1b9b1aa7..6d57b0ceec 100644
--- a/client/modules/IDE/reducers/files.js
+++ b/client/modules/IDE/reducers/files.js
@@ -3,7 +3,10 @@ import * as ActionTypes from '../../../constants';
 import {
   defaultSketch,
   defaultCSS,
-  defaultHTML
+  defaultHTML,
+  defaultModuleSketch,
+  defaultModuleHTML,
+  createDefaultModuleFiles
 } from '../../../../server/domain-objects/createDefaultFiles';
 
 export const initialState = () => {
@@ -51,6 +54,51 @@ export const initialState = () => {
   ];
 };
 
+export const moduleState = () => {
+  const a = objectID().toHexString();
+  const b = objectID().toHexString();
+  const c = objectID().toHexString();
+  const r = objectID().toHexString();
+  return [
+    {
+      name: 'root',
+      id: r,
+      _id: r,
+      children: [b, a, c],
+      fileType: 'folder',
+      content: ''
+    },
+    {
+      name: 'sketch.js',
+      content: defaultModuleSketch,
+      id: a,
+      _id: a,
+      isSelectedFile: true,
+      fileType: 'file',
+      children: [],
+      filePath: ''
+    },
+    {
+      name: 'index.html',
+      content: defaultModuleHTML,
+      id: b,
+      _id: b,
+      fileType: 'file',
+      children: [],
+      filePath: ''
+    },
+    {
+      name: 'style.css',
+      content: defaultCSS,
+      id: c,
+      _id: c,
+      fileType: 'file',
+      children: [],
+      filePath: ''
+    }
+  ];
+};
+
 function getAllDescendantIds(state, nodeId) {
   return state
     .find((file) => file.id === nodeId)
@@ -158,17 +206,10 @@ const files = (state, action) => {
         }
         return Object.assign({}, file, { blobURL: action.blobURL });
       });
-    case ActionTypes.NEW_PROJECT: {
-      const newFiles = action.files.map((file) => {
-        const corrospondingObj = state.find((obj) => obj.id === file.id);
-        if (corrospondingObj && corrospondingObj.fileType === 'folder') {
-          const isFolderClosed = corrospondingObj.isFolderClosed || false;
-          return { ...file, isFolderClosed };
-        }
-        return file;
-      });
-      return setFilePaths(newFiles);
-    }
+    case ActionTypes.NEW_PROJECT:
+      return initialState();
+    case ActionTypes.NEW_MODULE_PROJECT:
+      return moduleState();
     case ActionTypes.SET_PROJECT: {
       const newFiles = action.files.map((file) => {
         const corrospondingObj = state.find((obj) => obj.id === file.id);
diff --git a/client/modules/Preview/EmbedFrame.jsx b/client/modules/Preview/EmbedFrame.jsx
index 4b2ad60d9b..069be4480f 100644
--- a/client/modules/Preview/EmbedFrame.jsx
+++ b/client/modules/Preview/EmbedFrame.jsx
@@ -69,10 +69,22 @@ function resolveCSSLinksInString(content, files) {
   return newContent;
 }
 
-function jsPreprocess(jsText) {
+function jsPreprocess(jsText, isModule = false) {
   let newContent = jsText;
-  // check the code for js errors before sending it to strip comments
-  // or loops.
+  // If this is a module, we need to be careful with transformations
+  // as they might break import/export statements
+  if (isModule || /\b(import|export)\b/.test(jsText)) {
+    // For modules, we still want to check for errors but we'll be more careful with transformations
+    JSHINT(newContent, { esversion: 11, module: true });
+    // For modules, we only decomment but don't apply loop protection
+    // as it might break module semantics
+    newContent = decomment(newContent, {
+      ignore: /\/\/\s*noprotect/g,
+      space: true
+    });
+    return newContent;
+  }
+  // For regular scripts, apply the standard processing
   JSHINT(newContent);
 
   if (JSHINT.errors.length === 0) {
@@ -87,6 +99,9 @@ function jsPreprocess(jsText) {
 
 function resolveJSLinksInString(content, files) {
   let newContent = content;
+  // Check if this is an ES module (contains import/export statements)
+  const isModule = /\b(import|export)\b/.test(content);
+  // Handle regular string references to files
   let jsFileStrings = content.match(STRING_REGEX);
   jsFileStrings = jsFileStrings || [];
   jsFileStrings.forEach((jsFileString) => {
@@ -101,23 +116,63 @@ function resolveJSLinksInString(content, files) {
             jsFileString,
             quoteCharacter + resolvedFile.url + quoteCharacter
           );
-        } else if (resolvedFile.name.match(PLAINTEXT_FILE_REGEX)) {
-          newContent = newContent.replace(
-            jsFileString,
-            quoteCharacter + resolvedFile.blobUrl + quoteCharacter
-          );
         }
       }
     }
   });
-
-  return jsPreprocess(newContent);
+  // If this is a module, also handle import statements
+  if (isModule) {
+    // Match import statements like: import { x } from './file.js';
+    // or import x from './file.js';
+    const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+)?(['"])([^'"]+)(['"])/g;
+    let importMatch;
+    // eslint-disable-next-line no-cond-assign
+    while ((importMatch = importRegex.exec(content))) {
+      const [fullMatch, openQuote, importPath, closeQuote] = importMatch;
+      // Only process relative imports, not package imports
+      if (importPath.startsWith('./') || importPath.startsWith('../')) {
+        const resolvedFile = resolvePathToFile(importPath, files);
+        if (resolvedFile) {
+          if (resolvedFile.url) {
+            // Replace the import path with the resolved URL
+            newContent = newContent.replace(
+              fullMatch,
+              fullMatch.replace(
+                `${openQuote}${importPath}${closeQuote}`,
+                `${openQuote}${resolvedFile.url}${closeQuote}`
+              )
+            );
+          } else {
+            // Create a blob URL for the imported file
+            const blobUrl = createBlobUrl(resolvedFile);
+            const blobPath = blobUrl.split('/').pop();
+            objectUrls[
+              blobUrl
+            ] = `${resolvedFile.filePath}/${resolvedFile.name}`;
+            objectPaths[blobPath] = resolvedFile.name;
+            // Replace the import path with the blob URL
+            newContent = newContent.replace(
+              fullMatch,
+              fullMatch.replace(
+                `${openQuote}${importPath}${closeQuote}`,
+                `${openQuote}${blobUrl}${closeQuote}`
+              )
+            );
+          }
+        }
+      }
+    }
+  }
+  // Apply preprocessing with module awareness
+  return jsPreprocess(newContent, isModule);
 }
 
 function resolveScripts(sketchDoc, files) {
   const scriptsInHTML = sketchDoc.getElementsByTagName('script');
   const scriptsInHTMLArray = Array.prototype.slice.call(scriptsInHTML);
   scriptsInHTMLArray.forEach((script) => {
+    // Check if this is a module script
+    const isModule = script.getAttribute('type') === 'module';
     if (
       script.getAttribute('src') &&
       script.getAttribute('src').match(NOT_EXTERNAL_LINK_REGEX) !== null
@@ -137,9 +192,10 @@ function resolveScripts(sketchDoc, files) {
           // }${resolvedFile.name}`;
           objectUrls[blobUrl] = `${resolvedFile.filePath}/${resolvedFile.name}`;
           objectPaths[blobPath] = resolvedFile.name;
-          // script.setAttribute('data-tag', `${startTag}${resolvedFile.name}`);
-          // script.removeAttribute('src');
-          // script.innerHTML = resolvedFile.content; // eslint-disable-line
+          // Preserve the module type if it was set
+          if (isModule) {
+            script.setAttribute('type', 'module');
+          }
         }
       }
     } else if (
@@ -149,7 +205,14 @@ function resolveScripts(sketchDoc, files) {
       ) !== null
     ) {
       script.setAttribute('crossorigin', '');
-      script.innerHTML = resolveJSLinksInString(script.innerHTML, files); // eslint-disable-line
+      // For inline scripts, we need to handle module content differently
+      // to preserve ES module semantics
+      if (isModule) {
+        // For module scripts, we don't apply loop protection as it might break imports
+        script.innerHTML = resolveJSLinksInString(script.innerHTML, files); // eslint-disable-line
+      } else {
+        script.innerHTML = resolveJSLinksInString(script.innerHTML, files); // eslint-disable-line
+      }
     }
   });
 }
diff --git a/server/domain-objects/createDefaultFiles.js b/server/domain-objects/createDefaultFiles.js
index 82a1470123..57725fc2c0 100644
--- a/server/domain-objects/createDefaultFiles.js
+++ b/server/domain-objects/createDefaultFiles.js
@@ -6,6 +6,15 @@ function draw() {
   background(220);
 }`;
 
+export const defaultModuleSketch = `// ES Module version
+export function setup() {
+  createCanvas(400, 400);
+}
+
+export function draw() {
+  background(220);
+}`;
+
 export const defaultHTML = `<!DOCTYPE html>
 <html lang="en">
   <head>
@@ -23,6 +32,23 @@ export const defaultHTML = `<!DOCTYPE html>
 </html>
 `;
 
+export const defaultModuleHTML = `<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.1/p5.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.1/addons/p5.sound.min.js"></script>
+    <link rel="stylesheet" type="text/css" href="style.css">
+    <meta charset="utf-8" />
+
+  </head>
+  <body>
+    <main>
+    </main>
+    <script src="sketch.js" type="module"></script>
+  </body>
+</html>
+`;
+
 export const defaultCSS = `html, body {
   margin: 0;
   padding: 0;
@@ -45,3 +71,17 @@ export default function createDefaultFiles() {
     }
   };
 }
+
+export function createDefaultModuleFiles() {
+  return {
+    'index.html': {
+      content: defaultModuleHTML
+    },
+    'style.css': {
+      content: defaultCSS
+    },
+    'sketch.js': {
+      content: defaultModuleSketch
+    }
+  };
+}
diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json
index 0f46c59c14..a2cb95b7ad 100644
--- a/translations/locales/en-US/translations.json
+++ b/translations/locales/en-US/translations.json
@@ -3,6 +3,7 @@
     "File": {
       "Title": "File",
       "New": "New",
+      "NewModule": "New Module",
       "Share": "Share",
       "Duplicate": "Duplicate",
       "Open": "Open",
@@ -133,6 +134,7 @@
   },
   "Toast": {
     "OpenedNewSketch": "Opened new sketch.",
+    "OpenedNewModuleSketch": "Opened new module sketch.",
     "SketchSaved": "Sketch saved.",
     "SketchFailedSave": "Failed to save sketch.",
     "AutosaveEnabled": "Autosave enabled.",
@@ -362,7 +364,7 @@
     "CreateTokenSubmit": "Create",
     "NoTokens": "You have no existing tokens.",
     "NewTokenTitle": "Your new access token",
-    "NewTokenInfo": "Make sure to copy your new personal access token now.\n                  You won’t be able to see it again!",
+    "NewTokenInfo": "Make sure to copy your new personal access token now.\n                  You won't be able to see it again!",
     "ExistingTokensTitle": "Existing tokens"
   },
   "APIKeyList": {