Skip to content

fix: Use dynamic filename for "Download image" context menu#8408

Merged
mscolnick merged 2 commits intomarimo-team:mainfrom
daizutabi:fix-download-image-format
Feb 22, 2026
Merged

fix: Use dynamic filename for "Download image" context menu#8408
mscolnick merged 2 commits intomarimo-team:mainfrom
daizutabi:fix-download-image-format

Conversation

@daizutabi
Copy link
Contributor

📝 Summary

This pull request addresses an issue where the "Download image" option in the cell output context menu would always save the file with the hardcoded name image.png. This behavior ignored the actual MIME type of the image, causing formats like SVG, GIF, or JPEG to be saved with an incorrect .png extension.

Closes #8405

🔍 Description of Changes

The handle function for the "Download image" action in frontend/src/components/editor/cell/cell-context-menu.tsx has been updated to:

  1. Fetch the image source and create a blob.
  2. Inspect the blob.type property.
  3. Dynamically determine the correct file extension based on the MIME type.
  4. Set the download attribute of the anchor tag to image.[correct_extension].

📋 Checklist

  • I have read the contributor guidelines.
  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Tests have been added for the changes made.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Pull request title is a good summary of the changes - it will be used in the release notes.

@vercel
Copy link

vercel bot commented Feb 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Feb 22, 2026 1:22am

Request Review

Copy link
Collaborator

@Light2Dark Light2Dark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im wondering if its possible to avoid the fetch, just infer from the src string. This way we let the browser handle the download. Can also add tests for this function

claude-assisted

const IMAGE_EXT: Record<string, string> = {
  png: "png",
  jpg: "jpg",
  jpeg: "jpg",
  gif: "gif",
  webp: "webp",
  avif: "avif",
  bmp: "bmp",
  tiff: "tiff",
  svg: "svg",
  "svg+xml": "svg",
};

function getImageExtension(src: string): string {
  const dataUriMatch = src.match(/^data:image\/([^,;]+)/);
  if (dataUriMatch) {
    return IMAGE_EXT[dataUriMatch[1]] ?? "png";
  }

  try {
    const url = new URL(src, window.location.href);
    const ext = url.pathname.split(".").pop()?.toLowerCase() ?? "";
    return IMAGE_EXT[ext] ?? "png";
  } catch {
    return "png";
  }
}


const ext = getImageExtension(imageRightClicked.src);

@daizutabi
Copy link
Contributor Author

Thank you for your feedback. I updated the image download functionality to dynamically infer file extensions from the src string and improved the relevant utility functions and tests.

@mscolnick mscolnick merged commit 5cd8569 into marimo-team:main Feb 22, 2026
26 checks passed
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes an issue where the "Download image" context menu option always saved images with a hardcoded image.png filename, regardless of the actual image format. The fix introduces dynamic filename generation based on the image's MIME type or file extension.

Changes:

  • Added getImageExtension() utility function to infer file extensions from image sources (data URIs or URLs)
  • Updated the "Download image" handler to use dynamic extensions instead of hardcoded .png
  • Added comprehensive test coverage for the new utility function

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
frontend/src/utils/filenames.ts Added getImageExtension() function and IMAGE_EXTENSIONS mapping to extract file extensions from image sources
frontend/src/utils/tests/filenames.test.ts Added comprehensive tests for getImageExtension() covering URLs, data URIs, and edge cases
frontend/src/components/editor/cell/cell-context-menu.tsx Updated "Download image" handler to use dynamic extension based on image source

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 155 to 163
handle: () => {
if (imageRightClicked) {
const link = document.createElement("a");
link.download = "image.png";
link.href = imageRightClicked.src;
const ext = getImageExtension(imageRightClicked.src) || "png";
link.download = `image.${ext}`;
link.click();
}
},
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description states the solution should "Fetch the image source and create a blob" and "Inspect the blob.type property" to determine the extension. However, the implementation uses URL/data URI parsing instead, without fetching.

While this is a discrepancy from the described approach, the current implementation may actually be more appropriate because:

  1. Fetching adds latency and memory overhead
  2. It could fail for CORS-restricted images
  3. The browser can download directly from the href without fetching

However, this approach has a limitation: URLs without file extensions (e.g., /api/image/123) will fall back to .png even if the actual content type is different. Consider whether such URLs are expected in marimo. If they are, the blob.type approach would be more reliable. If not, the current implementation is acceptable and more efficient.

Copilot uses AI. Check for mistakes.
const IMAGE_EXTENSIONS: Record<string, string> = {
png: "png",
jpg: "jpg",
jpeg: "jpeg",
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IMAGE_EXTENSIONS mapping has separate entries for "jpg" and "jpeg", both mapping to their respective values. However, this means a MIME type of "image/jpeg" will map to extension "jpeg", while a URL with ".jpg" will map to extension "jpg". This creates inconsistency in the downloaded filenames.

Consider normalizing both "jpg" and "jpeg" to the same extension (preferably "jpg" as it's more commonly used) to ensure consistent behavior regardless of whether the extension comes from a data URI MIME type or a URL file extension.

Suggested change
jpeg: "jpeg",
jpeg: "jpg",

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +62
it("should return correct extensions for common image URLs", () => {
expect(getImageExtension("https://example.com/image.png")).toBe("png");
expect(getImageExtension("https://example.com/image.jpeg")).toBe("jpeg");
expect(getImageExtension("https://example.com/image.JPEG?param=1")).toBe(
"jpeg",
);
expect(getImageExtension("https://example.com/image.gif")).toBe("gif");
expect(getImageExtension("https://example.com/image.svg")).toBe("svg");
expect(getImageExtension("https://example.com/image.webp")).toBe("webp");
expect(getImageExtension("https://example.com/image.avif")).toBe("avif");
expect(getImageExtension("https://example.com/image.bmp")).toBe("bmp");
expect(getImageExtension("https://example.com/image.tiff")).toBe("tiff");
});
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests are missing coverage for URLs with ".jpg" extension. While "jpeg" is tested for both URLs and data URIs, "jpg" is defined in IMAGE_EXTENSIONS but not validated in tests. Consider adding a test case for URLs with ".jpg" extension to ensure this mapping works correctly.

Copilot uses AI. Check for mistakes.
@daizutabi daizutabi deleted the fix-download-image-format branch February 22, 2026 23:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Download image context menu uses hardcoded filename

4 participants