# “Blocked by CORS Policy” From Origin 'null' When Opening an HTML File — the Fix

Canonical: https://commareports.com/fix/cors-error-file-origin-null
Published: 2026-10-07

> Double-click an HTML page that loads a JSON file or a module script and the console says “from origin 'null' has been blocked by CORS policy”. Why file:// pages can't fetch their own folder, and the four fixes that actually work.

# “Blocked by CORS policy” from origin `'null'`

You double-click `index.html`. The chart area is empty, and the console
has one red line:

```
Access to fetch at 'file:///Users/you/report/data.json' from origin 'null'
has been blocked by CORS policy: Cross origin requests are only supported
for protocol schemes: http, data, isolated-app, chrome-extension, chrome,
https, chrome-untrusted.
```

Or, for a built app:

```
Access to script at 'file:///…/dist/assets/index-4f2a.js' from origin 'null'
has been blocked by CORS policy
```

Nothing is wrong with the file. The browser is refusing to let a page
opened from disk read other files on disk, and it is right to.

## Why `file://` pages are origin `'null'`

Every web page has an origin — scheme, host and port. Two pages share an
origin and can read each other's responses; otherwise the server has to
opt in with an `Access-Control-Allow-Origin` header.

A file has no host. Chrome and Edge give every `file://` page an
**opaque origin**, which serialises as the string `'null'`, and opaque
origins match nothing — not even the folder the page sits in. There is
no server to send an allow header either. So any request that runs in
CORS mode fails.

If that sounds paranoid: without it, any HTML attachment you open could
read `~/.ssh/` with one `fetch()`.

## What triggers it, and what doesn't

| In the page                                | From `file://`                  |
| ------------------------------------------ | ------------------------------- |
| `<img src="chart.png">`                    | Works                           |
| `<link rel="stylesheet" href="app.css">`   | Works                           |
| `<script src="app.js">` (classic)          | Works                           |
| `<script type="module" src="app.js">`      | **Blocked** — modules use CORS  |
| `import "./chart.js"` inside a module      | **Blocked**                     |
| `fetch("data.json")` / `XMLHttpRequest`    | **Blocked**                     |
| `d3.csv("data.csv")`, `Plotly.d3.json(…)`  | **Blocked** — they wrap `fetch` |
| Web workers, `@font-face` in some browsers | Often blocked                   |

That table is why the failure feels arbitrary. The styled shell of the
page loads, so it looks broken rather than missing — only the part that
reads data stays empty.

## Fix 1: serve the folder (your machine)

Run a static server from the folder that contains the page:

```bash
python3 -m http.server 8000
```

```bash
npx serve .
```

Open `http://localhost:8000/`. Everything now comes from
`http://localhost:8000`, one origin, and both `fetch` and module scripts
load. For a Vite build, `npx vite preview` does the same for `dist/`.
More options in [serve an HTML file locally](/serve-html-file-locally).

This is the fix for development. It does nothing for a colleague you
email the file to — and a [localhost link only works on your machine](/fix/localhost-link-doesnt-work-for-others).

## Fix 2: put the data inside the page

If the page fetches one JSON or CSV file, embed it instead:

```html
<script type="application/json" id="report-data">
  {
    "rows": [
      ["2026-09", 1840],
      ["2026-10", 2215]
    ]
  }
</script>
<script>
  const data = JSON.parse(document.getElementById("report-data").textContent);
  draw(data.rows);
</script>
```

A JSON block is inert — it is never executed, just read — and escaping is
easy: the only sequence that can break it is `</script`, which you write
as `<\/script`. For CSV, put it in the same kind of block with
`type="text/csv"` and parse the string.

Most notebook and charting exports already have a switch for this.
Plotly's `include_plotlyjs=True` keeps the library in the file
([the four modes](/fix/plotly-chart-not-showing-in-html)); Altair and
Bokeh embed their data by default.

## Fix 3: stop loading modules from disk

For a bundled app, make sure no module has to be fetched. With Vite,
`vite-plugin-singlefile` inlines every chunk into `index.html` — an
inline `<script type="module">` has nothing to fetch, so the CORS check
never runs. With relative paths (`base: './'`) the remaining images and
fonts resolve from disk. Details for the common build tools:
[Vite](/share-vite-build), [Angular](/share-angular-build),
[Next.js static export](/share-nextjs-static-export).

Images, stylesheets and fonts referenced from the HTML can be embedded
the same way — the free [HTML inliner](/tools/html-inliner) rewrites a
whole folder into one file and lists anything it couldn't resolve.

## Fix 4: don't send a file at all

The flag people find on Stack Overflow —
`--allow-file-access-from-files` — makes the error go away on the one
machine that was launched with it, and lowers the guard for every other
file that browser opens. It is a test, not a fix.

The durable version is a URL. Publish the page with its assets and send
the link: the recipient opens it in a normal browser tab, nothing is
downloaded, and nothing depends on their filesystem.

One honest caveat for Comma specifically: reports run in a sandboxed
iframe with scripts enabled but **no same-origin access**, so a script
that calls `fetch("data.json")` at runtime still won't find a sibling
file after publishing. Inline the data first (fix 2), then publish —
charts, filters and sorting keep working.

## Firefox and Safari

Firefox used to allow `file://` reads within the page's own directory and
now treats each file as a unique origin too
(`security.fileuri.strict_origin_policy`). Safari blocks local XHR unless
**Disable Local File Restrictions** is ticked in the Develop menu. Same
conclusion in every browser: don't rely on reading from disk.

## Try it

Comma is free — publish a self-contained HTML file, get a link, and
collect comments pinned to the exact chart.

**[Publish a report →](https://commareports.com/)**

### Related

- [Allure report opens as a blank page](/fix/allure-report-blank-page)
- [Playwright report is blank](/fix/playwright-report-blank)
- [“Not allowed to load local resource”](/fix/not-allowed-to-load-local-resource)
- [Same-origin policy, explained](/glossary/same-origin-policy)
- [Self-contained HTML](/glossary/self-contained-html)
