When click (embedded) button[aria-label=“Download”] of generated image it will not download,but show the generated picture!!!
Must remove in URL everything untill first “file=”
When click (embedded) button[aria-label=“Download”] of generated image it will not download,but show the generated picture!!!
Must remove in URL everything untill first “file=”
iframe one?
This behavior is expected once you combine three things: (1) Hugging Face Spaces are iframe-embedded, (2) your “Download” UI is ultimately an HTML link, and (3) the image URL becomes effectively cross-origin because it is served through a proxy chain.
Your URL shows the whole chain:
publicspace.hf.space
/gradio_api/proxy= https://privatespace.hf.space/gradio_api/file= https://privatespace.hf.space/gradio_api/file=/tmp/gradio/.../image.webp
That “proxy= … file= … file=” nesting is the key smell: the download link is not a clean “same-origin downloadable file”. It is a proxied, rewritten asset URL.
Below are the main causes, how to verify each one quickly, and the fixes that actually stick.
When you open a Space via https://huggingface.co/spaces/<user>/<space>, Hugging Face embeds the real app (the https://*.hf.space/ origin) inside an iframe. Hugging Face documents this explicitly. (Hugging Face)
So even before you add a private Space, you are already in an iframe context.
Hugging Face’s embed doc states that to embed a Space, it must be public. That is why many people do the “public wrapper Space → private Space” pattern. (Hugging Face)
<a download> for cross-origin URLsMost “download icons” in web UIs are implemented as an <a href="..."> link with a download attribute, or equivalent logic.
Modern browsers intentionally ignore the download attribute in cross-origin situations to prevent data leakage and abuse. Chrome formalized this in Chrome 65. (Chrome for Developers)
So if the final resource is treated as cross-origin (and in your case it is, because it ultimately points to the private Space origin or a proxy of it), the browser is allowed to do the “safe” thing: navigate to the image (preview it) instead of downloading.
This is also exactly what the Gradio team calls out in a related bug: download buttons fail in iframes because the embedded app and the parent have different origins and <a download> does not work across them. (GitHub)
publicspace.hf.spaceprivatespace.hf.spaceDifferent hostnames means different origins. Same-origin policy is strict about this. (MDN Web Docs)
Even if the UI tries to force download, the response often comes back as:
Content-Type: image/webpContent-Disposition: attachmentBrowsers will typically render images inline in a new tab when you navigate to such a URL.
In a public→private setup, it is common that the outer proxy route behaves like a normal image fetch, not like a “download endpoint”.
This category of “proxy breaks file access / download” issues exists in Gradio generally (proxy environments change URL handling and file access rules). (GitHub)
gr.load() + private Space outputs are a known rough edgeMultiple reports match the broader pattern: the UI loads, inference runs, but outputs that are files/images fail or behave oddly when the loaded Space is private.
Examples:
Your symptom is slightly different (preview instead of download), but it is the same underlying theme: file outputs are the fragile part when you cross Space visibility boundaries.
Your URL includes:
.../file=https://privatespace.../file=/tmp/gradio/...That means something treated an already-constructed file URL as a string and then wrapped it again.
This can happen when:
You can usually confirm this by inspecting the JSON returned by the predict call and seeing whether the “image” is a structured file object vs a raw URL string.
Do these three checks once. They remove guesswork.
<a> tag and see its href.Click download, then inspect the response headers for that request.
If you do not see something like Content-Disposition: attachment, the browser will happily preview it.
(And in cross-origin situations, even having download on the link often won’t help.)
When you manually strip to the first file=, you bypass one wrapper. If the behavior changes, that is consistent with “outer proxy URL is the problem”.
I’m listing these in the order that tends to be most robust on Spaces.
Instead of trying to download the private Space’s /tmp/gradio/... file through a proxy URL, do this:
gr.DownloadButton.Why it works:
publicspace.hf.space).<a download> restrictions. (Chrome for Developers)proxy=...file=...file=... URLs.Gradio explicitly provides DownloadButton for this use case. (Gradio)
Also, use Gradio’s Python client to call remote apps cleanly. (Gradio)
If you can add a small FastAPI route (common in Docker Spaces), you can make:
/download?path=<id>
and respond with Content-Disposition: attachment.
This approach avoids relying on the browser’s cross-origin download attribute behavior. Chrome’s change is specifically about ignoring <a download> cross-origin, not about forbidding downloads when servers mark attachments. (Chrome for Developers)
If you control the private Space app:
DownloadButtonThis avoids /gradio_api/file=/tmp/gradio/... entirely.
If you embed *.hf.space in an iframe on your own page and you set a restrictive sandbox, downloads can be blocked unless you allow them. This is a separate but common pitfall.
(If you are only using the Hugging Face Space page, you typically do not control this, so Solutions 1–3 are the practical ones.)
/tmp/gradio/... URLs across machines or replicasEven when not private, /tmp is ephemeral and can break across restarts. Keep downloads stable by re-hosting or regenerating.
gr.File or “raw URL string” outputs behave the same across versionsGradio’s file access and serving rules tightened over time, and there are reports of file serving changes and breakages behind proxies and in newer versions. (Gradio)
The volume of threads on this pattern should be treated as a warning sign. (Hugging Face Forums)
Include:
the exact URL shape showing proxy=...file=...file=...
Gradio version on both Spaces
DevTools screenshots of:
Content-Disposition)a minimal pair of Spaces: one public wrapper, one private generator
Reference the existing Gradio iframe download issue as related context. (GitHub)
DownloadButton. (Gradio)file=” indicates double URL rewriting, often from returning URLs instead of file objects.Uau, so many text and words without simple and fast solution (exactly AI way)
Solution (human=quick and fast and clean): Just replace gr.Image(…) with gr.Gallery(…)!
Anyway, thank you for your effort
Sorry for AI gen answer. Oh my. I had no idea that solution existed…![]()
Since I didn’t understand why it worked, I’ll add a note for future reference…
I see…
Yes. Swapping the output from gr.Image(...) to gr.Gallery(...) is a real, quick workaround for this specific “embedded public Space → private Space” download behavior, because Gallery’s download is implemented as a dedicated “download selected image” toolbar action. The Gallery docs explicitly describe that built-in download button. (Gradio)
import gradio as gr
def run(prompt):
img_path = generate_somehow(prompt) # filepath, PIL image, or numpy
return [img_path] # Gallery expects a list
with gr.Blocks() as demo:
prompt = gr.Textbox()
out = gr.Gallery(columns=1, buttons=["download", "fullscreen"]) # download on toolbar
gr.Button("Go").click(run, prompt, out)
demo.launch()
Notes:
columns=1.[(img, "caption")] (still a list).Gradio has an explicit bug report that download buttons don’t work in iframes, tied to how browsers handle downloads and origins. (GitHub)
Hugging Face Spaces are commonly consumed via embeds, and “public wrapper → private Space” introduces extra proxying and URL rewriting, which makes the download path more fragile.
Historically, Gallery download behavior was added and then fixed multiple times:
Gallery. (Gradio)So depending on your Gradio version, Gallery may be “on the newer, patched code path” while Image still hits the behavior you are seeing.
If you are on older Gradio builds, Gallery download itself could be broken (for example downloading the parent HTML, or behaving differently with captions). (GitHub)
If your “Gallery fix” ever stops working, this is usually why.
Use a dedicated DownloadButton and hand it a local filepath you control in the public Space. That avoids relying on embedded toolbar download behavior. (Gradio)
This is the “boring but reliable” route when proxies and private Spaces are involved.
I was wrong - the major problem will not resolve with replacing of gr.Image() with gr.Gallery and when press embedded download button (button[aria-label=“Download”]) of just generated picture (in Image or Gallery) it will not download actual file, but will open the image in the current window and you will lose all generated images (will reset UI)
The only working solution is to use window.open(img.src, “_blank”);
I have made many tries like hidden form submit; fetch(img.src)..then(blob =>; URL.createObjectURL(blob); canvas.toBlob(blob => …
without any success = can’t bypass browser security …
Again, thank you John6666 for your detailed info