Renaming a Package Without Stranding Users
npm has no rename operation. A package's name is part of every consumer's package.json, lockfile and import statements, so "renaming" really means publishing the code under a new name and persuading users to move. Done abruptly — the old name simply stops receiving releases — users stay on the old package indefinitely, missing fixes and security patches without knowing it, and the project splits into two user bases that never meet again. Done well, the move is announced in every channel users see, the old package keeps working, and a final release makes switching nearly automatic. This guide covers the common reasons to rename, the migration steps on both sides, a bridge release that eases the move, and how to protect the old name afterwards.
Common reasons to rename
- Moving into a scope —
acme-loggerbecomes@acme/logger, for ownership and dependency-confusion protection, as discussed in Preventing Dependency Confusion Attacks. - Rebranding — the project or company changes its name.
- Splitting or merging — one package becomes several, or several merge into one.
- Handover — a project moves to an organisation that publishes under its own scope, covered in Transferring Package Ownership to an Organization.
The broader lifecycle is covered in Package Deprecation and Lifecycle.
The migration plan
1. Publish under the new name
Publish the current code under the new name. Decide the version deliberately: continuing the version line (2.8.4 becomes @acme/logger@2.8.4) signals "same package, new name", while starting a new major (3.0.0) is appropriate if the rename coincides with breaking changes. Either way, update name, repository, bugs and homepage in package.json, and publish with the usual release pipeline.
2. Release a bridge version of the old package
A final release of the old package can make migration painless for many users. Two patterns:
Re-export — the old package depends on the new one and re-exports it, so existing code keeps working while pulling in the maintained implementation:
{
"name": "acme-logger",
"version": "2.9.0",
"main": "./index.cjs",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.mjs",
"require": "./index.cjs"
}
},
"dependencies": {
"@acme/logger": "^2.8.4"
}
}
// index.mjs
export * from '@acme/logger';
export { default } from '@acme/logger';
// index.cjs
module.exports = require('@acme/logger');
Warn once — the bridge logs a one-time message on first import pointing to the new name. Keep it to one line, and avoid noisy output in libraries that end up in other libraries' dependency trees.
With a re-export bridge, users on ^2.x of the old name receive the new implementation automatically on their next update, including future fixes published under the new name within the bridge's range.
3. Deprecate the old name
npm deprecate acme-logger "Renamed to @acme/logger. Replace with: npm i @acme/logger (see README)"
Deprecating the whole old package prints the message on every install. Do it after the bridge release, so the latest old version already works well and the warning is paired with a painless path forward. Keep the message short enough to read in a CI log, and include the exact install command for the new name — see Deprecating npm Package Versions.
4. Update everything users read
- The old package's README (published with the bridge release) opens with the new name and a migration snippet.
- The repository README, description and topics use the new name; if the repository was renamed, the hosting platform redirects old URLs.
- Documentation, examples and templates import from the new name.
- Release notes for both the bridge release and the first release under the new name explain the change.
5. Protect the old name
Keep ownership of the old name forever, and never unpublish it. Add it to whatever inventory you keep of owned package names, with the same 2FA and maintainer requirements as active packages, because an old name with many installs remains a valuable target even when nobody publishes to it. An unscoped name that becomes available again can be claimed by someone else, who would then receive every install from users who never migrated — a classic supply-chain attack path.
How users encounter the rename
Users meet the rename through several channels at different times, and each needs to carry the same message.
Existing users who update receive the bridge and see the warning. New users who find the old name through search, blog posts or old documentation see the deprecation notice and the README on the registry page. Users with dependency bots receive replacement pull requests if the bot knows about the rename. Users who never update see nothing — which is exactly why the old name must stay owned and harmless rather than disappear.
Renames inside a monorepo
When the renamed package lives in a monorepo alongside its dependents, the rename touches the whole repository. Rename the package folder and name field, update every internal dependent's package.json to the new name with the workspace protocol, update imports across the repository, and update release configuration (Changesets groups, CODEOWNERS, task-runner overrides) in the same pull request. Keep the bridge package as a small, separate workspace package with the old name, depending on the new one through workspace:^, so the release tooling publishes both in the same release and the bridge's dependency range is rewritten to the new package's real version at publish time. After the bridge release, mark the bridge package as ignored in the release configuration so it is not republished accidentally, but keep its folder so the release history remains understandable.
Helping consumers migrate
For users, a rename is a find-and-replace across imports plus a dependency swap:
npm uninstall acme-logger && npm install @acme/logger
# replace imports across the codebase
grep -rl "from 'acme-logger'" src | xargs sed -i "s/from 'acme-logger'/from '@acme\/logger'/g"
Publish a codemod for larger APIs, or rely on the re-export bridge for users who cannot migrate immediately. Dependency bots such as Renovate can be configured with package replacement rules, so consumers' repositories receive an automatic pull request that swaps the old name for the new one — some bots ship built-in replacement presets for well-known renames.
Worked example: moving a popular package into a scope
A maintainer moves tiny-date-fmt (40,000 weekly downloads) to @tinyfmt/date. They publish @tinyfmt/date@4.3.0 with the same code as tiny-date-fmt@4.3.0, then release tiny-date-fmt@4.4.0 as a re-export bridge depending on @tinyfmt/date@^4.3.0, deprecate tiny-date-fmt with a pointer, and update the README on both. Six months later, 70% of downloads use the new name; the remaining installs of the old name still receive fixes through the bridge. The maintainer keeps the old name and adds it to a yearly ownership review.
Prevention and guardrails
- Never unpublish the old name; keep ownership permanently.
- Continue or deliberately restart the version line, and explain the choice.
- Ship a bridge release before deprecating, where the API allows it.
- Update every surface users read — README, repository, docs, templates.
Frequently Asked Questions
Can npm support rename a package for me? No. Names are part of consumers' manifests and lockfiles, so there is no server-side rename. Publishing under the new name is the only mechanism.
Should the bridge package keep receiving releases? Usually not. With a caret range on the new package, users of the bridge receive new versions of the implementation automatically. Only a breaking change in the new package — outside the bridge's range — would need a new bridge release.
What about TypeScript types during the transition?
Re-export the types too (export * from '@acme/logger' covers them in declaration files), so consumers who still import the old name keep type-checking.
How long should we wait before stopping bridge maintenance? There is nothing to stop: a re-export bridge with a caret range needs no further releases until the new package ships a major. At that point, decide whether to release a new bridge major or leave the old name frozen with its deprecation message.
Will the rename reset download statistics and stars? Download counts are per package name, so the new name starts from zero while the old one declines. Repository stars stay with the repository if you rename it rather than creating a new one; hosting platforms redirect the old URL.
Can we rename a scoped package to a different scope? It is the same process — a scope is part of the name. Publish under the new scope, bridge and deprecate the old scoped name, and keep ownership of the old scope.
Related
- Package Deprecation and Lifecycle covers renames in the context of a package's lifecycle.
- Deprecating npm Package Versions writes the pointer users see on install.
- Publishing Scoped Packages to npm sets up the scope for the new name.
- Splitting a Package Out of a Monorepo often coincides with a rename.