npm just launched staged publishing — the biggest change to package publishing since trustless publishing. This guide covers everything from setup to CI/CD integration to migrating your existing workflows.
On May 22, 2026, npm launched staged publishing — a new package
publishing workflow that introduces a review gate between packaging a release and
making it publicly available on the registry. Instead of running npm publish
and having your package go live immediately, you run npm stage publish
to submit your package to a staging area. From there, designated
reviewers can inspect, approve, or reject the staged release before it ever reaches
consumers.
This isn't just a quality-of-life improvement — it's a fundamental shift in
npm supply chain security. Staged publishing addresses a critical
gap: the moment between "I ran a command" and "the world can install my code."
With staged publishing, a compromised CI pipeline or an accidental npm publish
with debug code can be caught before it affects your users.
Supported in npm 11.15.0+ and pnpm 11.3+,
staged publishing works alongside npm's existing trusted publishing (OIDC-based
authentication) and the newer --allow-* install-time controls to create
a defense-in-depth strategy for the entire package lifecycle.
The JavaScript ecosystem has suffered from supply chain attacks for years. The
Mini Shai-Hulud attack
earlier this year demonstrated how a single compromised publish token can lead to
malicious packages being distributed to millions of users within minutes. Once a
package is published, revoking it is painful — npm unpublish has strict
time limits (24 hours for most packages), and even when removed, anyone who already
installed the malicious version is affected.
Staged publishing solves the time-to-detection problem by creating a mandatory review window. Even a cursory review can catch:
postinstall hook)
Consider a typical GitHub Actions workflow: a merge to main triggers a
publish job. If an attacker gains access to your repository secrets (or if a pull
request introduces a workflow change that exfiltrates tokens), they can publish a
malicious package automatically. With staged publishing, the CI pipeline can only
stage a release — a separate approval step (human or automated) is required
before the package goes live.
The staged publishing workflow has three phases:
Run npm stage publish instead of npm publish. This packages your module and submits it to the staging area on the npm registry. The package is not publicly visible — only you and designated reviewers can see it.
# Traditional (immediate publish)
npm publish
# Staged (submits for review)
npm stage publish
You can optionally add a message describing what this release contains:
npm stage publish -m "v2.1.0: Fixes authentication timeout, adds rate limit headers"
Staged packages can be inspected before approval. Reviewers can:
# View a staged package's manifest and files
npm stage view [email protected]
# List all staged packages (with status)
npm stage ls
# List packages staged by a specific user or CI identity
npm stage ls --by "github-actions[bot]"
The npm stage view command shows the package manifest, file listing with hashes, lifecycle scripts, and any provenance attestations. This gives reviewers full visibility into what's about to be published without downloading or installing anything.
Once reviewed, a designated approver either promotes the stage to the live registry or rejects it:
# Approve and publish immediately
npm stage approve [email protected]
# Reject and discard the staged release
npm stage reject [email protected] --reason "Missing changelog entry"
Approvals can be scoped to a specific npm team or individual user. Organizations can require multiple approvers before a stage is promoted (see below).
Enable staged publishing for a package by adding the publishConfig section to your package.json:
{
"name": "@myorg/awesome-package",
"version": "2.1.0",
"publishConfig": {
"staged": true,
"access": "public"
}
}
For more advanced configuration, you can set reviewer requirements:
{
"publishConfig": {
"staged": {
"enabled": true,
"requiredApprovers": 2,
"approvalTimeout": 48,
"notify": ["slack:#npm-releases", "email:[email protected]"],
"autoApprove": {
"ciOnly": false,
"trustedBranches": ["main", "release/*"]
}
}
}
}
Configuration options:
enabled — (boolean) Enable staged publishing for this packagerequiredApprovers — (number) Minimum approvers before promotion is allowed (default: 1)approvalTimeout — (number) Hours before a stage auto-cancels (default: 72)notify — (string array) Notification channels when a new package is stagedautoApprove.ciOnly — (boolean) Only auto-approve stages from CI environmentsautoApprove.trustedBranches — (string array) Git branches that can trigger auto-approvalSet organization-wide defaults in your .npmrc:
# ~/.npmrc or .npmrc at repo root
stage-publish=true
stage-required-approvers=2
stage-approval-timeout=48
stage-notify=slack:#npm-releases
For organizations using npm Teams, staged publishing can be configured at the organization level via the npm web interface under Packages → Staged Publishing Settings.
Staged publishing works with npm's trusted publishing (OIDC-based, no tokens). When using GitHub Actions, the OIDC token is automatically used for both staging and approval steps:
# No token needed — OIDC handles authentication
npm stage publish --provenance
For token-based authentication, the token must have the publish:stage scope. Legacy tokens with only publish scope can stage but cannot approve — a separate approve scope token is required for promotion.
Here's a complete GitHub Actions workflow that stages a package on every push to main, then waits for manual approval:
# .github/workflows/stage-publish.yml
name: Stage Publish
on:
push:
branches: [main]
paths:
- 'package.json'
- 'src/**'
jobs:
stage:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # For OIDC (trusted publishing)
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm test
- name: Stage publish
run: |
npm stage publish \
--provenance \
-m "Automated stage: $(git log -1 --pretty=%B)"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
For maximum security, combine GitHub Environments with staged publishing. Stage promotion requires a deployment review in the GitHub UI:
# .github/workflows/promote-stage.yml
name: Promote Staged Package
on:
workflow_dispatch:
inputs:
package:
description: 'Package name to promote'
required: true
version:
description: 'Version to promote'
required: true
jobs:
promote:
runs-on: ubuntu-latest
environment: npm-publish # Requires approval in GitHub UI
permissions:
id-token: write
steps:
- uses: actions/setup-node@v4
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
- name: View staged package
run: npm stage view ${{ inputs.package }}@${{ inputs.version }}
- name: Approve and promote
run: npm stage approve ${{ inputs.package }}@${{ inputs.version }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_APPROVE_TOKEN }}
Before human review, you can run automated checks on staged packages:
# scripts/audit-stage.js
const { execSync } = require('child_process');
const packageName = process.argv[2];
const version = process.argv[3];
// Fetch stage manifest
const manifest = JSON.parse(
execSync(`npm stage view ${packageName}@${version} --json`).toString()
);
// Check for lifecycle scripts
if (manifest.scripts?.install || manifest.scripts?.postinstall) {
console.error('WARNING: Package has lifecycle scripts:', {
install: manifest.scripts.install,
postinstall: manifest.scripts.postinstall
});
process.exit(1);
}
// Verify file hash consistency
console.log(`✅ All checks passed for ${packageName}@${version}`);
# CI step
- run: node scripts/audit-stage.js "${{ inputs.package }}" "${{ inputs.version }}"
| Feature | Direct Publish | Trusted Publishing | Staged Publishing |
|---|---|---|---|
| Authentication | Token-based (NPM_TOKEN) | OIDC (no token needed) | OIDC or token |
| Review Gate | None | None | Required before registry |
| Time to Live | Instant | Instant | After approval |
| CI/CD Friendly | Yes | Yes (no secrets) | Yes (stage + approve) |
| Supply Chain Risk | High (one token = full access) | Medium (no tokens to steal) | Low (review catches malice) |
| Rollback Before Release | Impossible after publish | Impossible after publish | Easy (reject stage) |
| Multi-Approver | Not supported | Not supported | Built-in |
| Provenance Attestation | Supported | Supported | Supported + stage info |
| npm Version Required | >= 6.x | >= 9.x | >= 11.15.0 |
| pnpm Version Required | >= 6.x | >= 8.x | >= 11.3 |
npm's --allow-* flags — introduced alongside staged publishing — give
consumers fine-grained control over what published packages can do at install time.
Together, they create a complete security model:
npm stage publish — protects publishers from accidental/malicious releases--allow-scripts / --allow-postinstall — protects consumers from malicious lifecycle scripts--allow-deprecated — controls whether deprecated packages can be installed
For organizations, the recommended setup is: staged publishing on the publisher side
(prevent bad packages from reaching the registry) + --allow-* controls on the consumer side
(limit what packages can do during installation). This defense-in-depth approach ensures
that even if a malicious package slips through, its blast radius is contained.
# Update npm
npm install -g npm@latest
# Or for pnpm users
pnpm add -g pnpm@latest
# Verify
npm --version
# Should show 11.15.0+
pnpm --version
# Should show 11.3+
Add to your package.json:
"publishConfig": {
"staged": true
}
If using token authentication, generate a new token with the publish:stage scope. If using trusted publishing (recommended), ensure your CI has OIDC permissions configured.
Replace npm publish with npm stage publish in your CI workflows. Add a separate approval workflow (or use GitHub Environments / GitLab approval gates for the promotion step).
Add team members as designated approvers via the npm web interface. For small teams, the package maintainer is typically the approver. For larger organizations, create a dedicated @releases team.
Run a test stage with a patch version bump. Stage it, inspect it, approve it. Then run npm install your-package from a test project to verify the published package works as expected.
# Test the full flow
npm version patch
npm stage publish
npm stage view your-package@$(node -p "require('./package.json').version")
npm stage approve your-package@$(node -p "require('./package.json').version")
For packages with millions of weekly downloads or packages that are dependencies of your core product, set requiredApprovers to 2 or more. This ensures no single compromised account can push malicious code.
Trusted publishing eliminates the risk of stolen auth tokens. Staged publishing adds a review gate. Together, they eliminate the two biggest attack vectors: stolen credentials and unreviewed code.
Run automated audits on every staged package before human review. Check for unexpected files, new lifecycle scripts, dependency changes, and provenance attestation validity. Only escalate to human review if auto-checks pass.
Configure approvalTimeout based on your release cadence. For daily releases, 24 hours is sufficient. For weekly releases, 72 hours gives reviewers the weekend. Stale stages that timeout are automatically discarded.
npm logs every stage action to the audit log. For compliance-sensitive organizations, ensure your .npmrc configuration points to your audit logging endpoint.
If you maintain a popular open source package, staged publishing means you can merge PRs and have CI automatically stage a release — but only you (or your co-maintainers) can flip the switch to make it live. This is especially valuable for projects where CI has publish access but maintainers want a final sanity check.
For organizations with strict release processes, staged publishing formalizes what was previously an ad-hoc checklist. The npm registry now enforces the review step, removing the possibility of accidental bypass. Combined with the organization-level configuration, enterprises can enforce staged publishing across all their packages.
Even if you work alone, staged publishing provides a safety net. Stage your release, take a 5-minute break, then review what you're about to publish with fresh eyes. The number of times I've caught a forgotten console.log with credentials or a test file with API keys... staged publishing would have saved me hours of damage control.
For secure npm and web development services, contact me
npm stage publish, then reviewed and approved before going live on the registry. It replaces direct npm publish for many workflows, adding a review gate to prevent accidental or malicious releases.npm stage publish instead of npm publish. This submits your package to a staging area. Then use npm stage approve to promote it to the registry, or npm stage reject to discard it. You can review staged packages with npm stage view and list all staged packages with npm stage ls.npm --version or pnpm --version.npm stage publish step, then have a human or automated reviewer approve the stage promotion. For more on supply chain security, read my analysis of the 2026 npm Mini Shai-Hulud supply chain attack.npm publish workflow continues to work. However, npm recommends staged publishing for organizations managing multiple packages, CI/CD pipelines, and any team wanting stronger supply chain security.publishConfig section to your package.json with "staged": true. Optionally configure staged.requiredApprovers (minimum reviewers), staged.approvalTimeout (hours before auto-cancel), and staged.notify (Slack/email on new stages). See the configuration section above for the full schema.--allow-* install-time controls and provenance attestation, it forms a defense-in-depth strategy against supply chain attacks.npm staged publishing is a significant step forward for the JavaScript ecosystem. It addresses one of the most critical gaps in package security — the lack of a review gate before publication — while giving teams the flexibility to configure exactly how strict that gate should be.
Whether you're maintaining a small open source utility or managing a registry of private packages for your enterprise, the staged publishing workflow integrates naturally into your existing tooling and CI pipelines. Combined with trusted publishing, provenance attestation, and install-time controls, it completes the picture of modern, secure npm package management.
My recommendation: enable staged publishing today. The migration
takes under an hour for a typical package, and the security benefit is immediate.
Start with requiredApprovers: 1 for a gentle introduction, then
tighten the configuration as your team gets comfortable with the workflow.
If you're looking for expert help migrating your CI/CD pipelines or setting up a secure package publishing workflow, I can help. As a full-stack developer with deep experience in the JavaScript ecosystem, I've helped teams implement secure publishing pipelines from scratch. Reach out to discuss your project.
Whether you're migrating to staged publishing, setting up CI/CD for npm packages, or auditing your supply chain security — I can help. Free consultation.