diff --git a/src/frontend/src/components/details/DetailsImage.tsx b/src/frontend/src/components/details/DetailsImage.tsx
index 6b5999af6f..c4d3ccc630 100644
--- a/src/frontend/src/components/details/DetailsImage.tsx
+++ b/src/frontend/src/components/details/DetailsImage.tsx
@@ -19,6 +19,8 @@ import { api } from '../../App';
 import { UserRoles } from '../../enums/Roles';
 import { cancelEvent } from '../../functions/events';
 import { InvenTreeIcon } from '../../functions/icons';
+import { useEditApiFormModal } from '../../hooks/UseForm';
+import { useGlobalSettingsState } from '../../states/SettingsState';
 import { useUserState } from '../../states/UserState';
 import { PartThumbTable } from '../../tables/part/PartThumbTable';
 import { vars } from '../../theme';
@@ -42,11 +44,13 @@ export type DetailImageProps = {
  * Actions for Detail Images.
  * If true, the button type will be visible
  * @param {boolean} selectExisting - PART ONLY. Allows selecting existing images as part image
+ * @param {boolean} downloadImage - Allows downloading image from a remote URL
  * @param {boolean} uploadFile - Allows uploading a new image
  * @param {boolean} deleteFile - Allows deleting the current image
  */
 export type DetailImageButtonProps = {
   selectExisting?: boolean;
+  downloadImage?: boolean;
   uploadFile?: boolean;
   deleteFile?: boolean;
 };
@@ -245,7 +249,8 @@ function ImageActionButtons({
   apiPath,
   hasImage,
   pk,
-  setImage
+  setImage,
+  downloadImage
 }: Readonly<{
   actions?: DetailImageButtonProps;
   visible: boolean;
@@ -253,7 +258,10 @@ function ImageActionButtons({
   hasImage: boolean;
   pk: string;
   setImage: (image: string) => void;
+  downloadImage: () => void;
 }>) {
+  const globalSettings = useGlobalSettingsState();
+
   return (
     <>
       {visible && (
@@ -284,6 +292,25 @@ function ImageActionButtons({
               }}
             />
           )}
+          {actions.downloadImage &&
+            globalSettings.isSet('INVENTREE_DOWNLOAD_FROM_URL') && (
+              <ActionButton
+                icon={
+                  <InvenTreeIcon
+                    icon="download"
+                    iconProps={{ color: 'white' }}
+                  />
+                }
+                tooltip={t`Download remote image`}
+                variant="outline"
+                size="lg"
+                tooltipAlignment="top"
+                onClick={(event: any) => {
+                  cancelEvent(event);
+                  downloadImage();
+                }}
+              />
+            )}
           {actions.uploadFile && (
             <ActionButton
               icon={
@@ -341,6 +368,21 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
 
   const permissions = useUserState();
 
+  const downloadImage = useEditApiFormModal({
+    url: props.apiPath,
+    title: t`Download Image`,
+    fields: {
+      remote_image: {}
+    },
+    timeout: 10000,
+    successMessage: t`Image downloaded successfully`,
+    onFormSuccess: (response: any) => {
+      if (response.image) {
+        setAndRefresh(response.image);
+      }
+    }
+  });
+
   const hasOverlay: boolean = useMemo(() => {
     return (
       props.imageActions?.selectExisting ||
@@ -359,27 +401,33 @@ export function DetailsImage(props: Readonly<DetailImageProps>) {
   };
 
   return (
-    <AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1} pos="relative">
-      <>
-        <ApiImage
-          src={img}
-          mah={IMAGE_DIMENSION}
-          maw={IMAGE_DIMENSION}
-          onClick={expandImage}
-        />
-        {permissions.hasChangeRole(props.appRole) && hasOverlay && hovered && (
-          <Overlay color="black" opacity={0.8} onClick={expandImage}>
-            <ImageActionButtons
-              visible={hovered}
-              actions={props.imageActions}
-              apiPath={props.apiPath}
-              hasImage={props.src ? true : false}
-              pk={props.pk}
-              setImage={setAndRefresh}
-            />
-          </Overlay>
-        )}
-      </>
-    </AspectRatio>
+    <>
+      {downloadImage.modal}
+      <AspectRatio ref={ref} maw={IMAGE_DIMENSION} ratio={1} pos="relative">
+        <>
+          <ApiImage
+            src={img}
+            mah={IMAGE_DIMENSION}
+            maw={IMAGE_DIMENSION}
+            onClick={expandImage}
+          />
+          {permissions.hasChangeRole(props.appRole) &&
+            hasOverlay &&
+            hovered && (
+              <Overlay color="black" opacity={0.8} onClick={expandImage}>
+                <ImageActionButtons
+                  visible={hovered}
+                  actions={props.imageActions}
+                  apiPath={props.apiPath}
+                  hasImage={props.src ? true : false}
+                  pk={props.pk}
+                  setImage={setAndRefresh}
+                  downloadImage={downloadImage.open}
+                />
+              </Overlay>
+            )}
+        </>
+      </AspectRatio>
+    </>
   );
 }
diff --git a/src/frontend/src/components/forms/ApiForm.tsx b/src/frontend/src/components/forms/ApiForm.tsx
index 6f733ba89b..d7dbffd1a9 100644
--- a/src/frontend/src/components/forms/ApiForm.tsx
+++ b/src/frontend/src/components/forms/ApiForm.tsx
@@ -33,7 +33,10 @@ import {
   extractAvailableFields,
   mapFields
 } from '../../functions/forms';
-import { invalidResponse } from '../../functions/notifications';
+import {
+  invalidResponse,
+  showTimeoutNotification
+} from '../../functions/notifications';
 import { getDetailUrl } from '../../functions/urls';
 import { TableState } from '../../hooks/UseTable';
 import { PathParams } from '../../states/ApiState';
@@ -540,7 +543,7 @@ export function ApiForm({
               break;
           }
         } else {
-          invalidResponse(0);
+          showTimeoutNotification();
           props.onFormError?.();
         }
 
diff --git a/src/frontend/src/functions/icons.tsx b/src/frontend/src/functions/icons.tsx
index caa5d75f42..192bdbaf62 100644
--- a/src/frontend/src/functions/icons.tsx
+++ b/src/frontend/src/functions/icons.tsx
@@ -31,6 +31,7 @@ import {
   IconEdit,
   IconExclamationCircle,
   IconExternalLink,
+  IconFileDownload,
   IconFileUpload,
   IconFlag,
   IconFlagShare,
@@ -143,6 +144,7 @@ const icons = {
   notes: IconNotes,
   photo: IconPhoto,
   upload: IconFileUpload,
+  download: IconFileDownload,
   reject: IconX,
   refresh: IconRefresh,
   select_image: IconGridDots,
diff --git a/src/frontend/src/functions/notifications.tsx b/src/frontend/src/functions/notifications.tsx
index 7b36efbcdc..2ed72f34d5 100644
--- a/src/frontend/src/functions/notifications.tsx
+++ b/src/frontend/src/functions/notifications.tsx
@@ -39,6 +39,17 @@ export function invalidResponse(returnCode: number) {
   });
 }
 
+/**
+ * Display a notification on timeout
+ */
+export function showTimeoutNotification() {
+  notifications.show({
+    title: t`Timeout`,
+    message: t`The request timed out`,
+    color: 'red'
+  });
+}
+
 /*
  * Display a login / logout notification message.
  * Any existing login notification(s) will be hidden.
diff --git a/src/frontend/src/pages/company/CompanyDetail.tsx b/src/frontend/src/pages/company/CompanyDetail.tsx
index e21b9dd757..0dcf720d22 100644
--- a/src/frontend/src/pages/company/CompanyDetail.tsx
+++ b/src/frontend/src/pages/company/CompanyDetail.tsx
@@ -153,6 +153,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
               refresh={refreshInstance}
               imageActions={{
                 uploadFile: true,
+                downloadImage: true,
                 deleteFile: true
               }}
             />
diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx
index 9d095c9c49..7123aac686 100644
--- a/src/frontend/src/pages/part/PartDetail.tsx
+++ b/src/frontend/src/pages/part/PartDetail.tsx
@@ -511,6 +511,7 @@ export default function PartDetail() {
               appRole={UserRoles.part}
               imageActions={{
                 selectExisting: true,
+                downloadImage: true,
                 uploadFile: true,
                 deleteFile: true
               }}
@@ -531,7 +532,7 @@ export default function PartDetail() {
     ) : (
       <Skeleton />
     );
-  }, [part, instanceQuery]);
+  }, [globalSettings, part, instanceQuery]);
 
   // Part data panels (recalculate when part data changes)
   const partPanels: PanelType[] = useMemo(() => {
diff --git a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
index 618d43efbf..bee9b47b04 100644
--- a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
+++ b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
@@ -95,6 +95,7 @@ export default function ReturnOrderDetail() {
         type: 'text',
         name: 'customer_reference',
         label: t`Customer Reference`,
+        icon: 'customer',
         copy: true,
         hidden: !order.customer_reference
       },