The Error Scenario
Nothing kills coding momentum like staring at a wall of spaghetti-like relative paths. I was refactoring a project to replace import { helper } from '../../../../utils/helper.js' with something cleaner. Node.js now natively supports Subpath Imports via the "imports" field in package.json, allowing you to use aliases that start with #.
Everything looked correct on paper. However, the moment I launched the app, it crashed with this specific error:
Error [ERR_PACKAGE_IMPORT_NOT_DEFINED]: Package import specifier "#utils" is not defined in package "package.json" imported from /app/src/index.js
at new NodeError (node:internal/errors:399:5)
at throwImportNotDefined (node:internal/modules/esm/resolve:408:9)
Why is this happening?
Node.js triggers this error when it sees an import starting with # but can't find a matching rule in your configuration. It recognizes the syntax as a subpath import, but the resolution logic hits a dead end.
Usually, the culprit is one of these four issues:
- The
"imports"field is missing or misplaced inpackage.json. - You have a typo in the key (e.g., defining
"#util"but calling"#utils"). - The path mapping doesn't start with the mandatory
./prefix. - Your
package.jsonhas a syntax error, like a trailing comma, which causes Node.js to ignore the entire field.
The Solution: Correcting package.json
Fixing this requires a strictly formatted "imports" object. This field must live at the top level of your package.json, alongside "name" and "version".
1. Direct File Mapping
Use this if you want to map a specific alias to a single file:
{
"name": "my-app",
"type": "module",
"imports": {
"#utils": "./src/utils/index.js"
}
}
2. Dynamic Pattern Mapping
In most projects, you'll want to map an entire folder. To do this, you must use the * wildcard on both sides of the mapping:
{
"imports": {
"#utils/*": "./src/utils/*.js"
}
}
Now, import { log } from '#utils/logger' correctly points to ./src/utils/logger.js. Without that *, Node.js won't know how to resolve the sub-files.
Essential Rules to Remember
Node.js is notoriously rigid about how you structure these fields. One small slip-up results in a broken app.
The Hash Prefix is Mandatory
Unlike TypeScript paths or Webpack aliases, every key in the imports field must start with a #. This ensures they don't clash with scoped npm packages like @company/package.
Paths Must Be Relative
The target value must start with ./. It tells Node the path is relative to the package root. Using "src/lib.js" will fail; use "./src/lib.js" instead.
Watch for JSON Syntax Errors
If your package.json is invalid, Node.js won't just skip the bad line—it might fail to read the imports field entirely. I once spent 45 minutes debugging an import only to find a missing double quote in a completely different section of the file.
To stay safe, run your file through a validator. I use the JSON Formatter & Validator at ToolCraft to catch invisible syntax errors or stray commas that standard editors sometimes miss.
Advanced Usage: Conditional Imports
Subpath imports are powerful because they can swap files based on the environment. This is great for handling Node.js vs. Browser logic without a heavy bundler.
{
"imports": {
"#db-client": {
"node": "./src/db/server-side.js",
"default": "./src/db/client-side.js"
}
}
}
How to Verify the Fix
Once you've updated your configuration, don't just hope it works. Run these quick checks:
- Check Your Version: Run
node -v. You need at least Node.js 14.13.0 for full subpath import support. - Run a CLI Test: You can verify the resolution without starting your whole server:
node -e "import('#utils/logger').then(m => console.log('Success!'))"
- **Restart Your Dev Server:** Tools like `nodemon` or `ts-node` sometimes cache `package.json`. If the error persists after a fix, a full terminal restart usually clears the ghost errors.
## Quick Checklist
- Does every key in `"imports"` start with `#`?
- Does every value start with `./`?
- Is the `"imports"` field located at the root of `package.json`?
- If mapping a directory, did you include the `*` wildcard on both sides?
- Is your JSON syntax 100% valid?

