HTTP imports on node

node v18 shall have ESM import support over HTTPS remotely and HTTP locally and is currently under a flag.

This is experimental and things can change drastically, some of the feature might be removed as well, so take it with a pinch of salt.

Here is an example:

_Code sample:

import helloWorld from "";

console.log(helloWorld()); // Log Hello World
import helloWorld from "http://localhost:1337/hello-world.mjs";

console.log(helloWorld()); // Log Hello World

As you might have noticed, this is still under an experimental flag: --experimental-network-imports

For now, the --experimental-network-imports flag is required to enable loading
resources over http: or https:. In the future, a different mechanism will be
used to enforce this. Opt-in is required to prevent transitive dependencies
inadvertently using potentially mutable state that could affect reliability
of Node.js applications.

The contents of

export default function helloWorld () {
console.log('Hello, World!')

The most important thing to notice here is the MIME type of the URL.

$ curl -s -o /dev/null -w '%{content_type}' ''


The hosted mjs should respond with application/javascript as it's content-type or else it will fail to load.

Authorization, Cookie, and Proxy-Authorization headers are not sent to the server and CORS policies/headers are neither sent nor enforced.

Some of the common errors:

import helloWorld from "";
Error [ERR_NETWORK_IMPORT_BAD_RESPONSE]: import '' received a bad response: HTTP response returned status code of 404
import helloWorld from ""; // Say this redirects to
Error [ERR_NETWORK_IMPORT_DISALLOWED]: import of '' by '' is not support: cannot redirect to non-network location
import helloWorld from "";
RangeError [ERR_UNKNOWN_MODULE_FORMAT]: Unknown module format: null for URL
import helloWorld from "ftp://foo.js";

Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, https, http are supported by the default ESM loader. Received protocol 'ftp:'

How do the fetch works?

fetchModule does the basic sanity checks and also checks if it is already present in cache (cacheForGET) if not it invokes fetchWithRedirects

function fetchModule(parsed, { parentURL }) {
const { href } = parsed;
const existing = cacheForGET.get(href);
if (existing) {
return existing;
if (parsed.protocol === 'http:') {
return PromisePrototypeThen(isLocalAddress(parsed.hostname), (is) => {
return fetchWithRedirects(parsed);
return fetchWithRedirects(parsed);
function fetchWithRedirects(parsed) {
const existing = cacheForGET.get(parsed.href);
if (existing) {
return existing;
const handler = parsed.protocol === 'http:' ? HTTPGet : HTTPSGet;
const result = new Promise((fulfill, reject) => {
const req = handler(parsed, {
headers: {
Accept: '*/*'
.on('error', reject)
.on('response', (res) => {
// Checks for multiple error cases that were mentoined above
// Caches the content if required
// Returns a promise which shall resolve to the module's content
let HTTPSAgent;
function HTTPSGet(url, opts) {
const https = require('https'); // For HTTPGet, we use the `http` builtin
HTTPSAgent ??= new https.Agent({
keepAlive: true
return https.get(url, {
agent: HTTPSAgent,

So, summerizing the status of this experimental feature:

Source of truth is the commit on the main branch of node src.

Feel free to share this article. You may as well ping me on Twitter.