Import Attributes: Loading JSON Modules Natively With type: 'json'
AI generated
JS
() =>
JavaScript · ES Modules · Import Attributes
Import Attributes: Loading JSON Modules Natively
Import JSON files with type: 'json', no fetch() or JSON.parse() needed

Import Attributes let you import JSON files directly as an ES module, with an explicit type hint that became mandatory for security reasons. In many cases it replaces fetch() plus JSON.parse() with a single line.

13 min read Import Attributes JSON modules ES Modules

1. From the fetch() Detour to a Direct Import

Static configuration data, translation files, or test fixtures live as a JSON file in many projects. Until recently, the browser offered exactly one standard way to load them: call fetch('./data.json'), wait for the response, call .json(), and process the result, all asynchronously with its own error handling for network and parse failures.

Import Attributes introduce a direct, declarative path: import data from './data.json' with { type: 'json' }. The module system itself loads and parses the file, the result is available as the default export, with no manual fetch, no .json() call, and no extra promise layer that unnecessarily complicated plain configuration loading.

2. Syntax: import ... with { type: 'json' }

The syntax builds on existing import statements, adding a with clause at the end. Statically it looks like import config from './config.json' with { type: 'json' }, after which config directly holds the parsed JavaScript object. Without the type attribute, the engine rejects the import with an error, even if the file extension is clearly .json.

The attribute is deliberately redundant with the file extension, because the engine cannot rely on extensions alone: servers can serve JSON content under arbitrary paths without a .json extension, and Content-Type headers are not trustworthy enough from a security standpoint to decide the module kind on their own. The explicit declaration in the source code is the actual anchor of trust.


import config from './config.json' with { type: 'json' };
console.log(config.apiBaseUrl);

// Without the with clause: SyntaxError / TypeError, depending on the engine
// import config from './config.json'; // rejected

3. Dynamic Import With Attributes

For cases where the path is only known at runtime, or the JSON should only load conditionally, the same idea works with dynamic import(). Instead of a top-level statement, a second argument object is passed to import(), whose with property in turn holds the type attribute. The result is a promise that resolves to a module namespace object.

The actual data value sits in the .default property of the resolved module, analogous to the default export in static imports. That makes dynamic JSON imports a real alternative to fetch() for locally bundled data, with the advantage that build tools can already recognize the import as a dependency at build time and bundle it accordingly.


async function loadLocale(code) {
  const module = await import(`./locales/${code}.json`, {
    with: { type: 'json' },
  });
  return module.default;
}

const de = await loadLocale('de');

4. The Security Rationale Behind the Type Attribute

The requirement for an explicit type attribute is not a formality, it is a direct response to a real attack scenario. Without that requirement, an attacker who controls the content of a seemingly harmless JSON resource, for example through a compromised CDN response or an open upload endpoint, could try to place executable JavaScript there instead of plain JSON and get it executed via a normal JS import.

With the type attribute, the developer explicitly declares which module kind they expect, and the engine enforces a matching MIME type check against the server's response header. If the actual content type deviates from the expected type, the import fails instead of silently interpreting the file as a script. Historically this feature was called 'Import Assertions' with the assert keyword, but it was renamed to 'Import Attributes' with with due to unwanted error semantics.

5. Comparison: JSON and CSS Module Attributes

JSON is not the only use case for import attributes, conceptually the same syntax also exists for CSS modules via type: 'css', letting a stylesheet be imported as a CSSStyleSheet object instead of text. Support for CSS module scripts, however, currently lags noticeably behind support for JSON modules, especially outside Chromium-based browsers.

In practice that means JSON modules via with { type: 'json' } can already be used broadly in production today, while CSS module scripts are more of a preview of future bundler and framework integration. Anyone using both feature families in their code should check support for each separately rather than assuming uniform availability.


// JSON module, broadly supported
import theme from './theme.json' with { type: 'json' };

// CSS module, experimental / more limited support
import sheet from './styles.css' with { type: 'css' };
document.adoptedStyleSheets = [sheet];

6. Browser and Node.js Support

Chrome and Edge have fully supported static and dynamic JSON import attributes since version 123, Node.js caught up with version 22 and has been stable there since. Safari and Firefox support is currently more uneven: dynamic imports with type: 'json' work more broadly than static top-level imports, which is why the dynamic path is currently recommended for maximum compatibility.

For safe runtime feature testing, a try/catch around a dynamic test import works well, since there is no synchronous way to check support without an actual import attempt. In production code it is also worth adding a fallback to fetch() plus JSON.parse() for environments that do not yet know import attributes.


async function supportsJsonImportAttributes() {
  try {
    await import('data:application/json,{}', { with: { type: 'json' } });
    return true;
  } catch {
    return false;
  }
}

7. Support in Bundlers and TypeScript

Vite and Webpack now natively support the import attribute syntax in current versions and resolve the with clause already at build time, so the bundled output works regardless of the target browser. TypeScript needs at least version 5.3 for the new syntax, together with a suitable module target like 'esnext' or 'nodenext' in tsconfig.json.

