You generated a report in the browser, captured a drawing from a canvas, or fetched a binary from an API and now you want to let users download it. This comes up constantly in dev threads, and the solutions vary by environment and file type.
Hi folks,
What is the simplest, cross-platform way to handle file downloads and saving on the web and server side? Specifically, how to save a file in JavaScript? I need to save JSON, text, images from a canvas, and also files returned by fetch. Bonus points for Node.js tips and any guidance on choosing a good file format or handling large media.
JavaScript supports saving files in two broad contexts: in the browser to the user’s device and on the server with Node.js. Let’s cover some of the most reliable patterns.
This approach works widely and requires no libraries. Create a Blob, generate an object URL, and click a hidden anchor with the download attribute.
// Save JSON as a file
const data = { name: "Acme", createdAt: Date.now() };
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "data.json"; // file name for the user
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);Code language: JavaScript (javascript)
The File System Access API allows writing directly to user-selected files. It is available in Chromium-based browsers in secure contexts (https).
async function saveTextFile() {
// Prompt the user for a file location and name
const handle = await window.showSaveFilePicker({
suggestedName: "report.txt",
types: [{ description: "Text Files", accept: { "text/plain": [".txt"] } }]
});
const writable = await handle.createWritable();
await writable.write("Hello from JS!");
await writable.close();
}
// Fallback: if showSaveFilePicker is not available, use the Blob + a[download] patternCode language: JavaScript (javascript)
If your API returns a binary (e.g., PDF or image), turn the response into a Blob and use the same download approach.
async function downloadFromApi() {
const res = await fetch("/api/report.pdf");
if (!res.ok) throw new Error("Network error");
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "report.pdf";
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}Code language: JavaScript (javascript)
For a canvas element, use toBlob and then download.
function saveCanvas(canvas) {
canvas.toBlob(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "drawing.png";
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}, "image/png", 0.92);
}Code language: JavaScript (javascript)
On the server, use the filesystem APIs. For text or small buffers, writeFile is enough. For large downloads, stream to disk.
// Node 18+ has global fetch
import { promises as fs } from "fs";
import { createWriteStream } from "fs";
import { pipeline } from "stream/promises";
// Simple write
await fs.writeFile("data.json", JSON.stringify({ ok: true }, null, 2));
// Stream a large file from a URL to disk
const res = await fetch("https://example.com/large-video.mp4");
if (!res.ok) throw new Error("Failed to download");
await pipeline(res.body, createWriteStream("video.mp4"));Code language: JavaScript (javascript)
- Use the correct MIME type and file extension. For images, decide between formats like JPG and PNG based on your needs. See JPEG vs PNG for trade-offs.
- For browser downloads, a meaningful a.download name improves UX.
- For server responses, set Content-Disposition to attachment to suggest a file name and trigger download.
If your app needs to deliver or transform large files or images at scale, you can store and serve your media assets from Cloudinary and still use the same JavaScript download techniques.
- Build stable public URLs for assets and transformations.
- Transform on the fly and download the result. For example, generate a resized or reformatted URL and then trigger a download with an anchor tag.
- If you need a quick pre-download conversion step, try a simple utility like image to JPG to standardize assets before saving.
// Example: client-side download of a transformed image URL
const transformedUrl = "https://res.cloudinary.com/demo/image/upload/w_1200,q_auto,f_auto/sample.jpg";
const a = document.createElement("a");
a.href = transformedUrl;
a.download = "photo.jpg";
document.body.appendChild(a);
a.click();
a.remove();Code language: JavaScript (javascript)
- Unsupported APIs: Always feature-detect showSaveFilePicker and fall back to Blob download.
- CORS: When fetching assets from another origin, ensure appropriate CORS headers are set.
- Memory: Prefer streaming for large files in Node.js to avoid loading the entire file into memory.
- Browser: Use Blob + a[download] for universal downloads; use File System Access API where available.
- Canvas: toBlob then download.
- Node.js: use fs.writeFile for small payloads and streaming for large files.
- Choose the right format and naming; consider JPG vs PNG trade-offs for images.
- For scalable delivery and transformations, host and transform via Cloudinary and download the resulting URL.
Ready to simplify file delivery and media handling in your app? Create your free Cloudinary account and start optimizing, transforming, and delivering assets at scale.