Scoped Packages in NPM

Prince BansalPrince Bansal
8 min read

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 packages

  • AWS SDK v3: Split modules like @aws-sdk/client-s3, @aws-sdk/client-dynamodb - allows users to install only what they need

  • Material 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

  1. 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.

  2. Module Resolution: Node.js module resolution algorithm handles this structure seamlessly. When you import @angular/core, Node looks for:

    • node_modules/@angular/core instead of

    • node_modules/core (as it would for a regular package)

  3. Disk Space: The additional folder level doesn't significantly impact disk space usage.

  4. 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:

  1. Create an npm Organization account

  2. Create a new package with your org scope (e.g., @yourorg/package-name)

  3. Publish the new scoped version

  4. Mark the old package as deprecated with a message directing users to the new scoped version

  5. 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.

0
Subscribe to my newsletter

Read articles from Prince Bansal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Prince Bansal
Prince Bansal