Importing JSON is finally standard
For years, loading a JSON file into JavaScript meant fs.readFile + JSON.parse, a bundler plugin, or a require('./data.json') that only worked in CommonJS. ES modules had no standard way to import JSON — until import attributes, standardized in ECMAScript 2025. Now you can import a JSON file directly, with an explicit attribute telling the runtime it's JSON:
import config from "./config.json" with { type: "json" };
console.log(config.version); // config is the parsed object, not a stringThe imported value is the already-parsed object — no JSON.parse step. As of 2026 this works in Node.js 22+, Deno, and current Chrome, Firefox, Safari, and Edge, with no bundler configuration. It builds on JSON in JavaScript and JSON in Node.js by making the import itself a language feature.
The syntax: the `with` keyword
An import attribute comes after the module specifier, introduced by with, as an object of key–value pairs:
// static import
import pkg from "./package.json" with { type: "json" };
// dynamic import — note the attributes go under a "with" key
const data = await import("./data.json", { with: { type: "json" } });
console.log(data.default); // the JSON object is the default exportThe ECMAScript standard defines the type attribute with values "json" and "text"; browsers additionally support "css" for CSS module imports. For JSON, type: "json" is the one you need.
Why is the `type` attribute required?
It looks redundant — the file already ends in .json, so why must you *also* say type: "json"? The answer is security, specifically preventing a MIME-confusion / privilege-escalation attack.
A module's file extension or URL doesn't reliably tell the runtime what the server actually returned. Without the attribute, a server could serve a URL that *looks* like JSON but returns executable JavaScript, and the importing code might run it as a script. The type: "json" attribute makes the developer's intent explicit and binding: the runtime must treat the resource as JSON data, and if what comes back isn't valid JSON (or the server's MIME type contradicts it), the import fails instead of silently executing code. In short, the attribute guarantees "this is data, never code."
It replaced import assertions (`assert`)
If you've seen the older assert { type: "json" } syntax, that was an earlier proposal (import *assertions*) that has been deprecated in favor of import *attributes* with with. The change was deliberate: assert implied the attribute only *checked* the type after loading, but the committee realized the attribute can actually *influence how the module is fetched and interpreted* — so with (which reads as "load this with these attributes") replaced assert. Migrate any assert { type: "json" } to with { type: "json" }; the assertion form is being removed from engines.
// ❌ deprecated
import data from "./data.json" assert { type: "json" };
// ✅ current (ES2025)
import data from "./data.json" with { type: "json" };When to use it — and when not to
Use JSON imports for static, bundled-with-your-code data: configuration, package.json or tsconfig.json values, i18n message catalogs, fixture data, feature manifests. It's cleaner than reading and parsing a file, and the value is typed as an object.
Don't use it for data that changes at runtime or comes from a network endpoint — imports are resolved once and cached for the life of the module, so an imported JSON file is effectively a constant. For dynamic data, keep using fetch + response.json(). And remember imported JSON is read-only in spirit: mutating the object mutates the single shared module instance everyone else imported.
For related fundamentals see JSON in JavaScript, the JSON.stringify guide, and JSONC & JSON5 for comment-friendly config formats.