8 min readby Youssef.Anpmnamingdeveloper tools

npm Package Naming: Rules, Conventions, and Mistakes to Avoid

A GitHub repo can be renamed in five seconds, no harm done, GitHub redirects the old URL forever. An npm package can't. Once you run npm publish, that name is permanent on the registry, there's no rename button, no redirect, no second chance. Every npm install, every import statement in someone else's codebase, every Stack Overflow answer that pastes your package name, references that exact string forever. Get the name wrong and you don't get a do-over, you get a deprecated package sitting on the registry as a tombstone, with users still pointed at the wrong name. The rules below exist so you don't end up there.

What npm actually requires (not conventions — hard rules)

These aren't style opinions, they're enforced by the registry itself. Get any of them wrong and npm publish fails outright, before your code ever reaches the registry:

  • All lowercase, no exceptions, npm rejects uppercase letters outright.
  • No spaces, and no leading dots or underscores.
  • Must be unique on the registry, unless you publish under a scope.
  • Can't match a Node.js core module name, fs, http, path, and the rest are all off the table.
  • Special characters like ~'!()*aren't allowed.
  • A practical length ceiling around 214 characters, baked into npm's own name-validation tooling, though nothing reasonable should ever get near it.

Every one of these is enforced by validate-npm-package-name, the package npm itself runs internally before anything gets published. None of this is a “best practice,” it's checked at publish time, get any of it wrong and npm publish simply fails before your code reaches the registry.

The rule nobody tells you until it's too late: names are permanent

There's no rename command for a published npm package. Once a name is live on the registry, it's live forever, you can't free it up, transfer it cleanly, or ask npm to swap it for something better. If you need to change a name, the actual process is: publish your code again under the new name, then run npm deprecate on the old package, pointing anyone still using it toward the replacement. The old name keeps working, anyone with it already in their package.jsonkeeps installing it without errors, but it's now dead weight sitting on the registry forever, unmaintained and effectively unfixable. Compare that to a GitHub repo, which redirects automatically the moment you rename it, npm offers no equivalent. Whatever you publish first is what you're stuck with, so don't publish a placeholder name “just to reserve it.”

Lowercase kebab-case is the convention — here's why it survives

npm only strictly requires lowercase, nothing in the registry forces hyphens. But kebab-case, words separated by single hyphens, became the dominant convention anyway, because it matches exactly how the name gets used: typed into npm install, written in an import statement, searched on npmjs.com. react-dom, date-fns, and fast-glob are all lowercase and hyphenated, and each name reads like a description of what the package does, not a label someone made up on the spot. Compare that to snake_case, which is technically legal but almost never used, a snake_case package name looks instantly out of place next to everything else in node_modules, a small signal that the author didn't check what the ecosystem actually does before publishing.

Scoped vs. unscoped: when to use @yourname/package

A scope is a namespace prefix, @username or @orgname, attached to a package name, like @vercel/og or @tanstack/react-query. The scope and the package name together form one unique identifier, even if the unscoped version of that name is already taken by someone else.

Scopes exist to guarantee namespace ownership. Once you claim @yourname on npm, every package you publish under it is yours, no one can take a name you've already used inside your own scope, even if the equivalent unscoped name was grabbed years ago by an unrelated project.

Publishing to your own scope is free, the scope itself costs nothing to create or use for public packages. Private scoped packages are the part that needs a paid plan, public scoped packages publish exactly like any unscoped package.

Say you want to publish json-streamand it's already taken, or close enough to an existing package that it'd be a bad idea anyway. Instead of hunting for a worse name, publish @yourname/json-stream, no conflict, no negotiation with whoever owns the unscoped name.

If you're publishing under an organization, or building a multi-package suite, several related packages meant to ship and version together, scope everything under one @orgfrom day one. That's exactly what Vercel, TanStack, and Babel do, and it pairs directly with the naming conventions for monorepos we cover at the repo level, this is the npm-package-level version of the same decision.

