package.json Fields for Publishing Libraries

February 15, 2025 | 3 minutes

The specification for package.json files contains many different fields. In addition, bundlers like Webpack and Vite support some fields that aren't part of the official specification. If you're trying to publish a Javascript library and optimize it for different environments, it can be confusing.

The main, module, types, and exports fields are the key fields needed to make the code from your library available for use in other Javascript libraries and applications. Here's a closer look at when to use each option.

main

The main field tells Node where to find the primary entrypoint to your package. The default value is index.js if you omit main. Some build tools support the main and module fields, with the main field being used to define the CJS entrypoint to your package. In CJS environments, you import code using the require() method.

This field is supported in all Node versions, so it's a good way to provide backwards compatibility. It limits you to a single entrypoint, though, so in newer Node versions, the exports field is preferred (see below).

module

The module field tells Node where to find the primary entrypoint to your package in ES Module (ESM) environments. In ESM environments, you import code using the import method.

While it's not officially part of the package.json specification, it's widely supported by bundlers like Webpack and Vite. Also, if the package or application importing your package uses Typescript, then Typescript will use the module field if you have moduleResolution: node set in your tsconfig.file.

exports

The exports field is newer than the main and module fields. This field:

  • Allows you to specify multiple entry points
  • Supports conditional entry resolution across environments
    • Allows you to provide a default condition that can be a fallback for future unknown cases
  • Gives you the ability to hide entry points not defined in the exports field

It's supported in Node 12 and above. All paths provided should be relative paths that start with ./.

The exports field is the recommended way to tell Node where to find the entry points to a package. However, if you need to support Node 10 or below (which are very outdated at the time of writing), then the main field is required. For maximum compatibility across Node versions, you should provide both. The exports field will take precedence over the main field if both are included and the Node version supports exports.

1{
2  "exports": {
3    "import": "./index.js", // ESM
4    "module": "./index.js", // ESM
5    "require": "./index.cjs", // CJS
6    "default": "./index.js" // Fallback
7  }
8}

types

The types field tells Typescript where to find the type definitions for your package if your package exports type files. It should point to a directory with *.d.ts files or a single *.d.ts file. If you aren't using Typescript or aren't exporting types from your package, you don't need to include this field.

What about the type field?

When publishing packages, you may also come across the type field. This field tells Node whether to load *.js files as CJS or ESM. If this property is missing, it defaults to CJS.

If your package builds to only CJS or ESM, make sure that this field matches the correct type. If your build tool is configured to build both CJS and ESM files with one as .js files, then you should make sure that this field matches the correct type. As an example, your package may provide *.cjs files for CJS and *.js files for ESM. In this case, you should set type: "module" in your package.json.

1{
2  "type": "module", // Means load .js files as ESM
3  "exports": {
4    "import": "./index.js", // ESM
5    "module": "./index.js", // ESM
6    "require": "./index.cjs", // CJS
7    "default": "./index.js" // Fallback
8  }
9}

Regardless of the type setting, *.cjs files are always treated as CJS and *.mjs files are always treated as ESM.