The file:// protocol: what works differently when a page is opened from disk

Open an HTML file from Downloads and the address bar shows file://. The page is not on the web: nobody else can reach it, a script cannot fetch a file next to it, some browser features are withheld, and every stylesheet and image must be sitting beside it. Serve the same file at https:// and all of that goes away.

The file:// protocol is the address scheme a browser uses when it opens a page straight from a disk rather than from a server, and a page opened that way behaves differently in ways that look like bugs.

The markup. The highlighted line is the part this term is about.
The markup. The highlighted line is the part this term is about.

It has no normal origin, so scripts that fetch a neighbouring file are refused; some features, sharing, service workers, parts of storage, are unavailable; every stylesheet, font and image must be physically next to the file; and the page is reachable from exactly one machine.

This guide covers why file pages are restricted, what does not work, and why the fix is an address rather than a workaround.

file:///C:/Users/you/Desktop/report.html
file:///Users/you/Desktop/report.html

That in the address bar is the browser saying: this page is not on the web.

Why file:// pages are restricted

Web pages are kept apart by origin — scheme, host and port. A file on a disk has no host, so it has no origin in the usual sense. Every rule built on origins therefore has no clear answer for it.

file:// is the address a browser shows for a page opened from disk. The page is not on the web: only this machine can reach it, some features are withheld, scripts cannot fetch neighbours, and every referenced file must be present.
file:// is the address a browser shows for a page opened from disk. The page is not on the web: only this machine can reach it, some features are withheld, scripts cannot fetch neighbours, and every referenced file must be present.

Browsers resolved that by treating local pages as their own opaque case and withholding capabilities rather than guessing. See CORS for what origins are protecting.

file:// on your own disk ✗ Only you can open it ✗ Path breaks when moved ✗ No preview card when shared ✗ Some browser features stay switched off https:// on a hosted page ✓ Anyone with the link opens it ✓ Address is stable ✓ Preview card in chat apps ✓ Full browser features
A file on a disk versus a page at an address. Only the second has an origin, and only the second has readers.

What does not work

Feature Over file:// Why
fetch() of a local file Blocked Would let any downloaded page read your disk
ES modules (type="module") Blocked Module loading is origin-based
Service workers Blocked Require a secure origin
localStorage Varies No origin to key against
Cookies Effectively not Same reason
Clipboard, camera, location Blocked Require a secure origin
Inline scripts and styles Work No request involved

The first row is the one that catches people: a page that loads its data from a neighbouring data.json works when served and is blocked when opened from a disk.

What does work

Quite a lot, which is why single-file pages are a reasonable format:

A self-contained page avoids every restriction above, because it makes no requests at all.

Serving a folder locally, in one line

cd /path/to/your/folder
python3 -m http.server 8000

Then open http://localhost:8000. Now the page has an origin and every restriction lifts.

This is the fastest way to test whether file:// was your problem: if the page works on localhost and not from the disk, it was.

Why your recipients cannot use the file at all

Beyond the technical restrictions, a file:// page has a more basic limitation: there is no address to give anybody. The path exists on your machine only.

Which produces the familiar set of problems:

All four come from the same fact. It is a file, and the reader needed a page.

The transition

file:// on your own disk ✗ Only you can open it ✗ Path breaks when moved ✗ No preview card when shared ✗ Some browser features stay switched off https:// on a hosted page ✓ Anyone with the link opens it ✓ Address is stable ✓ Preview card in chat apps ✓ Full browser features
A file on a disk versus a page at an address.

Serving the same HTML over https lifts every restriction and gives you something to send. Publishing the page is the whole of it — the file does not change at all.

Why the restrictions exist

A page opened from disk can, in principle, read other files on the same disk, which a web page must never be able to do.

Browsers responded by giving file pages an origin that matches nothing, including other file pages, so a script in one cannot read another, and by withholding the features that assume a real origin.

The side effect is that a perfectly ordinary page with a fetch('data.json') works from a server and fails from a double-click, with a security error that looks like a bug in the page.

The phone case

On a phone, file:// barely exists for a user: there is no double-click, the browser is registered for web addresses, and a downloaded HTML file lands in a file manager with no obvious way to open it. Opening an HTML file on a phone covers what does work, and the answer that works everywhere is the same as on a desktop: give the page an address.

Getting a page off file://: 4 steps

  1. Recognise the symptom. Works in the editor's preview, fails when double-clicked, and the address bar starts with file://.
  2. Make the page self-contained. Styles inside, images embedded or at https:// addresses, no script that fetches a neighbouring file. The checklist.
  3. Test in a viewer that renders from a string. The HTML viewer draws the file without a file:// origin, which is closer to how the reader will see it.
  4. Serve it at an address. A NOS document is https:// for everyone: Share, then Share link, then Create link. That removes every file:// restriction at once.

Questions people ask

What does file:// mean?

The page was loaded from the local filesystem rather than fetched from a server. There is no host and no origin in the normal sense.

What stops working?

fetch of local files, modules, service workers, and some storage. Each is refused because the page has no proper origin to be trusted with them.

Why does my page work when served and not when opened?

Because the restrictions only apply to file://. Running a local server for thirty seconds is the usual way to confirm this is the cause.

How do I serve a folder locally?

python3 -m http.server 8000 in that folder, then open localhost:8000. No configuration and no installation on most machines.

Keep reading