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.
Join the conversation! Your thoughts help the community grow.