pnpm/pnpm · error · PnpmError
INVALID_JSR_SPECIFIER
INVALID_JSR_SPECIFIER
Error message
JSR specifier '${rawSpecifier}' is missing a package name What it means
Thrown by parseJsrSpecifier when a `jsr:` dependency specifier carries no package name and no alias to derive one from. JSR packages are always scoped (`@scope/name`), so after stripping the `jsr:` prefix the parser needs either a leading `@scope/name` or an alias; with neither, there is nothing to resolve. The remainder of the specifier is treated as a version selector, which is why a nameless specifier is rejected rather than guessed.
Source
Thrown at pnpm11/resolving/jsr-specifier-parser/src/index.ts:43
}
}
// syntax: jsr:@<scope>/<name>@<version_selector>
const jsrPkgName = rawSpecifier.substring(0, index)
return {
jsrPkgName,
npmPkgName: jsrToNpmPackageName(jsrPkgName),
versionSelector: rawSpecifier.substring(index + '@'.length),
}
}
// syntax: jsr:<name>@<version_selector> (invalid)
if (rawSpecifier.includes('@')) {
throw new PnpmError('MISSING_JSR_PACKAGE_SCOPE', 'Package names from JSR must have a scope')
}
if (!alias) {
throw new PnpmError('INVALID_JSR_SPECIFIER', `JSR specifier '${rawSpecifier}' is missing a package name`)
}
// syntax: jsr:<spec>
return {
versionSelector: rawSpecifier,
jsrPkgName: alias,
npmPkgName: jsrToNpmPackageName(alias),
}
}
function jsrToNpmPackageName (jsrPkgName: string): string {
if (jsrPkgName[0] !== '@') {
throw new PnpmError('MISSING_JSR_PACKAGE_SCOPE', 'Package names from JSR must have a scope')
}
// The returned name is used in registry URLs and metadata cache file paths,
// so anything that is not a valid npm package name must never make it through.
if (!validateNpmPackageName(jsrPkgName).validForOldPackages) {
throw new PnpmError('INVALID_JSR_PACKAGE_NAME', `The package name '${jsrPkgName}' is invalid`)View on GitHub (pinned to 6261b7f388)
Solutions
- Rewrite the dependency with the full scoped JSR name: "@scope/pkg": "jsr:@scope/pkg" (optionally with a selector: "jsr:@scope/pkg@^1.0.0")
- Look the package up on jsr.io and copy its exact `@scope/name` verbatim
- If you intended an alias install, put the scoped name in the specifier itself rather than relying on the dependency key
Example fix
// before (package.json) "foo": "jsr:foo" // after "@scope/foo": "jsr:@scope/foo@^1.0.0"
Defensive patterns
Strategy: validation
Validate before calling
const JSR_SPEC = /^jsr:@[a-z0-9-]+\/[a-z0-9-._]+(@.+)?$/
export function assertValidJsrDependencies (deps: Record<string, string> = {}) {
for (const [name, spec] of Object.entries(deps)) {
if (!spec.startsWith('jsr:')) continue
if (!JSR_SPEC.test(spec)) {
throw new Error(`Invalid JSR specifier "${spec}" for "${name}"; expected jsr:@scope/name[@selector]`)
}
}
}
// run against manifest.dependencies/devDependencies before pnpm install Prevention
- Always copy JSR names from jsr.io including the @scope/ prefix
- Lint package.json in CI for any jsr: specifier that lacks an @scope/name portion
- Remember JSR packages surface on npm as @jsr/scope__name — never hand-write that form yourself
When it happens
Trigger: parseJsrSpecifier is invoked with a raw specifier that starts with `jsr:`, the remainder does not start with `@`, contains no `@` (so it cannot be a name@selector pair), and the optional alias argument is undefined. Concrete shapes: dependency entries like `"foo": "jsr:foo"` or `"foo": "jsr:"` where the scoped name was never typed.
Common situations: Hand-editing package.json and forgetting the `@scope/` part; copying a Deno-style import specifier (`jsr:foo`) into package.json; code generators or codemods that emit the protocol prefix but drop the scoped package name.
Related errors
- MISSING_JSR_PACKAGE_SCOPE
- INVALID_JSR_PACKAGE_NAME
- readPackage hook returned package manifest object's property
- LOGIN_NON_INTERACTIVE
- LOGIN_FAILED
AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17).
Data as JSON: /api/errors/e0f27011f4968956.
Report an issue: GitHub.