diff --git a/apps/app-frontend/src/components/ui/CurseForgeProfileImportModal.vue b/apps/app-frontend/src/components/ui/CurseForgeProfileImportModal.vue
new file mode 100644
index 00000000..008d2d04
--- /dev/null
+++ b/apps/app-frontend/src/components/ui/CurseForgeProfileImportModal.vue
@@ -0,0 +1,401 @@
+<template>
+  <ModalWrapper ref="modal" :header="'Import from CurseForge Profile Code'">
+    <div class="modal-body">
+      <div class="input-row">
+        <p class="input-label">Profile Code</p>
+        <div class="iconified-input">
+          <SearchIcon aria-hidden="true" class="text-lg" />
+          <input
+            ref="codeInput"
+            v-model="profileCode"
+            autocomplete="off"
+            class="h-12 card-shadow"
+            spellcheck="false"
+            type="text"
+            placeholder="Enter CurseForge profile code"
+            maxlength="20"
+            @keyup.enter="importProfile"
+          />
+          <Button v-if="profileCode" class="r-btn" @click="() => (profileCode = '')">
+            <XIcon />
+          </Button>
+        </div>
+      </div>
+      
+      <div v-if="metadata && !importing" class="profile-info">
+        <h3>Profile Information</h3>
+        <p><strong>Name:</strong> {{ metadata.name }}</p>
+      </div>
+      
+      <div v-if="error" class="error-message">
+        <p>{{ error }}</p>
+      </div>
+      
+      <div v-if="importing && importProgress.visible" class="progress-section">
+        <div class="progress-info">
+          <span class="progress-text">{{ importProgress.message }}</span>
+          <span class="progress-percentage">{{ Math.floor(importProgress.percentage) }}%</span>
+        </div>
+        <div class="progress-bar-container">
+          <div
+            class="progress-bar" 
+            :style="{ width: `${importProgress.percentage}%` }"
+          ></div>
+        </div>
+      </div>
+      
+      <div class="button-row">
+        <Button @click="hide" :disabled="importing">
+          <XIcon />
+          Cancel
+        </Button>
+        <Button 
+          v-if="!metadata" 
+          @click="fetchMetadata" 
+          :disabled="!profileCode.trim() || fetching"
+          color="secondary"
+        >
+          <SearchIcon v-if="!fetching" />
+          {{ fetching ? 'Checking...' : 'Check Profile' }}
+        </Button>
+        <Button 
+          v-if="metadata" 
+          @click="importProfile" 
+          :disabled="importing"
+          color="primary"
+        >
+          <DownloadIcon v-if="!importing" />
+          {{ importing ? 'Importing...' : 'Import Profile' }}
+        </Button>
+      </div>
+    </div>
+  </ModalWrapper>
+</template>
+
+<script setup>
+import { ref, nextTick, onMounted, onUnmounted } from 'vue'
+import { useRouter } from 'vue-router'
+import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
+import { Button } from '@modrinth/ui'
+import { 
+  XIcon, 
+  SearchIcon, 
+  DownloadIcon 
+} from '@modrinth/assets'
+import { 
+  fetch_curseforge_profile_metadata, 
+  import_curseforge_profile 
+} from '@/helpers/import.js'
+import { handleError } from '@/store/notifications.js'
+import { trackEvent } from '@/helpers/analytics'
+import { loading_listener } from '@/helpers/events.js'
+
+const props = defineProps({
+  closeParent: {
+    type: Function,
+    default: null
+  }
+})
+
+const router = useRouter()
+const modal = ref(null)
+const codeInput = ref(null)
+const profileCode = ref('')
+const metadata = ref(null)
+const fetching = ref(false)
+const importing = ref(false)
+const error = ref('')
+const importProgress = ref({
+  visible: false,
+  percentage: 0,
+  message: 'Starting import...',
+  totalMods: 0,
+  downloadedMods: 0
+})
+
+let unlistenLoading = null
+let activeLoadingBarId = null
+let progressFallbackTimer = null
+
+defineExpose({
+  show: () => {
+    profileCode.value = ''
+    metadata.value = null
+    fetching.value = false
+    importing.value = false
+    error.value = ''
+    importProgress.value = {
+      visible: false,
+      percentage: 0,
+      message: 'Starting import...',
+      totalMods: 0,
+      downloadedMods: 0
+    }
+    modal.value?.show()
+    
+    nextTick(() => {
+      setTimeout(() => {
+        codeInput.value?.focus()
+      }, 100)
+    })
+    
+    trackEvent('CurseForgeProfileImportStart', { source: 'ImportModal' })
+  },
+})
+
+const hide = () => {
+  modal.value?.hide()
+}
+
+const fetchMetadata = async () => {
+  if (!profileCode.value.trim()) return
+  
+  fetching.value = true
+  error.value = ''
+  
+  try {
+    const result = await fetch_curseforge_profile_metadata(profileCode.value.trim())
+    metadata.value = result
+    trackEvent('CurseForgeProfileMetadataFetched', { 
+      profileCode: profileCode.value.trim() 
+    })
+  } catch (err) {
+    console.error('Failed to fetch CurseForge profile metadata:', err)
+    error.value = 'Failed to fetch profile information. Please check the code and try again.'
+    handleError(err)
+  } finally {
+    fetching.value = false
+  }
+}
+
+const importProfile = async () => {
+  if (!profileCode.value.trim()) return
+  
+  importing.value = true
+  error.value = ''
+  activeLoadingBarId = null // Reset for new import session
+  importProgress.value = {
+    visible: true,
+    percentage: 0,
+    message: 'Starting import...',
+    totalMods: 0,
+    downloadedMods: 0
+  }
+  
+  // Fallback progress timer in case loading events don't work
+  progressFallbackTimer = setInterval(() => {
+    if (importing.value && importProgress.value.percentage < 90) {
+      // Slowly increment progress as a fallback
+      importProgress.value.percentage = Math.min(90, importProgress.value.percentage + 1)
+    }
+  }, 1000)
+  
+  try {
+    const { result, profilePath } = await import_curseforge_profile(profileCode.value.trim())
+    
+    trackEvent('CurseForgeProfileImported', { 
+      profileCode: profileCode.value.trim() 
+    })
+    
+    hide()
+    
+    // Close the parent modal if provided
+    if (props.closeParent) {
+      props.closeParent()
+    }
+    
+    // Navigate to the imported profile
+    await router.push(`/instance/${encodeURIComponent(profilePath)}`)
+  } catch (err) {
+    console.error('Failed to import CurseForge profile:', err)
+    error.value = 'Failed to import profile. Please try again.'
+    handleError(err)
+  } finally {
+    importing.value = false
+    importProgress.value.visible = false
+    if (progressFallbackTimer) {
+      clearInterval(progressFallbackTimer)
+      progressFallbackTimer = null
+    }
+    activeLoadingBarId = null
+  }
+}
+
+onMounted(async () => {
+  // Listen for loading events to update progress
+  unlistenLoading = await loading_listener((event) => {
+    console.log('Loading event received:', event) // Debug log
+    
+    // Handle all loading events that could be related to CurseForge profile import
+    const isCurseForgeEvent = event.event?.type === 'curseforge_profile_download'
+    const hasProfileName = event.event?.profile_name && importing.value
+    
+    if ((isCurseForgeEvent || hasProfileName) && importing.value) {
+      // Store the loading bar ID for this import session
+      if (!activeLoadingBarId) {
+        activeLoadingBarId = event.loader_uuid
+      }
+      
+      // Only process events for our current import session
+      if (event.loader_uuid === activeLoadingBarId) {
+        if (event.fraction !== null && event.fraction !== undefined) {
+          const baseProgress = (event.fraction || 0) * 100
+          
+          // Calculate custom progress based on the message
+          let finalProgress = baseProgress
+          const message = event.message || 'Importing profile...'
+          
+          // Custom progress calculation for different stages
+          if (message.includes('Fetching') || message.includes('metadata')) {
+            finalProgress = Math.min(10, baseProgress)
+          } else if (message.includes('Downloading profile ZIP') || message.includes('profile ZIP')) {
+            finalProgress = Math.min(15, 10 + (baseProgress - 10) * 0.5)
+          } else if (message.includes('Extracting') || message.includes('ZIP')) {
+            finalProgress = Math.min(20, 15 + (baseProgress - 15) * 0.5)
+          } else if (message.includes('Configuring') || message.includes('profile')) {
+            finalProgress = Math.min(30, 20 + (baseProgress - 20) * 0.5)
+          } else if (message.includes('Copying') || message.includes('files')) {
+            finalProgress = Math.min(40, 30 + (baseProgress - 30) * 0.5)
+          } else if (message.includes('Downloaded mod') && message.includes(' of ')) {
+            // Parse "Downloaded mod X of Y" message
+            const match = message.match(/Downloaded mod (\d+) of (\d+)/)
+            if (match) {
+              const current = parseInt(match[1])
+              const total = parseInt(match[2])
+              // Mods take 40% of progress (from 40% to 80%)
+              const modProgress = (current / total) * 40
+              finalProgress = 40 + modProgress
+            } else {
+              finalProgress = Math.min(80, 40 + (baseProgress - 40) * 0.5)
+            }
+          } else if (message.includes('Downloading mod') || message.includes('mods')) {
+            // General mod downloading stage (40% to 80%)
+            finalProgress = Math.min(80, 40 + (baseProgress - 40) * 0.4)
+          } else if (message.includes('Installing Minecraft') || message.includes('Minecraft')) {
+            finalProgress = Math.min(95, 80 + (baseProgress - 80) * 0.75)
+          } else if (message.includes('Finalizing') || message.includes('completed')) {
+            finalProgress = Math.min(100, 95 + (baseProgress - 95))
+          } else {
+            // Default: use the base progress but ensure minimum progression
+            finalProgress = Math.max(importProgress.value.percentage, baseProgress)
+          }
+          
+          importProgress.value.percentage = Math.min(100, Math.max(0, finalProgress))
+          importProgress.value.message = message
+        } else {
+          // Loading complete
+          importProgress.value.percentage = 100
+          importProgress.value.message = 'Import completed!'
+          activeLoadingBarId = null
+        }
+      }
+    }
+  })
+})
+
+onUnmounted(() => {
+  if (unlistenLoading) {
+    unlistenLoading()
+  }
+  if (progressFallbackTimer) {
+    clearInterval(progressFallbackTimer)
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.modal-body {
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+  padding: 1rem;
+}
+
+.input-row {
+  display: flex;
+  flex-direction: column;
+  gap: 0.5rem;
+}
+
+.input-label {
+  font-weight: 600;
+  color: var(--color-contrast);
+  margin: 0;
+}
+
+.profile-info {
+  background: var(--color-bg);
+  border: 1px solid var(--color-button);
+  border-radius: var(--radius-md);
+  padding: 1rem;
+  
+  h3 {
+    margin: 0 0 0.5rem 0;
+    color: var(--color-contrast);
+  }
+  
+  p {
+    margin: 0.25rem 0;
+    color: var(--color-base);
+  }
+}
+
+.error-message {
+  background: var(--color-red);
+  border: 1px solid var(--color-red);
+  border-radius: var(--radius-md);
+  padding: 0.75rem;
+  
+  p {
+    margin: 0;
+    color: var(--color-contrast);
+    font-weight: 500;
+  }
+}
+
+.progress-section {
+  display: flex;
+  flex-direction: column;
+  gap: 0.5rem;
+}
+
+.progress-info {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  .progress-text {
+    color: var(--color-base);
+    font-size: 0.875rem;
+  }
+  
+  .progress-percentage {
+    color: var(--color-contrast);
+    font-size: 0.875rem;
+    font-weight: 600;
+  }
+}
+
+.progress-bar-container {
+  width: 100%;
+  height: 4px;
+  background: var(--color-button);
+  border-radius: 2px;
+  overflow: hidden;
+}
+
+.progress-bar {
+  height: 100%;
+  background: var(--color-brand);
+  border-radius: 2px;
+  transition: width 0.3s ease;
+  min-width: 0;
+}
+
+.button-row {
+  display: flex;
+  gap: 0.5rem;
+  justify-content: flex-end;
+  margin-top: auto;
+}
+</style>
diff --git a/apps/app-frontend/src/components/ui/InstanceCreationModal.vue b/apps/app-frontend/src/components/ui/InstanceCreationModal.vue
index ee6328ff..c086afaf 100644
--- a/apps/app-frontend/src/components/ui/InstanceCreationModal.vue
+++ b/apps/app-frontend/src/components/ui/InstanceCreationModal.vue
@@ -163,6 +163,14 @@
         <div v-else class="table-content empty">No profiles found</div>
       </div>
       <div class="button-row">
+        <Button
+          v-if="selectedProfileType.name === 'Curseforge'"
+          @click="showCurseForgeProfileModal"
+          :disabled="loading"
+        >
+          <CodeIcon />
+          Import from Profile Code
+        </Button>
         <Button
           :disabled="
             loading ||
@@ -194,10 +202,12 @@
       </div>
     </div>
   </ModalWrapper>
+  <CurseForgeProfileImportModal ref="curseforgeProfileModal" :close-parent="hide" />
 </template>
 
 <script setup>
 import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
+import CurseForgeProfileImportModal from '@/components/ui/CurseForgeProfileImportModal.vue'
 import {
   CodeIcon,
   FolderOpenIcon,
@@ -283,6 +293,11 @@ const hide = () => {
     unlistener.value = null
   }
 }
+
+const showCurseForgeProfileModal = () => {
+  curseforgeProfileModal.value?.show()
+}
+
 onUnmounted(() => {
   if (unlistener.value) {
     unlistener.value()
@@ -338,6 +353,7 @@ const game_versions = computed(() => {
 })
 
 const modal = ref(null)
+const curseforgeProfileModal = ref(null)
 
 const check_valid = computed(() => {
   return (
diff --git a/apps/app-frontend/src/helpers/import.js b/apps/app-frontend/src/helpers/import.js
index ba0bc4ff..d6bc9d38 100644
--- a/apps/app-frontend/src/helpers/import.js
+++ b/apps/app-frontend/src/helpers/import.js
@@ -61,3 +61,31 @@ export async function is_valid_importable_instance(instanceFolder, launcherType)
 export async function get_default_launcher_path(launcherType) {
   return await invoke('plugin:import|get_default_launcher_path', { launcherType })
 }
+
+/// Fetch CurseForge profile metadata from profile code
+/// eg: fetch_curseforge_profile_metadata("eSrNlKNo")
+export async function fetch_curseforge_profile_metadata(profileCode) {
+  return await invoke('plugin:import|fetch_curseforge_profile_metadata', { profileCode })
+}
+
+/// Import a CurseForge profile from profile code
+/// eg: import_curseforge_profile("eSrNlKNo")
+export async function import_curseforge_profile(profileCode) {
+  try {
+    // First, fetch the profile metadata to get the actual name
+    const metadata = await fetch_curseforge_profile_metadata(profileCode)
+
+    // create a basic, empty instance using the actual profile name
+    const profilePath = await create(metadata.name, '1.19.4', 'vanilla', 'latest', null, true)
+
+    const result = await invoke('plugin:import|import_curseforge_profile', {
+      profilePath,
+      profileCode,
+    })
+
+    // Return the profile path for navigation
+    return { result, profilePath }
+  } catch (error) {
+    throw error
+  }
+}
diff --git a/apps/app/build.rs b/apps/app/build.rs
index 9d2b8789..aaf69482 100644
--- a/apps/app/build.rs
+++ b/apps/app/build.rs
@@ -51,6 +51,8 @@ fn main() {
                 "import",
                 InlinedPlugin::new()
                     .commands(&[
+                        "fetch_curseforge_profile_metadata",
+                        "import_curseforge_profile",
                         "get_importable_instances",
                         "import_instance",
                         "is_valid_importable_instance",
diff --git a/apps/app/capabilities/plugins.json b/apps/app/capabilities/plugins.json
index c78dd971..26a0d64a 100644
--- a/apps/app/capabilities/plugins.json
+++ b/apps/app/capabilities/plugins.json
@@ -19,13 +19,10 @@
     "window-state:default",
     "window-state:allow-restore-state",
     "window-state:allow-save-window-state",
-    
+
     {
       "identifier": "http:default",
-      "allow": [
-        { "url": "https://modrinth.com/*" },
-        { "url": "https://*.modrinth.com/*" }
-      ]
+      "allow": [{ "url": "https://modrinth.com/*" }, { "url": "https://*.modrinth.com/*" }]
     },
 
     "auth:default",
diff --git a/apps/app/src/api/import.rs b/apps/app/src/api/import.rs
index 0cab81cd..b642aa90 100644
--- a/apps/app/src/api/import.rs
+++ b/apps/app/src/api/import.rs
@@ -2,6 +2,11 @@ use std::path::PathBuf;
 
 use crate::api::Result;
 use theseus::pack::import::ImportLauncherType;
+use theseus::pack::import::curseforge_profile::{
+    CurseForgeProfileMetadata,
+    fetch_curseforge_profile_metadata as fetch_cf_metadata,
+    import_curseforge_profile as import_cf_profile,
+};
 
 use theseus::pack::import;
 
@@ -12,6 +17,8 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
             import_instance,
             is_valid_importable_instance,
             get_default_launcher_path,
+            fetch_curseforge_profile_metadata,
+            import_curseforge_profile,
         ])
         .build()
 }
@@ -68,3 +75,24 @@ pub async fn get_default_launcher_path(
 ) -> Result<Option<PathBuf>> {
     Ok(import::get_default_launcher_path(launcher_type))
 }
+
+/// Fetch CurseForge profile metadata from profile code
+/// eg: fetch_curseforge_profile_metadata("eSrNlKNo")
+#[tauri::command]
+pub async fn fetch_curseforge_profile_metadata(
+    profile_code: String,
+) -> Result<CurseForgeProfileMetadata> {
+    Ok(fetch_cf_metadata(&profile_code).await?)
+}
+
+/// Import a CurseForge profile from profile code
+/// profile_path should be a blank profile for this purpose- if the function fails, it will be deleted
+/// eg: import_curseforge_profile("profile-path", "eSrNlKNo")
+#[tauri::command]
+pub async fn import_curseforge_profile(
+    profile_path: String,
+    profile_code: String,
+) -> Result<()> {
+    import_cf_profile(&profile_code, &profile_path).await?;
+    Ok(())
+}
diff --git a/packages/app-lib/src/api/pack/import/curseforge_profile.rs b/packages/app-lib/src/api/pack/import/curseforge_profile.rs
new file mode 100644
index 00000000..0c3527a6
--- /dev/null
+++ b/packages/app-lib/src/api/pack/import/curseforge_profile.rs
@@ -0,0 +1,604 @@
+use std::io::Cursor;
+
+use async_zip::base::read::seek::ZipFileReader;
+use serde::{Deserialize, Serialize};
+
+use crate::{
+    State,
+    event::{LoadingBarType, ProfilePayloadType},
+    prelude::ModLoader,
+    state::{LinkedData, ProfileInstallStage},
+    util::fetch::fetch,
+};
+
+use super::copy_dotminecraft;
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct CurseForgeManifest {
+    pub minecraft: CurseForgeMinecraft,
+    pub manifest_type: String,
+    pub manifest_version: i32,
+    pub name: String,
+    pub version: String,
+    pub author: String,
+    pub files: Vec<CurseForgeFile>,
+    pub overrides: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct CurseForgeMinecraft {
+    pub version: String,
+    pub mod_loaders: Vec<CurseForgeModLoader>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct CurseForgeModLoader {
+    pub id: String,
+    pub primary: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct CurseForgeFile {
+    #[serde(rename = "projectID")]
+    pub project_id: u32,
+    #[serde(rename = "fileID")]
+    pub file_id: u32,
+    pub required: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct CurseForgeProfileMetadata {
+    pub name: String,
+    pub download_url: String,
+}
+
+/// Fetch CurseForge profile metadata from profile code
+pub async fn fetch_curseforge_profile_metadata(
+    profile_code: &str,
+) -> crate::Result<CurseForgeProfileMetadata> {
+    let state = State::get().await?;
+
+    // Make initial request to get redirect URL
+    let url = format!(
+        "https://api.curseforge.com/v1/shared-profile/{}",
+        profile_code
+    );
+
+    // Try to fetch the profile - the CurseForge API should redirect to the ZIP file
+    let response = fetch(&url, None, &state.fetch_semaphore, &state.pool).await;
+
+    let download_url = match response {
+        Ok(_bytes) => {
+            // If we get bytes back, use the original URL
+            url
+        }
+        Err(e) => {
+            // If we get an error, it might contain redirect information
+            let error_msg = format!("{:?}", e);
+            if let Some(redirect_start) =
+                error_msg.find("https://shared-profile-media.forgecdn.net/")
+            {
+                let redirect_end = error_msg[redirect_start..]
+                    .find(' ')
+                    .unwrap_or(error_msg.len() - redirect_start);
+                error_msg[redirect_start..redirect_start + redirect_end]
+                    .to_string()
+            } else {
+                return Err(crate::ErrorKind::InputError(format!(
+                    "Failed to fetch CurseForge profile metadata: {}",
+                    e
+                ))
+                .into());
+            }
+        }
+    };
+
+    // Now fetch the ZIP file and extract the name from manifest.json
+    let zip_bytes =
+        fetch(&download_url, None, &state.fetch_semaphore, &state.pool).await?;
+
+    // Create a cursor for the ZIP data
+    let cursor = std::io::Cursor::new(zip_bytes);
+    let mut zip_reader =
+        ZipFileReader::with_tokio(cursor).await.map_err(|e| {
+            crate::ErrorKind::InputError(format!(
+                "Failed to read profile ZIP: {}",
+                e
+            ))
+        })?;
+
+    // Find and extract manifest.json
+    let manifest_index = zip_reader
+        .file()
+        .entries()
+        .iter()
+        .position(|f| {
+            f.filename().as_str().unwrap_or_default() == "manifest.json"
+        })
+        .ok_or_else(|| {
+            crate::ErrorKind::InputError(
+                "No manifest.json found in profile".to_string(),
+            )
+        })?;
+
+    let mut manifest_content = String::new();
+    let mut reader = zip_reader
+        .reader_with_entry(manifest_index)
+        .await
+        .map_err(|e| {
+            crate::ErrorKind::InputError(format!(
+                "Failed to read manifest.json: {}",
+                e
+            ))
+        })?;
+
+    reader.read_to_string_checked(&mut manifest_content).await?;
+
+    // Parse the manifest to get the actual name
+    let manifest: CurseForgeManifest = serde_json::from_str(&manifest_content)?;
+
+    let profile_name = if manifest.name.is_empty() {
+        format!("CurseForge Profile {}", profile_code)
+    } else {
+        manifest.name.clone()
+    };
+
+    Ok(CurseForgeProfileMetadata {
+        name: profile_name,
+        download_url,
+    })
+}
+
+/// Import a CurseForge profile from profile code
+pub async fn import_curseforge_profile(
+    profile_code: &str,
+    profile_path: &str,
+) -> crate::Result<()> {
+    let state = State::get().await?;
+
+    // Initialize loading bar
+    let loading_bar = crate::event::emit::init_loading(
+        LoadingBarType::CurseForgeProfileDownload {
+            profile_name: profile_path.to_string(),
+        },
+        100.0,
+        "Importing CurseForge profile...",
+    )
+    .await?;
+
+    // First, fetch the profile metadata to get the download URL
+    crate::event::emit::emit_loading(
+        &loading_bar,
+        10.0,
+        Some("Fetching profile metadata..."),
+    )?;
+    let metadata = fetch_curseforge_profile_metadata(profile_code).await?;
+
+    // Download the profile ZIP file
+    crate::event::emit::emit_loading(
+        &loading_bar,
+        5.0,
+        Some("Downloading profile ZIP..."),
+    )?;
+    let zip_bytes = fetch(
+        &metadata.download_url,
+        None,
+        &state.fetch_semaphore,
+        &state.pool,
+    )
+    .await?;
+
+    // Create a cursor for the ZIP data
+    crate::event::emit::emit_loading(
+        &loading_bar,
+        5.0,
+        Some("Extracting ZIP contents..."),
+    )?;
+    let cursor = Cursor::new(zip_bytes);
+    let mut zip_reader =
+        ZipFileReader::with_tokio(cursor).await.map_err(|e| {
+            crate::ErrorKind::InputError(format!(
+                "Failed to read profile ZIP: {}",
+                e
+            ))
+        })?;
+
+    // Find and extract manifest.json
+    let manifest_index = zip_reader
+        .file()
+        .entries()
+        .iter()
+        .position(|f| {
+            f.filename().as_str().unwrap_or_default() == "manifest.json"
+        })
+        .ok_or_else(|| {
+            crate::ErrorKind::InputError(
+                "No manifest.json found in profile".to_string(),
+            )
+        })?;
+
+    let mut manifest_content = String::new();
+    let mut reader = zip_reader
+        .reader_with_entry(manifest_index)
+        .await
+        .map_err(|e| {
+            crate::ErrorKind::InputError(format!(
+                "Failed to read manifest.json: {}",
+                e
+            ))
+        })?;
+
+    reader.read_to_string_checked(&mut manifest_content).await?;
+
+    // Parse the manifest
+    crate::event::emit::emit_loading(
+        &loading_bar,
+        5.0,
+        Some("Parsing profile manifest..."),
+    )?;
+    let manifest: CurseForgeManifest = serde_json::from_str(&manifest_content)?;
+
+    // Determine modloader and version
+    crate::event::emit::emit_loading(
+        &loading_bar,
+        5.0,
+        Some("Configuring profile..."),
+    )?;
+    let (mod_loader, loader_version) = if let Some(primary_loader) =
+        manifest.minecraft.mod_loaders.iter().find(|l| l.primary)
+    {
+        parse_modloader(&primary_loader.id)
+    } else if let Some(first_loader) = manifest.minecraft.mod_loaders.first() {
+        parse_modloader(&first_loader.id)
+    } else {
+        (ModLoader::Vanilla, None)
+    };
+
+    let game_version = manifest.minecraft.version.clone();
+
+    // Get appropriate loader version if needed
+    let final_loader_version = if mod_loader != ModLoader::Vanilla {
+        crate::launcher::get_loader_version_from_profile(
+            &game_version,
+            mod_loader,
+            loader_version.as_deref(),
+        )
+        .await?
+    } else {
+        None
+    };
+
+    // Set profile data
+    crate::api::profile::edit(profile_path, |prof| {
+        prof.name = if manifest.name.is_empty() {
+            format!("CurseForge Profile {}", profile_code)
+        } else {
+            manifest.name.clone()
+        };
+        prof.install_stage = ProfileInstallStage::PackInstalling;
+        prof.game_version = game_version.clone();
+        prof.loader_version = final_loader_version.clone().map(|x| x.id);
+        prof.loader = mod_loader;
+
+        // Set linked data for modpack management
+        prof.linked_data = Some(LinkedData {
+            project_id: String::new(),
+            version_id: String::new(),
+            locked: false,
+        });
+
+        async { Ok(()) }
+    })
+    .await?;
+
+    // Create a temporary directory to extract overrides
+    let temp_dir = state
+        .directories
+        .caches_dir()
+        .join(format!("curseforge_profile_{}", profile_code));
+    tokio::fs::create_dir_all(&temp_dir).await?;
+
+    // Extract overrides directory if it exists
+    crate::event::emit::emit_loading(
+        &loading_bar,
+        10.0,
+        Some("Extracting profile files..."),
+    )?;
+    let overrides_dir = temp_dir.join(&manifest.overrides);
+    tokio::fs::create_dir_all(&overrides_dir).await?;
+
+    // Extract all files that are in the overrides directory
+    // First collect the entries we need to extract to avoid borrowing conflicts
+    let entries_to_extract: Vec<(usize, String)> = {
+        let zip_file = zip_reader.file();
+        zip_file
+            .entries()
+            .iter()
+            .enumerate()
+            .filter_map(|(index, entry)| {
+                let file_path = entry.filename().as_str().unwrap_or_default();
+                if file_path.starts_with(&format!("{}/", manifest.overrides)) {
+                    Some((index, file_path.to_string()))
+                } else {
+                    None
+                }
+            })
+            .collect()
+    };
+
+    // Now extract each file
+    for (index, file_path) in entries_to_extract {
+        let relative_path = file_path
+            .strip_prefix(&format!("{}/", manifest.overrides))
+            .unwrap();
+        let output_path = overrides_dir.join(relative_path);
+
+        // Create parent directories
+        if let Some(parent) = output_path.parent() {
+            tokio::fs::create_dir_all(parent).await?;
+        }
+
+        // Extract file
+        let mut reader =
+            zip_reader.reader_with_entry(index).await.map_err(|e| {
+                crate::ErrorKind::InputError(format!(
+                    "Failed to read file {}: {}",
+                    file_path, e
+                ))
+            })?;
+
+        let mut file_content = Vec::new();
+        reader.read_to_end_checked(&mut file_content).await?;
+
+        tokio::fs::write(&output_path, file_content).await?;
+    }
+
+    // Copy overrides to profile
+    crate::event::emit::emit_loading(
+        &loading_bar,
+        5.0,
+        Some("Copying profile files..."),
+    )?;
+    let _loading_bar = copy_dotminecraft(
+        profile_path,
+        overrides_dir,
+        &state.io_semaphore,
+        None,
+    )
+    .await?;
+
+    // Download and install mods from CurseForge
+    crate::event::emit::emit_loading(
+        &loading_bar,
+        10.0,
+        Some("Downloading mods..."),
+    )?;
+    install_curseforge_mods(
+        &manifest.files,
+        profile_path,
+        &state,
+        &loading_bar,
+    )
+    .await?;
+
+    // Clean up temporary directory
+    tokio::fs::remove_dir_all(&temp_dir).await.ok();
+
+    // Install Minecraft if needed
+    crate::event::emit::emit_loading(
+        &loading_bar,
+        20.0,
+        Some("Installing Minecraft..."),
+    )?;
+    if let Some(profile_val) = crate::api::profile::get(profile_path).await? {
+        crate::launcher::install_minecraft(
+            &profile_val,
+            Some(_loading_bar),
+            false,
+        )
+        .await?;
+    }
+
+    // Mark the profile as fully installed
+    crate::event::emit::emit_loading(
+        &loading_bar,
+        20.0,
+        Some("Finalizing profile..."),
+    )?;
+    crate::api::profile::edit(profile_path, |prof| {
+        prof.install_stage = ProfileInstallStage::Installed;
+        async { Ok(()) }
+    })
+    .await?;
+
+    // Emit profile sync event to trigger file system watcher refresh
+    crate::event::emit::emit_profile(profile_path, ProfilePayloadType::Synced)
+        .await?;
+
+    // Complete the loading bar
+    crate::event::emit::emit_loading(
+        &loading_bar,
+        5.0,
+        Some("Import completed!"),
+    )?;
+
+    Ok(())
+}
+
+/// Parse CurseForge modloader ID into ModLoader and version
+fn parse_modloader(id: &str) -> (ModLoader, Option<String>) {
+    if id.starts_with("forge-") {
+        let version = id.strip_prefix("forge-").unwrap_or("").to_string();
+        (ModLoader::Forge, Some(version))
+    } else if id.starts_with("fabric-") {
+        let version = id.strip_prefix("fabric-").unwrap_or("").to_string();
+        (ModLoader::Fabric, Some(version))
+    } else if id.starts_with("quilt-") {
+        let version = id.strip_prefix("quilt-").unwrap_or("").to_string();
+        (ModLoader::Quilt, Some(version))
+    } else if id.starts_with("neoforge-") {
+        let version = id.strip_prefix("neoforge-").unwrap_or("").to_string();
+        (ModLoader::NeoForge, Some(version))
+    } else {
+        (ModLoader::Vanilla, None)
+    }
+}
+
+/// Install mods from CurseForge files list
+async fn install_curseforge_mods(
+    files: &[CurseForgeFile],
+    profile_path: &str,
+    state: &State,
+    loading_bar: &crate::event::LoadingBarId,
+) -> crate::Result<()> {
+    if files.is_empty() {
+        return Ok(());
+    }
+
+    let num_files = files.len();
+    tracing::info!("Installing {} CurseForge mods", num_files);
+
+    // Download mods sequentially to track progress properly
+    for (index, file) in files.iter().enumerate() {
+        // Update progress message with current mod
+        let progress_message =
+            format!("Downloading mod {} of {}", index + 1, num_files);
+        crate::event::emit::emit_loading(
+            loading_bar,
+            0.0, // Don't increment here, just update message
+            Some(&progress_message),
+        )?;
+
+        download_curseforge_mod(file, profile_path, state).await?;
+
+        // Emit progress for each downloaded mod (20% total for mods, divided by number of mods)
+        let mod_progress = 20.0 / num_files as f64;
+        crate::event::emit::emit_loading(
+            loading_bar,
+            mod_progress,
+            Some(&format!("Downloaded mod {} of {}", index + 1, num_files)),
+        )?;
+    }
+
+    Ok(())
+}
+
+/// Download a single mod from CurseForge
+async fn download_curseforge_mod(
+    file: &CurseForgeFile,
+    profile_path: &str,
+    _state: &State,
+) -> crate::Result<()> {
+    // Log the download attempt
+    tracing::info!(
+        "Downloading CurseForge mod: project_id={}, file_id={}",
+        file.project_id,
+        file.file_id
+    );
+
+    // Get profile path and create mods directory first
+    let profile_full_path =
+        crate::api::profile::get_full_path(profile_path).await?;
+    let mods_dir = profile_full_path.join("mods");
+    tokio::fs::create_dir_all(&mods_dir).await?;
+
+    // First, get the file metadata to get the correct filename
+    let metadata_url = format!(
+        "https://www.curseforge.com/api/v1/mods/{}/files/{}",
+        file.project_id, file.file_id
+    );
+
+    tracing::info!("Fetching metadata from: {}", metadata_url);
+
+    let client = reqwest::Client::new();
+    let metadata_response =
+        client.get(&metadata_url).send().await.map_err(|e| {
+            crate::ErrorKind::InputError(format!(
+                "Failed to fetch metadata for mod {}/{}: {}",
+                file.project_id, file.file_id, e
+            ))
+        })?;
+
+    if !metadata_response.status().is_success() {
+        return Err(crate::ErrorKind::InputError(format!(
+            "HTTP error fetching metadata for mod {}/{}: {}",
+            file.project_id,
+            file.file_id,
+            metadata_response.status()
+        ))
+        .into());
+    }
+
+    // Parse the metadata JSON to get the filename
+    let metadata_json: serde_json::Value =
+        metadata_response.json().await.map_err(|e| {
+            crate::ErrorKind::InputError(format!(
+                "Failed to parse metadata JSON for mod {}/{}: {}",
+                file.project_id, file.file_id, e
+            ))
+        })?;
+
+    let original_filename = metadata_json
+        .get("data")
+        .and_then(|data| data.get("fileName"))
+        .and_then(|name| name.as_str())
+        .map(|s| s.to_string())
+        .unwrap_or_else(|| {
+            // Fallback to the old format if API response is unexpected
+            format!("mod_{}_{}.jar", file.project_id, file.file_id)
+        });
+
+    tracing::info!("Original filename: {}", original_filename);
+
+    // Now download the mod using the direct download URL
+    let download_url = format!(
+        "https://www.curseforge.com/api/v1/mods/{}/files/{}/download",
+        file.project_id, file.file_id
+    );
+
+    tracing::info!("Downloading from: {}", download_url);
+
+    let response = client.get(&download_url).send().await.map_err(|e| {
+        crate::ErrorKind::InputError(format!(
+            "Failed to download mod {}/{}: {}",
+            file.project_id, file.file_id, e
+        ))
+    })?;
+
+    if !response.status().is_success() {
+        return Err(crate::ErrorKind::InputError(format!(
+            "HTTP error downloading mod {}/{}: {}",
+            file.project_id,
+            file.file_id,
+            response.status()
+        ))
+        .into());
+    }
+
+    // Write the file with its original name
+    let final_path = mods_dir.join(&original_filename);
+    let bytes = response.bytes().await.map_err(|e| {
+        crate::ErrorKind::InputError(format!(
+            "Failed to read response bytes for mod {}/{}: {}",
+            file.project_id, file.file_id, e
+        ))
+    })?;
+
+    tokio::fs::write(&final_path, &bytes).await.map_err(|e| {
+        crate::ErrorKind::InputError(format!(
+            "Failed to write mod file {:?}: {}",
+            final_path, e
+        ))
+    })?;
+
+    tracing::info!(
+        "Successfully downloaded mod: {} ({} bytes)",
+        original_filename,
+        bytes.len()
+    );
+
+    Ok(())
+}
diff --git a/packages/app-lib/src/api/pack/import/mod.rs b/packages/app-lib/src/api/pack/import/mod.rs
index 1f79d5e5..ff6e91b2 100644
--- a/packages/app-lib/src/api/pack/import/mod.rs
+++ b/packages/app-lib/src/api/pack/import/mod.rs
@@ -19,6 +19,7 @@ use crate::{
 
 pub mod atlauncher;
 pub mod curseforge;
+pub mod curseforge_profile;
 pub mod gdlauncher;
 pub mod mmc;
 
diff --git a/packages/app-lib/src/event/mod.rs b/packages/app-lib/src/event/mod.rs
index 92b7b53f..f635da47 100644
--- a/packages/app-lib/src/event/mod.rs
+++ b/packages/app-lib/src/event/mod.rs
@@ -176,6 +176,9 @@ pub enum LoadingBarType {
         import_location: PathBuf,
         profile_name: String,
     },
+    CurseForgeProfileDownload {
+        profile_name: String,
+    },
     CheckingForUpdates,
     LauncherUpdate {
         version: String,
