Utilo

MJS MIME Type – Free, Online, Private

text/javascript is the type for a .mjs module file. The extension tells Node to treat the file as a module. The browser still wants text/javascript.

What .mjs is

The .mjs extension exists so Node.js will parse the file as an ECMAScript module even when the nearest package.json says commonjs. Browsers do not use that rule. A browser treats a script as a module when the script element says type=module or when another module imports it. The media type is still text/javascript from RFC 9239. There is no text/mjs registration. A server that has no mapping for .mjs falls through to application/octet-stream, the browser refuses to evaluate the module, and the import fails with a type error that does not mention the missing types entry.

IANA's JavaScript type is text/javascript (RFC 9239). .mjs is a module filename convention, not a different registration. The media type is text/javascript. Extensions that use it on this page: .mjs. A client compares the type, not the filename, once the response has arrived. The filename still matters on disk and in Content-Disposition, which is why a wrong extension and a wrong header are two different bugs.

What sends text/javascript

What sends text/javascript: Map .mjs to text/javascript, with no charset parameter, the same way you map .js. If you emit both extensions, keep their contents consistent with how Node will parse them. Do not serve CommonJS module.exports from a .mjs URL and expect Node to wrap it. Do not serve native ESM from a .js URL in a CommonJS package and expect Node to treat it as a module. The browser header cannot paper over that. On the web, both extensions are text/javascript, and the import graph is the authority. The file on disk is still .mjs. Once the response exists, a client trusts text/javascript, not the filename. If you do not need Node to see a different extension, emit .js for browser modules and save yourself a second types entry. Keep .mjs when a package is consumed both from Node and from a CDN and you want the extension to document the module format for humans.

Content-Type for .mjs

Send text/javascript with no charset parameter. The registration either defines no charset parameter or tells clients to ignore one, and the payload is UTF-8 by rule rather than by a parameter. Adding charset=utf-8 looks helpful and has broken strict parsers.

Content-TypeContent-Type: text/javascript

nginx, Apache, and Express

These snippets map .mjs to text/javascript. nginx reads the types block when it picks a type from the URL's extension. A location that sets default_type, or an application that sets the header itself, overrides that map. Apache AddType applies in the directory context where you put it. The Express static example writes the header for files ending in .mjs so a stale mime database in the framework cannot disagree with the type on this page.

nginx# /etc/nginx/mime.types, inside the types { } block
text/javascript             mjs;
ApacheAddType text/javascript .mjs
Expressimport express from "express";

const app = express();
app.use(express.static("public", {
  setHeaders(res, filePath) {
    if (filePath.endsWith(".mjs")) {
      res.setHeader("Content-Type", "text/javascript");
    }
  },
}));

Pitfalls for text/javascript

Pitfalls that show up for text/javascript, and not for a neighboring type with a similar extension: The outage is a static host updated for .js and not for .mjs after a bundler starts emitting modules. Every dynamic import of a .mjs URL fails. Add the types line. A second trap is a corporate proxy that only allow-lists application/javascript and blocks text/javascript, or the reverse. RFC 9239's type is text/javascript. Update the proxy. A third is serving .mjs as application/node, which is not a real type, from a custom server meant to force a download. A fourth is a source map comment with a wrong path. The map is still application/json.

  • text/ecmascript and application/ecmascript are obsolete aliases. Do not send them for .mjs.
  • Node's type field in package.json does not travel in the HTTP header. A CDN does not read package.json to pick a Content-Type.
  • A .mjs file imported by Node from disk has no Content-Type at all. This page is about HTTP.
  • WASM instantiated from a module uses application/wasm on the .wasm response, not text/javascript.

Frequently asked questions

Is there a text/mjs type?

No. Serve .mjs as text/javascript, the RFC 9239 type.

Why does the browser reject my module?

The response was probably application/octet-stream because the server has no .mjs mapping. Set text/javascript.

Does the header make a file a module?

No. In the browser, type=module or an import does. In Node, the extension and package.json do. The header is text/javascript either way.

Should I send a charset?

No. Omit it, as with any JavaScript response under RFC 9239.