Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
To download HTML in a browser, put the markup in a Blob, create a temporary blob: URL with URL.createObjectURL(), and click an anchor whose download attribute contains the suggested filename. If the page lives at a URL, fetch it first, verify response.ok, read the response as text, and pass that text to the same download function.
These are separate jobs: saving HTML you already have is local; retrieving HTML from another origin is subject to Fetch, same-origin rules, and CORS. The browser—not JavaScript—controls the final prompt, save location, and whether a filename suggestion is accepted.
Download HTML that your page already generated
This function accepts an HTML string, creates an HTML Blob, and starts a download without navigating away from the current page.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
function downloadHtml(html, filename = "page.html") {
const blob = new Blob([html], { type: "text/html;charset=utf-8" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
link.click();
// Keep the URL alive long enough for the browser to start the download.
setTimeout(() => URL.revokeObjectURL(url), 30_000);
}
const html = `<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Saved page</title></head>
<body><h1>Hello</h1></body>
</html>`;
downloadHtml(html, "saved-page.html");
The text/html;charset=utf-8 type tells the browser what the Blob contains. The .html extension makes the suggested file type clear to users and operating systems. A Blob URL is temporary and exists only in the browser context that created it.
#1 Best Overall
Download the current document’s markup
If you want the DOM as it currently exists, serialize it before calling the function. This captures the current element tree, not necessarily every original response byte.
const html = "<!doctype html>n" + document.documentElement.outerHTML;
downloadHtml(html, "current-page.html");
Runtime state has limits: a canvas may not serialize its pixels into markup, event listeners are not represented, and resources referenced by relative URLs may resolve differently when the file is opened locally. If you need a self-contained archive, inline or separately save the required CSS, scripts, fonts, and images.
Fetch an HTML page from a URL, then save it
fetch() retrieves the response; it does not itself display a Save dialog. Read the response body as text, then reuse the Blob download path.
async function downloadPage(url, filename = "page.html") {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}`);
}
const html = await response.text();
downloadHtml(html, filename);
}
downloadPage("/reports/weekly.html", "weekly.html")
.catch(error => console.error("Download failed:", error));
For a same-origin URL, this normally works when the server returns the page to your document’s origin. For a cross-origin URL, the remote server must allow your request with an appropriate CORS policy. JavaScript cannot bypass a CORS denial. A request that appears in the Network panel can still be unreadable to page code if the response lacks permission for your origin.
Use the response Blob when you do not need to inspect text
async function downloadResponse(url, filename = "page.html") {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const blob = await response.blob();
const objectUrl = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = objectUrl;
link.download = filename;
link.click();
setTimeout(() => URL.revokeObjectURL(objectUrl), 30_000);
}
This preserves the response’s media type when the server supplies one, although the filename you suggest should still end in .html for an HTML page. Use response.text() when you need to modify, sanitize, or inspect the markup first.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Start the download from a user action
Call download code from a button click or another clear user gesture when possible. Browsers may apply popup or download protections to work started much later from an unrelated timer, especially when the operation opens a new window. A normal anchor download is not a silent arbitrary-path write: the browser chooses its Downloads folder, prompts according to user settings, and does not provide a general overwrite-in-place editor workflow.
document.querySelector("#save").addEventListener("click", () => {
const html = "<!doctype html>n" + document.documentElement.outerHTML;
downloadHtml(html, "page.html");
});
Use a button such as <button id="save" type="button">Download HTML</button>. Do not assume that setting download guarantees a particular folder, prompt, or automatic overwrite.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteHow the download attribute behaves
| Source | Can the anchor download it? | Important constraint |
|---|---|---|
HTML string converted to a blob: URL |
Yes | Revoke the object URL only after the browser has started using it. |
data: URL |
Yes | Large documents can make data URLs inefficient; a Blob is usually clearer. |
| Same-origin URL | Usually | The server’s response headers and browser settings can affect handling. |
| Cross-origin URL used directly in an anchor | Not as a general override | The download attribute is not a way to defeat cross-origin restrictions. |
The attribute is a filename suggestion. A Content-Disposition response header can influence the final filename or whether content is displayed. Browsers can also sanitize illegal filename characters or apply their own download policy.
Object-URL cleanup and memory
Each call to URL.createObjectURL() holds a reference to Blob data until it is released or the document goes away. Revoke URLs that you no longer need:
const objectUrl = URL.createObjectURL(blob);
try {
link.href = objectUrl;
link.click();
} finally {
setTimeout(() => URL.revokeObjectURL(objectUrl), 30_000);
}
Immediate revocation can make the URL unavailable before the download begins. The 30-second delay is a practical cleanup interval, not a universal browser-mandated value; adjust it if your application downloads very large files or targets unusual environments.
Rank #3
When you need an existing file or in-place editing
The anchor pattern creates a new downloaded copy. It does not grant JavaScript permission to select an arbitrary path, overwrite an existing file, or silently edit a file on disk. A File System Access API workflow can let a user pick a file and approve writes where that API is supported, but browser support and permission requirements vary. Verify support in the browsers you target and provide the Blob download as a fallback.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Troubleshooting common failures
“Failed to fetch” or a CORS error
Cause: the destination is cross-origin and has not authorized your page, or the request was blocked by the network.
Fix: use a same-origin endpoint, configure the destination’s CORS response headers, or retrieve the page on a server you control and return it to the browser. A client-side proxy cannot grant permission that the destination withholds.
The file contains an error page instead of the target page
Cause: the server returned an HTTP error or an application login page. Always inspect response.status, response.url, and (when useful) the returned text before saving.
const response = await fetch(url, { redirect: "follow" });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log("Final URL:", response.url);
const html = await response.text();
The downloaded file is empty or incomplete
Cause: the Blob was built from an empty variable, the response was consumed once already, or the object URL was revoked too early.
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
Fix: check html.length, call text() only once per response, and delay revokeObjectURL() until after the click has had time to start.
The page looks different when opened locally
Cause: relative links, blocked local scripts, missing assets, or server-side rendering that occurred only on the original request.
Fix: use absolute asset URLs where appropriate, inline required resources, or keep the page on a web server. Downloading response HTML does not reproduce the browser’s rendered pixels or JavaScript state.
The suggested filename is ignored
Cause: browser policy, an HTTP Content-Disposition header, or filename sanitization.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Fix: provide a simple filename such as page.html, test in the target browser, and treat the value as a suggestion rather than a guarantee.
Best Value
Or skip the browser setup
If your real goal is a clean capture of a live page—not just its raw HTML—ScreenshotNeo makes one API request and returns PNG, JPEG, WebP, or PDF. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
See the full parameter list in the ScreenshotNeo API documentation. A direct request looks like this:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo includes full-page captures with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and page-range controls, custom CSS and JavaScript, clicks, waits, ad/tracker/request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work, which can simplify migration.
Recommended Free Tools
The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to get started.
Frequently Asked Questions
Can JavaScript save HTML directly to a user-selected path?
Not with the ordinary anchor download flow. The browser controls the destination and any prompt. A File System Access API workflow can request user-approved file access where supported, but it is not universal.
Does downloading HTML also save images, CSS, and JavaScript?
No. The downloaded file contains the markup you supplied or fetched. Referenced assets remain separate resources unless you inline them or package them yourself.
Why does fetched HTML differ from what I see in DevTools?
The response may be server HTML before client-side JavaScript runs, while DevTools shows a live DOM after scripts and user interactions. Serialize the DOM for the latter, or use a browser-rendering capture service for a rendered result.
Can I download HTML from any public website in browser JavaScript?
No. Cross-origin Fetch requires the destination’s CORS permission, and a direct cross-origin anchor is not a universal bypass.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

