diff --git a/web/src/lib/components/shared-components/map/map.svelte b/web/src/lib/components/shared-components/map/map.svelte
index 5742799409..bf66018eb6 100644
--- a/web/src/lib/components/shared-components/map/map.svelte
+++ b/web/src/lib/components/shared-components/map/map.svelte
@@ -196,7 +196,7 @@
   }
 
   const handleSettingsClick = async () => {
-    const settings = await modalManager.open(MapSettingsModal, { settings: { ...$mapSettings } });
+    const settings = await modalManager.show(MapSettingsModal, { settings: { ...$mapSettings } });
     if (settings) {
       const shouldUpdate = !isEqual(omit(settings, 'allowDarkMode'), omit($mapSettings, 'allowDarkMode'));
       $mapSettings = settings;
diff --git a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
index 915b041d4e..8cf2fb9dfc 100644
--- a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
+++ b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
@@ -132,7 +132,7 @@
             variant="ghost"
             size="medium"
             icon={mdiHelpCircleOutline}
-            onclick={() => info && modalManager.open(HelpAndFeedbackModal, { info })}
+            onclick={() => info && modalManager.show(HelpAndFeedbackModal, { info })}
             aria-label={$t('support_and_feedback')}
           />
         </div>
diff --git a/web/src/lib/components/shared-components/side-bar/purchase-info.svelte b/web/src/lib/components/shared-components/side-bar/purchase-info.svelte
index fe48a68009..aafb430046 100644
--- a/web/src/lib/components/shared-components/side-bar/purchase-info.svelte
+++ b/web/src/lib/components/shared-components/side-bar/purchase-info.svelte
@@ -28,7 +28,7 @@
   const { isPurchased } = purchaseStore;
 
   const openPurchaseModal = async () => {
-    await modalManager.open(PurchaseModal);
+    await modalManager.show(PurchaseModal, {});
     showMessage = false;
   };
 
diff --git a/web/src/lib/components/shared-components/side-bar/server-status.svelte b/web/src/lib/components/shared-components/side-bar/server-status.svelte
index 665decc44f..0a9f3d9d8c 100644
--- a/web/src/lib/components/shared-components/side-bar/server-status.svelte
+++ b/web/src/lib/components/shared-components/side-bar/server-status.svelte
@@ -56,7 +56,7 @@
     {#if $connected && version}
       <button
         type="button"
-        onclick={() => info && modalManager.open(ServerAboutModal, { versions, info })}
+        onclick={() => info && modalManager.show(ServerAboutModal, { versions, info })}
         class="dark:text-immich-gray flex gap-1"
       >
         {#if isMain}
diff --git a/web/src/lib/components/user-settings-page/partner-settings.svelte b/web/src/lib/components/user-settings-page/partner-settings.svelte
index 9d187b6adf..b18390e28f 100644
--- a/web/src/lib/components/user-settings-page/partner-settings.svelte
+++ b/web/src/lib/components/user-settings-page/partner-settings.svelte
@@ -99,7 +99,7 @@
   };
 
   const handleCreatePartners = async () => {
-    const users = await modalManager.open(PartnerSelectionModal, { user });
+    const users = await modalManager.show(PartnerSelectionModal, { user });
 
     if (!users) {
       return;
diff --git a/web/src/lib/components/user-settings-page/user-api-key-list.svelte b/web/src/lib/components/user-settings-page/user-api-key-list.svelte
index 823f5a0a96..ab9ffb294c 100644
--- a/web/src/lib/components/user-settings-page/user-api-key-list.svelte
+++ b/web/src/lib/components/user-settings-page/user-api-key-list.svelte
@@ -37,7 +37,7 @@
   }
 
   const handleCreate = async () => {
-    const result = await modalManager.open(ApiKeyModal, {
+    const result = await modalManager.show(ApiKeyModal, {
       title: $t('new_api_key'),
       apiKey: { name: 'API Key' },
       submitText: $t('create'),
@@ -55,7 +55,7 @@
         },
       });
 
-      await modalManager.open(ApiKeySecretModal, { secret });
+      await modalManager.show(ApiKeySecretModal, { secret });
     } catch (error) {
       handleError(error, $t('errors.unable_to_create_api_key'));
     } finally {
@@ -64,7 +64,7 @@
   };
 
   const handleUpdate = async (key: ApiKeyResponseDto) => {
-    const result = await modalManager.open(ApiKeyModal, {
+    const result = await modalManager.show(ApiKeyModal, {
       title: $t('api_key'),
       submitText: $t('save'),
       apiKey: key,
diff --git a/web/src/lib/managers/modal-manager.svelte.ts b/web/src/lib/managers/modal-manager.svelte.ts
index 055df14502..12f9224018 100644
--- a/web/src/lib/managers/modal-manager.svelte.ts
+++ b/web/src/lib/managers/modal-manager.svelte.ts
@@ -1,19 +1,20 @@
 import ConfirmDialog from '$lib/components/shared-components/dialog/confirm-dialog.svelte';
 import { mount, unmount, type Component, type ComponentProps } from 'svelte';
 
-type OnCloseData<T> = T extends { onClose: (data: infer R) => void | Promise<void> } ? R : never;
+type OnCloseData<T> = T extends { onClose: (data?: infer R) => void } ? R : never;
+type ExtendsEmptyObject<T> = keyof T extends never ? Record<string, never> : T;
 
 class ModalManager {
-  open<T = { onClose: (data: unknown) => void }, K = OnCloseData<T>>(
-    Component: Component<{ onClose: T }>,
-    props?: Record<string, never>,
-  ): Promise<K>;
-  open<T extends object, K = OnCloseData<T>>(Component: Component<T>, props: Omit<T, 'onClose'>): Promise<K>;
-  open<T extends object, K = OnCloseData<T>>(Component: Component<T>, props?: Omit<T, 'onClose'>) {
-    return new Promise<K>((resolve) => {
-      let modal: object = {};
+  show<T extends object>(Component: Component<T>, props: ExtendsEmptyObject<Omit<T, 'onClose'>>) {
+    return this.open(Component, props).onClose;
+  }
 
-      const onClose = async (data: K) => {
+  open<T extends object, K = OnCloseData<T>>(Component: Component<T>, props: ExtendsEmptyObject<Omit<T, 'onClose'>>) {
+    let modal: object = {};
+    let onClose: () => Promise<void>;
+
+    const deferred = new Promise<K | undefined>((resolve) => {
+      onClose = async (data?: K) => {
         await unmount(modal);
         resolve(data);
       };
@@ -21,15 +22,20 @@ class ModalManager {
       modal = mount(Component, {
         target: document.body,
         props: {
-          ...((props ?? {}) as T),
+          ...(props as T),
           onClose,
         },
       });
     });
+
+    return {
+      onClose: deferred,
+      close: () => onClose(),
+    };
   }
 
   openDialog(options: Omit<ComponentProps<typeof ConfirmDialog>, 'onClose'>) {
-    return this.open(ConfirmDialog, options);
+    return this.show(ConfirmDialog, options);
   }
 }
 
diff --git a/web/src/routes/admin/jobs-status/+page.svelte b/web/src/routes/admin/jobs-status/+page.svelte
index c5327a4271..6636d748cf 100644
--- a/web/src/routes/admin/jobs-status/+page.svelte
+++ b/web/src/routes/admin/jobs-status/+page.svelte
@@ -39,7 +39,7 @@
     <HStack gap={0}>
       <Button
         leadingIcon={mdiPlus}
-        onclick={() => modalManager.open(JobCreateModal)}
+        onclick={() => modalManager.show(JobCreateModal, {})}
         size="small"
         variant="ghost"
         color="secondary"
diff --git a/web/src/routes/admin/user-management/+page.svelte b/web/src/routes/admin/user-management/+page.svelte
index 8ac3793046..dcf82bd10d 100644
--- a/web/src/routes/admin/user-management/+page.svelte
+++ b/web/src/routes/admin/user-management/+page.svelte
@@ -59,15 +59,15 @@
   };
 
   const handleCreate = async () => {
-    await modalManager.open(UserCreateModal);
+    await modalManager.show(UserCreateModal, {});
     await refresh();
   };
 
   const handleEdit = async (dto: UserAdminResponseDto) => {
-    const result = await modalManager.open(UserEditModal, { user: dto, canResetPassword: dto.id !== $user.id });
+    const result = await modalManager.show(UserEditModal, { user: dto, canResetPassword: dto.id !== $user.id });
     switch (result?.action) {
       case 'resetPassword': {
-        await modalManager.open(PasswordResetSuccess, { newPassword: result.data });
+        await modalManager.show(PasswordResetSuccess, { newPassword: result.data });
         break;
       }
       case 'update': {
@@ -78,14 +78,14 @@
   };
 
   const handleDelete = async (user: UserAdminResponseDto) => {
-    const result = await modalManager.open(UserDeleteConfirmModal, { user });
+    const result = await modalManager.show(UserDeleteConfirmModal, { user });
     if (result) {
       await refresh();
     }
   };
 
   const handleRestore = async (user: UserAdminResponseDto) => {
-    const result = await modalManager.open(UserRestoreConfirmModal, { user });
+    const result = await modalManager.show(UserRestoreConfirmModal, { user });
     if (result) {
       await refresh();
     }