Skip to content

Fix: publint Not Working — Package Exports Invalid, Types Not Found, or Dual Package Errors

FixDevs ·

Quick Answer

How to fix publint package validation issues — exports field configuration, dual ESM/CJS packaging, type resolution, main/module/types fields, files array, and common packaging mistakes.

The Problem

publint reports errors on your package:

npx publint
# ✗ "exports['.'].import.types" types is not the first in the object
# ✗ "main" file does not exist
# ✗ "module" field should be ESM but found CJS

Or consumers can’t import your package correctly:

import { something } from 'my-package';
// Error: Cannot find module 'my-package' or its corresponding type declarations

Or the package works in one environment but not another:

Works with: import { foo } from 'my-package'
Fails with: const { foo } = require('my-package')

Why This Happens

publint checks that your package.json is configured correctly for publishing to npm. Modern packages need to support multiple module systems and provide proper type information:

  • exports field is the modern standard — it replaces main, module, and types for Node.js 12+. But many tools still read the legacy fields, so you need both.
  • types must be first in conditional exports — TypeScript resolves types from the first matching condition. If types isn’t listed before default, TypeScript may not find your type declarations.
  • ESM and CJS have different file extensions.mjs is always ESM, .cjs is always CJS, .js follows "type" in package.json. Mismatched extensions cause “Cannot use import/require” errors.
  • files controls what’s published — if dist/ isn’t in the files array, npm publish excludes it, and consumers get an empty package.

Fix 1: Run publint and Fix Errors

npx publint
# Or check online: https://publint.dev/

# Common output:
# ✗ "exports['.'].import.types" types is not the first in the object
# ✗ "exports['.'].require" file does not exist
# ✓ "main" is valid
# ✓ "types" is valid

Fix each error type:

// ❌ WRONG — types is not first
{
  "exports": {
    ".": {
      "import": {
        "default": "./dist/index.js",
        "types": "./dist/index.d.ts"
      }
    }
  }
}

// ✅ CORRECT — types must be first
{
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      }
    }
  }
}

Fix 2: Correct package.json for Dual ESM/CJS

// package.json — the complete modern setup
{
  "name": "my-package",
  "version": "1.0.0",
  "description": "My awesome package",
  "type": "module",

  // Legacy fields — for older tools and bundlers
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",

  // Modern exports — takes precedence over main/module
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    },
    "./utils": {
      "import": {
        "types": "./dist/utils.d.ts",
        "default": "./dist/utils.js"
      },
      "require": {
        "types": "./dist/utils.d.cts",
        "default": "./dist/utils.cjs"
      }
    },
    // Export CSS
    "./styles.css": "./dist/styles.css",
    // Export package.json (some tools need this)
    "./package.json": "./package.json"
  },

  // What's included in the published package
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ],

  // Side effects — enables tree-shaking
  "sideEffects": false,
  // Or specify files with side effects:
  // "sideEffects": ["./dist/styles.css"]

  // Engines
  "engines": {
    "node": ">=18"
  },

  // Keywords for npm search
  "keywords": ["utility", "typescript"],

  // Repository
  "repository": {
    "type": "git",
    "url": "https://github.com/user/my-package"
  },

  "license": "MIT",

  "scripts": {
    "build": "tsup",
    "lint": "publint && attw --pack",
    "prepublishOnly": "npm run build && npm run lint"
  }
}

Fix 3: ESM-Only Package

// Simpler setup when you don't need CJS support
{
  "name": "my-esm-package",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    }
  },
  "files": ["dist"],
  "sideEffects": false
}

Fix 4: CJS-Only Package (Legacy)

// For packages that must support older Node.js
{
  "name": "my-cjs-package",
  "version": "1.0.0",
  // No "type": "module" — defaults to CJS
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "require": "./dist/index.js"
    }
  },
  "files": ["dist"]
}

Fix 5: Validate with Multiple Tools

# publint — checks package.json configuration
npx publint

# arethetypeswrong — checks TypeScript resolution
npx @arethetypeswrong/cli --pack
# Shows how different moduleResolution settings resolve your types

# Are the Types Wrong output:
# ┌───────────────────┬──────────────────┬──────────────────┐
# │                   │ node16 (import)  │ node16 (require) │
# ├───────────────────┼──────────────────┼──────────────────┤
# │ "my-package"      │ ✅               │ ✅               │
# │ "my-package/utils"│ ✅               │ ❌ No types      │
# └───────────────────┴──────────────────┴──────────────────┘

# npm pack --dry-run — see what will be published
npm pack --dry-run
# Lists all files that will be in the tarball

# Check package size
npx pkg-size
# Or: npx bundlephobia my-package
// package.json — validation scripts
{
  "scripts": {
    "build": "tsup",
    "check:exports": "publint",
    "check:types": "attw --pack",
    "check:size": "pkg-size",
    "prerelease": "npm run build && npm run check:exports && npm run check:types"
  }
}

Fix 6: Common Patterns and Fixes

// React component library
{
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    },
    "./styles.css": "./dist/styles.css"
  },
  "peerDependencies": {
    "react": ">=18.0.0",
    "react-dom": ">=18.0.0"
  },
  "sideEffects": ["*.css"]
}

// CLI tool
{
  "type": "module",
  "bin": {
    "my-cli": "./dist/cli.js"
  },
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    }
  },
  "files": ["dist"]
}

// Package with subpath exports (e.g., my-pkg/server, my-pkg/client)
{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    },
    "./server": {
      "types": "./dist/server.d.ts",
      "default": "./dist/server.js"
    },
    "./client": {
      "types": "./dist/client.d.ts",
      "default": "./dist/client.js"
    }
  }
}

Still Not Working?

“types is not the first in the object” — in each exports condition block, the types key must come before default, import, or require. TypeScript resolves types from the first matching condition, so types must be first for correct resolution.

“file does not exist” — the file referenced in exports, main, module, or types isn’t in the built output. Run your build command first, then run publint. Check that files in package.json includes the dist/ directory.

Package works with import but not require — the require condition in exports is missing or points to an ESM file. ESM files (.js with "type": "module", or .mjs) can’t be require()d. Generate a .cjs output for CJS consumers.

Types resolve in one moduleResolution but not another — run npx @arethetypeswrong/cli --pack to check all resolution modes. node16 and bundler resolve differently. The types condition in exports fixes most issues. For node16 CJS, you need .d.cts files alongside .cjs files.

For related packaging issues, see Fix: tsup Not Working and Fix: Changesets Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles