Publishing an npm package is straightforward, but releasing an untested package can create problems for every developer who depends on it. A missing file, incorrect export path, undeclared dependency, or broken build can make a package unusable immediately after publication.
These problems are especially important when maintaining shared libraries, command-line tools, JavaScript utilities, or TypeScript packages used by multiple applications.
npm provides several ways to test a package before making it available to other developers. You can inspect the package contents, install a local package archive, publish a package under a temporary distribution tag, and verify the installed result before promoting it to the default release tag.
This process is often called staged publishing. It helps developers validate the actual package that consumers will install instead of relying only on tests that run inside the source repository.
In this article, you will learn how npm package publishing works, how to test a package safely, how to use distribution tags, and how to prepare a reliable release workflow.
What Is npm Staged Publishing?
npm staged publishing is a release approach in which a package is prepared, tested, and made available through controlled steps before it becomes the default version for consumers.
For example, suppose you maintain a package named my-utils.
Your current stable version is 1.4.0, and you have prepared version 1.5.0 with new functionality.
Instead of immediately making the new version the default release, you can publish it with a separate distribution tag, such as beta.
The release process might look like this:
Source Code
|
v
Run Tests and Build
|
v
Inspect Package Contents
|
v
Create Package Archive
|
v
Test the Archive
|
v
Publish Version 1.5.0 with beta Tag
|
v
Verify Installation and Behavior
|
v
Promote Version 1.5.0 to latestThis workflow separates package preparation from release promotion.
However, publishing with a temporary tag does not make the package private. If the package is published to a public npm registry, users who know its name and version may still be able to install it.
If you need to test a package without making it publicly available, test a local package archive or use a private registry.
Why Testing the Published Package Matters
A package can pass its unit tests and still fail when installed in another project.
This happens because tests often run against source files in the repository. Consumers, however, install the files included in the published package.
Consider a TypeScript package with the following structure:
my-utils/
├── src/
│ └── index.ts
├── test/
│ └── index.test.ts
├── package.json
├── tsconfig.json
└── README.mdYour source code might work correctly, but the package could fail for several reasons:
The compiled JavaScript files are missing.
The
exportsfield points to the wrong location.TypeScript declaration files are not included.
A runtime dependency is missing from
dependencies.Required files are excluded by
.npmignoreor thefilesfield.The package works with one module system but fails with another.
The published archive contains unnecessary development files.
Testing the actual package archive helps identify these problems before they reach consumers.
Step 1: Prepare the Package for Release
Start by reviewing the package configuration.
A TypeScript package might use the following package.json structure:
{
"name": "my-utils",
"version": "1.5.0",
"description": "Reusable JavaScript utility functions",
"main": "./dist/index.cjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "node --test",
"prepack": "npm run build"
}
}This configuration is illustrative. The build process must actually generate the files referenced by main, types, and exports.
In particular, the example assumes that the build produces both an ES module file and a CommonJS file. A standard TypeScript compilation does not automatically generate both outputs from every configuration.
Configure the build system to generate the required formats before using this manifest.
Understanding the Important Fields
The name and version fields identify the package and its release version.
The main field identifies the CommonJS entry point for tools that use that convention. The types field identifies the TypeScript declaration file.
The exports field defines the package entry points that consumers can access. Incorrect export paths are a common cause of installation and import failures.
The files field specifies which files should be included in the published package. This helps prevent test files, development configuration, and other unnecessary files from being distributed.
The prepack script runs before npm creates the package archive. This is useful when the compiled output must exist before packaging.
Before publishing, verify that your build configuration produces every file referenced by the package manifest.
Step 2: Run Tests and Build the Package
Run the package's test suite and build process:
npm ci
npm test
npm run buildThe npm ci command installs dependencies from the lockfile and is useful for repeatable CI builds.
The test command should verify the package's expected behavior, including important edge cases.
The build command should generate all runtime and type declaration files required by consumers.
You should also run static checks, linting, or type checking when these are part of your project's quality requirements.
Do not publish a package merely because the build succeeds. A successful build confirms that the configured compilation completed; it does not prove that the resulting package works in a consumer application.
Step 3: Inspect the Package Contents
Before publishing, inspect the files npm will include in the package.
Run:
npm pack --dry-runThis command previews the package contents without creating the final archive.
Review the output carefully.
For a TypeScript package, you should normally see the compiled JavaScript files, declaration files, package manifest, documentation, and license file when applicable.
Check that the archive does not contain unnecessary files such as local environment files, private credentials, large development datasets, or internal configuration.
You can also generate the actual package archive:
npm packnpm creates a compressed package archive, typically with a name similar to:
my-utils-1.5.0.tgzThis archive represents the package artifact that can be installed by another project.
Testing this archive is more reliable than testing only the source directory because it validates the files included in the distributable package.
Step 4: Install the Package in a Separate Project
Create a temporary consumer project outside the package repository.
mkdir ../my-utils-consumer
cd ../my-utils-consumer
npm init -yInstall the package archive generated in the previous step:
npm install ../my-utils/my-utils-1.5.0.tgzAdjust the relative path to match your directory structure.
Now create a small test file that imports and uses the package.
For example, suppose the package exports an add function:
import { add } from "my-utils";
console.log(add(10, 20));The expected output is:
30Run the test using a compatible Node.js configuration.
If your package supports both CommonJS and ES modules, test both import styles in separate consumer scenarios.
Also test TypeScript compilation when the package advertises TypeScript support.
For example, create a TypeScript file that imports the package and uses its public API. Compile it using the TypeScript version and module-resolution settings that your consumers are expected to use.
This can reveal missing declaration files, incorrect exports, and incompatible type definitions.
Step 5: Publish the Package Under a Temporary Tag
After testing the archive locally, you can publish the package to npm under a temporary distribution tag.
First, make sure you are authenticated to the correct registry and have permission to publish the package.
Then run:
npm publish --tag betaThis command publishes the package version using the beta distribution tag instead of the default latest tag.
Consumers can install the staged version explicitly:
npm install my-utils@betaAlternatively, they can install an exact version:
npm install [email protected]Using an exact version is particularly useful in automated tests because it identifies the precise release being validated.
Distribution tags are pointers to published versions. They do not create a separate package or isolate the release from the registry.
For public packages, remember that publishing makes the package available according to the registry's access and visibility rules. Do not publish confidential code or secrets as part of a staging test.
Step 6: Verify the Published Version
After publishing the beta version, install it in a clean consumer environment.
npm view my-utils dist-tagsThis command displays the package's distribution tags.
To inspect the published version:
npm view my-utils@beta versionThen install the beta package in a separate project:
npm install my-utils@betaRun the package's integration tests against the installed version.
Verify that the package can be imported, that its public methods behave as expected, and that required dependencies are installed correctly.
If your package includes TypeScript declarations, verify type checking as well.
You should also check the package's documentation and usage examples against the actual published API.
Step 7: Promote the Tested Version to Stable
Once the staged version passes the required checks, you can move the default distribution tag to the approved version.
For example:
npm dist-tag add [email protected] latestThis changes the latest tag to point to version 1.5.0.
Consumers who install the package without specifying a version or tag will generally receive the version selected by the registry's default tag behavior.
Promoting a distribution tag does not rebuild or republish the package. It changes which already-published version the tag identifies.
Keep the beta tag if you still need it, or update it according to your release policy.
Before promoting the release, confirm that the exact version being promoted is the one that passed testing.
Staged Publishing Compared With Other Testing Methods
Different testing approaches serve different purposes.
Method | What it validates | Does it publish to npm? |
|---|---|---|
Unit tests | Individual functions and logic | No |
Build verification | Compilation and generated files | No |
| Expected package contents | No |
Local | Packaging and consumer installation | No |
Temporary distribution tag | Published package availability and registry installation | Yes |
Private registry | Package behavior in a controlled registry environment | Yes, to that registry |
Stable release promotion | Default distribution tag selection | No new package publication |
A reliable release process combines several of these methods rather than relying on a single check.
Local archive testing is especially valuable because it validates the package without requiring an initial public release.
Best Practices for Safe npm Releases
Automate the Release Checks
Run tests, builds, package-content inspection, and consumer installation checks in continuous integration.
Automated checks reduce the chance that a release depends on a developer remembering every manual step.
For critical packages, test the packed archive in a separate directory before allowing the release workflow to continue.
Use Immutable Version Numbers
Treat published package versions as immutable release artifacts.
If a version contains a defect, publish a new version rather than attempting to replace the existing release with different contents.
This helps ensure that consumers installing the same version receive a consistent artifact.
Keep Dependencies Accurate
Runtime dependencies belong in dependencies when the published package needs them at runtime and expects npm to install them for consumers.
Packages required only for development and testing generally belong in devDependencies.
Peer dependencies should be declared when the package expects the consuming application to provide a compatible dependency.
An incorrect dependency declaration can cause a package to work inside its repository but fail after installation.
Protect Publishing Credentials
Use appropriately scoped credentials and follow npm's supported authentication and publishing-security practices.
In CI, use protected secrets or trusted publishing mechanisms where supported.
Do not store access tokens in source files or include them in package archives.
Test Backward Compatibility
For libraries used by other applications, check whether the release changes public methods, exported types, configuration options, or runtime behavior.
A package may pass its own tests but still break existing consumers.
Maintain tests for important public APIs and verify breaking changes against the package's versioning policy.
Common Mistakes and Troubleshooting
Problem | Possible cause | Recommended solution |
|---|---|---|
Package imports fail | Incorrect | Inspect the manifest and test imports in a clean project |
TypeScript cannot find declarations | Missing declaration files or incorrect | Verify the build output and package contents |
Required files are missing | Incorrect | Review |
Installation fails because a dependency is missing | Incorrect dependency declaration | Move required runtime dependencies into the appropriate field |
Beta installation returns an unexpected version | Distribution tag points to another release | Inspect tags and install the exact version when testing |
Stable users receive the wrong version | The | Verify the tag after promotion |
Publishing is rejected | Authentication, package permissions, or version conflict | Check registry access and use a new version when required |
Advantages and Disadvantages of Staged Publishing
Staged publishing helps developers verify a package before making it the default release. It reduces the risk of broken exports, missing files, and incorrect dependencies reaching consumers.
Distribution tags also make it easier to share a release with selected testers while keeping the default tag unchanged.
However, a temporary tag does not make a public package private. The package is still published to the registry, and testers must know which tag or version to install.
Staged publishing also introduces additional release steps. Teams need a clear promotion process and should ensure that the version promoted to stable is the exact artifact that was tested.
Summary
npm staged publishing provides a controlled way to validate packages before promoting them to a stable release.
Start by running tests and building the package. Inspect the archive with npm pack --dry-run, install the generated .tgz file in a separate consumer project, and verify imports, dependencies, and TypeScript declarations.
When registry-based testing is required, publish the package with a temporary distribution tag such as beta. After the required checks pass, promote the tested version to latest.
The most important principle is to test the package consumers will actually install, not just the source code in your repository. This makes npm releases more predictable and reduces avoidable problems for downstream developers.


Join the conversation! Your thoughts help the community grow.