Importing JSON in ES Modules (No Fetch, No Bundler)
If you’ve ever tried import config from './config.json' in a plain ES module project and hit a wall, you’re not alone. For years, importing JSON without a bundler meant falling back to fetch() or converting your data to a JavaScript file. That workaround era is over.
As of 2025, import attributes are baseline across modern browsers. You can now do a native JavaScript JSON module import with zero build tooling required.
Key Takeaways
- Use
with { type: 'json' }to natively import JSON in ES modules — no bundler orfetch()needed. - The
typeattribute is mandatory for security: it ensures the runtime validates the MIME type before processing the file. - JSON modules only expose a default export. Destructure from it after import.
- Your JSON files must be served over HTTP with the correct
Content-Type: application/jsonheader.
The Modern Syntax: Import Attributes
The current syntax uses the with keyword to declare the module type:
// Static import
import config from './config.json' with { type: 'json' }
console.log(config.apiUrl)
For dynamic loading:
// Dynamic import
const { default: config } = await import('./config.json', {
with: { type: 'json' }
})
Note: The older
assert { type: 'json' }syntax (supported in Chrome 91–122) is now deprecated. Always usewith.
Why the type: 'json' Attribute Is Required
The with { type: 'json' } attribute isn’t optional decoration. It serves a specific security purpose: it forces the browser or runtime to validate the response MIME type as application/json before processing anything.
Without it, a server could return JavaScript disguised as a JSON file, and the engine would have no way to enforce the distinction. The type attribute prevents that.
JSON Modules Only Have a Default Export
One thing that trips developers up: JSON modules do not support named exports. The entire JSON object comes in as the default export.
import data from './data.json' with { type: 'json' }
// ✅ Correct
const { users, settings } = data
// ❌ This won't work
import { users } from './data.json' with { type: 'json' }
Destructure from the default export after import.
Discover how at OpenReplay.com.
Browser and Node.js Support
| Environment | Minimum Version |
|---|---|
| Chrome | 123+ |
| Firefox | 138+ |
| Safari | 17.2+ |
| Node.js | Current LTS and newer |
All of these are now widely deployed. If you’re targeting modern browsers and current Node.js releases, you don’t need a bundler or a fetch() call to load JSON.
Note: Earlier Node.js versions provided JSON modules behind experimental flags. JSON module support with import attributes is now stable in current Node.js releases. See the Node.js ESM documentation for exact version details.
Practical Constraints to Know Before You Ship
You need an HTTP server. Module imports—including JSON modules—generally do not work when loading pages directly via file://. Browsers apply strict security rules to module loading. Use a local dev server like Vite, serve, or any static file server.
Your JSON file must be served with the correct MIME type. The server needs to return Content-Type: application/json. Most static servers handle this automatically for .json files, but double-check if you’re using a custom server or CDN configuration.
JSON must be valid. There’s no error recovery at the import level. A syntax error in your JSON file will cause the module to fail to load entirely. Validate your JSON before deploying.
A Real-World Example
// config.json
{
"apiUrl": "https://api.example.com",
"timeout": 5000,
"features": {
"darkMode": true
}
}
// app.js
import config from './config.json' with { type: 'json' }
const { apiUrl, timeout, features } = config
if (!apiUrl || typeof timeout !== 'number') {
throw new Error('Invalid configuration')
}
This pattern works cleanly for configuration files, feature flags, i18n strings, or any static structured data your app needs at startup.
Conclusion
Import attributes give you a clean, native way to import JSON in ES modules without fetch(), without a bundler, and without workarounds. The with { type: 'json' } syntax is now widely supported in modern browsers and current Node.js releases. Just make sure your files are served over HTTP with the right MIME type, and you’re set.
FAQs
Yes. The import attributes proposal is designed to be extensible. CSS module scripts, for example, use with type css in supported browsers. However, JSON is the most widely supported type today. Other types depend on the runtime and may not yet be available everywhere.
The import will fail entirely and throw a SyntaxError. Unlike fetch where you can catch and inspect the raw response, a JSON module import offers no partial recovery. Validate your JSON files with a linter or a CI check before deploying to avoid silent breakage at load time.
Not for the JSON import itself. Modern browsers and Node.js handle it natively. However, you may still want a bundler for other reasons like code splitting, tree shaking, or transpiling syntax for older targets. The point is that JSON loading alone no longer requires one.
Browsers enforce strict security rules on module loading. The file protocol does not support the mechanisms required for module requests, so the browser blocks the import. You need to serve your files through an HTTP server, even locally. Tools like Vite or the serve npm package handle this with minimal setup.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.