Scoped Packages in NPM


Hey there! Just so you know - I didn't write this article myself. I had a chat with AI, asked a bunch of questions I was curious about, and this is what came out of it. I'm posting these notes because they help me remember stuff and might be useful for you too. Enjoy!"
Ever wondered why some npm packages start with an @
symbol? If you've used modern JavaScript frameworks, you've likely encountered packages like @angular/core
, @babel/preset-env
, or @mui/material
. That @
prefix isn't just a styling choice—it represents a powerful feature in the npm ecosystem: scoped packages.
This comprehensive guide will demystify npm scoped packages, explaining what they are, why they matter, and how they can benefit your development workflow. Whether you're working solo or managing packages for a large organization, understanding scoped packages is essential for modern JavaScript development.
What Are Scoped Packages?
Scoped packages in npm are packages that belong to a namespace, denoted by the @
symbol followed by a scope name. The complete format is @scope/package-name
. This namespace mechanism was introduced in npm 2.0 to solve several problems in the npm ecosystem.
The Anatomy of Scoped Packages
Naming Convention
Regular packages:
package-name
(e.g.,express
,lodash
)Scoped packages:
@scope/package-name
(e.g.,@angular/core
,@babel/preset-env
)
Directory Structure
Scoped packages are stored differently in the node_modules
directory:
node_modules/
├── express/ # Regular package
├── lodash/ # Regular package
├── @angular/ # Scope directory
│ ├── core/ # Scoped package
│ ├── common/ # Scoped package
│ └── router/ # Scoped package
└── @babel/ # Another scope directory
├── core/ # Scoped package
└── preset-env/ # Scoped package
How Scoped Packages Appear in package.json
Dependencies
{
"dependencies": {
"express": "^4.18.2",
"lodash": "^4.17.21",
"@angular/core": "^15.0.0",
"@babel/core": "^7.20.5"
}
}
The Benefits of Scoped Packages
1. Namespace Management
Scoped packages solve the naming collision problem. As the npm registry grew to millions of packages, finding unique names became increasingly difficult. Scopes allow multiple packages with the same short name to exist under different namespaces.
Examples:
@company-a/utils
and@company-b/utils
can coexist without conflicts@nestjs/core
and@angular/core
- both use "core" but for different frameworks@mui/material
and@chakra-ui/react
- UI libraries with clear ownership
This prevents package name squatting and reduces the need for creative naming just to achieve uniqueness.
2. Organization and Grouping
Scopes make it easier to group related packages together, improving discoverability, maintenance, and conveying official relationships.
Real-world examples:
Angular Framework:
@angular/core
,@angular/router
,@angular/forms
- clearly indicates these are official Angular packagesAWS SDK v3: Split modules like
@aws-sdk/client-s3
,@aws-sdk/client-dynamodb
- allows users to install only what they needMaterial UI:
@mui/material
,@mui/icons-material
,@mui/system
- unified branding under one scope
This organizational structure makes it easier for:
Developers to identify official packages vs community packages
Maintainers to manage versioning and dependencies between related packages
Teams to understand which packages belong together
3. Access Control and Permissions
Normal (Unscoped) Packages Permission Model:
Basic maintainer system with limited controls
All maintainers have full publish and administrative access
No concept of read-only vs. read-write permissions
No team-based access control
No hierarchical permission structure
Cannot group packages under a namespace with shared permissions
# Normal package permission commands
npm owner add username normal-package
npm owner rm username normal-package
npm owner ls normal-package
What You Cannot Do With Normal Packages:
Create team-based access levels
Set different permission tiers (read-only, read-write)
Group users into teams with shared permissions
Revoke access for multiple packages at once when someone leaves
Create private packages without an individual paid account
Scoped Packages Permission Model
With scoped packages (which require an npm Organization account - paid service), you get:
Real-World Example: Enterprise Development Team
Imagine a company called Acme Corporation with three teams:
Frontend Team
Backend Team
DevOps Team
Scenario: Acme creates an npm organization called @acme
and adds all their developers to it:
# Creating teams within the organization
npm team create @acme:frontend
npm team create @acme:backend
npm team create @acme:devops
They create several packages:
@acme/ui-components
- Shared React components@acme/api-client
- Generated API client@acme/config
- Shared configuration@acme/deployment-tools
- Internal deployment scripts
Access Control Implementation:
# Assigning specific access levels
npm access grant read-write @acme:frontend @acme/ui-components
npm access grant read-only @acme:backend @acme/ui-components
npm access grant read-write @acme:backend @acme/api-client
npm access grant read-only @acme:frontend @acme/api-client
npm access grant read-write @acme:devops @acme/deployment-tools
npm access grant read-only @acme:frontend,@acme:backend @acme/config
Real Benefits:
Frontend developers can publish new versions of UI components but only consume the API client
Backend developers can update the API client but can't modify UI components
All teams can read the shared configuration, but only certain authorized people can update it
DevOps tools remain restricted to the DevOps team
This fine-grained control would be difficult to achieve without scopes, especially while maintaining clear ownership and organization of packages.
Enterprise Security Benefits
Prevent unauthorized publishing of malicious versions
Audit who published which version of a package
Restrict sensitive internal packages to specific teams
Working with Scoped Packages
Installation
Installing scoped packages works just like regular packages:
# Install a scoped package
npm install @angular/core
# Install and save as a dependency
npm install --save @angular/core
# Install a specific version
npm install @angular/core@15.0.0
Usage in Code
Importing scoped packages follows the same pattern as regular packages:
// CommonJS
const angularCore = require('@angular/core');
// ES Modules
import { Component } from '@angular/core';
How Scoped Packages Affect Your node_modules Structure
Directory Structure Differences
Scoped packages create a nested directory structure in your node_modules
folder, unlike regular packages which are installed directly at the root level:
node_modules/
├── express/ # Regular package - flat structure
├── lodash/ # Regular package - flat structure
├── @angular/ # Scope directory - nested structure
│ ├── core/ # Actual package inside scope
│ ├── common/ # Actual package inside scope
│ └── router/ # Actual package inside scope
└── @babel/ # Another scope directory
├── core/ # Actual package inside scope
└── preset-env/ # Actual package inside scope
Implications of This Structure
Path Length: Scoped packages result in longer file paths since they're nested one level deeper. This rarely causes issues on modern systems but could occasionally cause problems on Windows with its path length limitations.
Module Resolution: Node.js module resolution algorithm handles this structure seamlessly. When you import
@angular/core
, Node looks for:node_modules/@angular/core
instead ofnode_modules/core
(as it would for a regular package)
Disk Space: The additional folder level doesn't significantly impact disk space usage.
Package Deduplication: Package managers like npm and Yarn perform deduplication the same way for scoped and non-scoped packages. The scope is just considered part of the package name.
Visual Differentiation
This nested structure makes it easier to visually identify related packages when exploring your node_modules
directory, as all packages from the same organization are grouped together under their scope folder.
Common Use Cases for Scoped Packages
1. Enterprise Development
Large companies use scopes to organize internal packages:
@company/ui-components
@company/api-clients
@company/utils
2. Framework Ecosystems
Major frameworks use scopes to organize their ecosystem:
Angular:
@angular/*
React:
@react-*/*
Vue:
@vue/*
3. Monorepos
Monorepos often use scoped packages to manage multiple packages in a single repository:
@project/client
@project/server
@project/shared
Limitations and Considerations
Paid Requirement for Private Packages
Private scoped packages require an npm paid plan
Public scoped packages are free but visible to everyone
Migration Challenges
Moving from a regular package to a scoped package requires a major version bump
Users will need to update their import statements
Registry Limitations
Some alternate npm registries might have limited support for scoped packages
Self-hosted registries need proper configuration to handle scopes
Frequently Asked Questions about Scoped Packages
1. Can I convert an existing regular package to a scoped package?
Yes, but it requires publishing under a new name. Since package-name
and @scope/package-name
are treated as completely different packages, this is considered a breaking change and requires a major version bump. Users will need to update their import statements and package.json dependencies.
2. Do scoped packages work with all package managers?
Most modern JavaScript package managers support scoped packages, including npm, Yarn, and pnpm. However, very old versions might have limited support. Always ensure you're using an up-to-date package manager.
3. Are there any naming restrictions for scopes?
Scope names follow similar rules to package names but cannot contain dots or underscores. They can use hyphens, however. For example, @my-company/package
is valid, but @my_company/package
is not.
4. Do I need an npm Organization account to use the advanced permission features?
Yes, you need a paid npm Organization account to access the advanced permission features that come with scoped packages. The basic npm free tier only allows for simple maintainer management of public packages.
5. Can I convert my existing normal packages to use the scoped package permission system?
Yes, but it requires creating a new scoped package and deprecating the old one. You would need to:
Create an npm Organization account
Create a new package with your org scope (e.g.,
@yourorg/package-name
)Publish the new scoped version
Mark the old package as deprecated with a message directing users to the new scoped version
Eventually, you might want to transfer ownership of the original package to npm to prevent security issues
This is considered a breaking change and should be communicated clearly to your users.
Conclusion
Scoped packages provide a powerful way to organize, manage, and secure npm packages. They've become an essential part of the JavaScript ecosystem, particularly for enterprise development, framework maintainers, and open-source communities.
By understanding how scoped packages work and implementing them in your projects, you can better organize your code, avoid naming conflicts, and take advantage of npm's permission system to secure your packages.
Subscribe to my newsletter
Read articles from Prince Bansal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