The typosquatting problem and why npm cracked down

npm didn't tighten its naming rules for style reasons, it did it to fight typosquatting. Attackers were publishing malicious packages under names nearly identical to popular ones, swapping a hyphen, dropping a letter, doubling a character, hoping a developer would mistype something like reactnative for react-nativeand pull down malware instead of the real dependency. Because of that history, npm now rejects new packages whose name differs from an existing package only in punctuation, you can't publish a name that's judged too close to one that already exists just because you swapped a hyphen for nothing. This isn't a style preference enforced for tidiness, it's a real security mechanic. It closes off one of the cheapest attack vectors in the entire supply chain, a single careless keystroke during npm install.

Good and bad npm package names: real examples

Abstract advice is easy to agree with and hard to apply when you're staring at a blank name field at midnight. Here are six real before/after pairs and what separates them.

BadGoodWhy
my-utilsdate-fns-tzGeneric name that's certainly taken vs. specific, modular, and instantly clear about what it does
JSONParserfast-json-parsecamelCase is illegal regardless of availability; lowercase and descriptive both work and read naturally
reactnative-thing@yourorg/native-utilsConfusingly close to an existing major package, exactly what npm's typosquatting rules target, vs. scoped and unambiguous
utils2query-stringSequential naming signals no real identity; the name states its one job instead
my-awesome-cli-tool-for-converting-filesconvert-cliTechnically legal but painful to type on every install and import; same purpose, a fraction of the keystrokes
NodeHelpernode-fs-helpersPascalCase is illegal and the name is generic; lowercase with a specific scope (fs) stays searchable

Notice what the bad column has in common: it's either illegal casing, a name so generic it tells you nothing, or padding, numbers, filler words, that makes the name longer without making it clearer. The good column does the opposite every time, lowercase, specific, and exactly as long as it needs to be.

A pre-publish checklist

  1. 01Is it all lowercase, with no special characters? This is the one npm enforces outright, check it first.
  2. 02Run the validator before you publish. This is the exact check npm publish runs internally, so you catch a failure before you've committed to anything.
  3. 03Search npmjs.com for your exact name and close variants. A name that feels "different enough" to you might still get flagged as too similar.
  4. 04Does it collide with a Node.js core module name? It can't, and never will.
  5. 05If it's part of a multi-package suite, should it be scoped under your org instead of standing alone? Scoping costs nothing and avoids a naming fight later.
  6. 06Are you fully comfortable that this name is permanent? If there's any hesitation, that's the moment to keep brainstorming, not after you've published.
npx validate-npm-package-name <your-name>

That's the exact check npm publishruns internally, so running it yourself first means any failure shows up before you've committed to anything.

Name it once, name it right

Lowercase, descriptive, and checked against the registry before you publish, because there's no undo button once it's live. Most npm packages start life as a GitHub repo, so getting that name right first makes the npm name an easy follow-on decision instead of a second naming crisis. If you're starting from scratch, our GitHub Repository Name Generator turns a one-line description into ready-to-use names, a solid foundation before you ever type npm init.

Frequently asked questions

Can npm package names have capital letters?

No. npm package names must be entirely lowercase, the registry rejects uppercase characters in new package names outright, no exceptions.

Can I rename an npm package after publishing?

Not directly. You publish under the new name, then deprecate the old one with npm deprecate, pointing users to the replacement. The old name remains permanently on the registry.

What is a scoped npm package?

A package prefixed with @username or @orgname, like @vercel/og. Scoping guarantees you own that namespace and avoids naming conflicts with existing unscoped packages.

Why did npm restrict naming punctuation?

To fight typosquatting: malicious actors were publishing packages with names nearly identical to popular ones, differing only in punctuation, to trick developers into installing malware by typo.

How long can an npm package name be?

There's a practical ceiling around 214 characters enforced by npm's internal name-validation tooling, but any name anywhere close to that length is impractical to type and should be shortened regardless.