Back

Importing JSON in ES Modules (No Fetch, No Bundler)

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 or fetch() needed.
  • The type attribute 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/json header.

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 use with.

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.

Browser and Node.js Support

EnvironmentMinimum Version
Chrome123+
Firefox138+
Safari17.2+
Node.jsCurrent 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.

OpenReplay