Anyone using older bundler versions should check before switching whether the with clause is parsed correctly, since a plain parse-time syntax error (not a runtime one) can abort the entire build. A short isolated test import in a new file is the fastest way to verify that before a larger refactor.


// tsconfig.json (excerpt)
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler"
  }
}

8. When fetch(), When Import Attributes?

Import attributes suit JSON that is known at build time and should be bundled with the rest of the code, for example configuration files, static translation files, or test fixtures. The advantage: build tools recognize the dependency, can add it to the dependency graph, and can apply tree-shaking or caching strategies to it, exactly like with any other JS import.

fetch() remains the right choice for JSON that is only loaded from a server at runtime, that changes between requests, or that comes from user input or external APIs. Import attributes are not a replacement for network requests, they are a simplification for the narrow case of locally shipped, static JSON files.


// Static, known at build time -> Import Attributes
import featureFlags from './feature-flags.json' with { type: 'json' };

// Dynamic, runtime dependent -> fetch()
const response = await fetch('/api/user-preferences');
const preferences = await response.json();

9. Best Practices and Summary

Import attributes solve a concrete problem more elegantly than fetch() plus JSON.parse() ever could: static JSON becomes a normal module dependency that the build system understands, type-checks, and tracks. The requirement for an explicit type attribute is not syntactic overhead, it is a deliberate security measure against MIME type confusion.

For production use it is currently worth checking the target environment: with modern bundler usage (Vite, Webpack, current TypeScript) the syntax is already safe to use, with direct browser usage without a build step a feature detection with a fetch() fallback is worthwhile for now, until Safari and Firefox catch up on static imports.

Approach When Sensible Build-Time Detection Support Status
Import Attributes (static) Known, bundled JSON Yes Chrome/Edge/Node stable, Safari/Firefox partial
Import Attributes (dynamic) Conditional loading of JSON modules Partial Broader support than static
fetch() + JSON.parse() Runtime/server data, external APIs No Available everywhere
CSS modules (type: 'css') Import stylesheets as CSSStyleSheet Partial Mostly Chromium browsers

Mironsoft

Modern browser APIs, performance, and maintainable JavaScript

JavaScript that holds up in the real browser, not just in the tutorial?

We review existing frontend code for outdated patterns, unnecessary dependencies, and performance traps, then replace them with modern, native browser APIs that mean less bundle weight and less maintenance burden.

Code Review

Systematically finding outdated patterns, unnecessary dependencies, and memory leaks.

Performance Optimization

Improving bundle size, load time, and runtime performance with modern APIs.

Modernization

Deliberately introducing native browser APIs instead of heavy libraries.

10. Summary

Import Attributes: The Essentials at a Glance

Syntax

import x from './d.json' with { type: 'json' } loads and parses JSON directly as a module, without fetch() or JSON.parse().

Security rationale

The type attribute enforces a MIME type check and prevents externally controlled resources from being interpreted as a script instead of data.

Support

Chrome, Edge, and Node.js support static and dynamic JSON imports stably, Safari/Firefox currently favor the dynamic path.

Scope

Only for JSON known at build time and shipped locally, for runtime data fetch() remains the right choice.

11. FAQ: Import Attributes: The Essentials at a Glance

1What exactly does with { type: 'json' } mean?
It is a mandatory addition to the import statement that tells the engine the loaded resource should be parsed as JSON instead of executed as JavaScript.
2Why is not the .json file extension enough as a hint?
Because servers can serve JSON content under arbitrary paths without a .json extension, and file extensions alone are not a reliable security signal, which is why explicit declaration in code is mandatory.
3What was 'Import Assertions' and how does it differ?
The predecessor used the assert keyword instead of with. It was renamed because assert was meant to fully discard the import on error, which was too strict for some use cases.
4Does this also work with dynamic import()?
Yes, the second argument of import() takes an object with a with property, which in turn holds type, the result is a promise resolving to a module namespace object.
5Where does the actual data value live in a dynamic import?
In the .default property of the resolved module, so const data = (await import(...)).default, analogous to the default export in static imports.
6Do all browsers already support import attributes?
Chrome, Edge, and Node.js have supported them stably since 2024, Safari and Firefox support the dynamic path more broadly than the static top-level import, a direct support check is worthwhile before production use.
7Do I need a polyfill for older environments?
No, a polyfill for module loading syntax is practically impossible, instead feature detection via a test import with a fetch() plus JSON.parse() fallback is recommended.
8Do bundlers like Vite or Webpack recognize the syntax?
Current versions of both tools already resolve the with clause at build time, so the result works regardless of the target browser, older versions should be tested beforehand.
9Can I use this to load remotely hosted JSON too?
Technically yes with absolute URLs and CORS clearance, but practically import attributes are mainly meant for locally bundled JSON known at build time, for server data fetch() remains more sensible.
10Is this feature also available for CSS?
Conceptually yes, via with { type: 'css' } for so-called CSS module scripts, but support for that currently lags noticeably behind support for JSON modules.