“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:
python3 -m http.server 8000
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.
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 2: put the data inside the page
If the page fetches one JSON or CSV file, embed it instead:
<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); 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, Angular,
Next.js static export.
Images, stylesheets and fonts referenced from the HTML can be embedded the same way — the free 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.