diff --git a/content/nav.yml b/content/nav.yml
index f042ffffc86..a72f424dd63 100644
--- a/content/nav.yml
+++ b/content/nav.yml
@@ -158,6 +158,8 @@
url: /generating-provenance-statements
- title: Trusted publishing with OIDC
url: /trusted-publishers
+ - title: Staged publishing
+ url: /staged-publishing
- title: About ECDSA registry signatures
url: /about-registry-signatures
- title: Verifying ECDSA registry signatures
diff --git a/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-private-packages.mdx b/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-private-packages.mdx
index 9c6de2e7447..8031e953743 100644
--- a/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-private-packages.mdx
+++ b/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-private-packages.mdx
@@ -81,16 +81,19 @@ npm install my-package
By default, scoped packages are published with private visibility.
-
+There are two ways to publish your package to the npm registry:
-**Important:** Publishing to npm requires either:
+1. [Direct publishing](#direct-publishing)
+2. [Staged publishing](#staged-publishing)
-- Two-factor authentication (2FA) enabled on your account, OR
-- A granular access token with bypass 2FA enabled
+### Direct publishing
-For more information, see the npm documentation on [requiring 2FA for package publishing](/requiring-2fa-for-package-publishing-and-settings-modification).
+To publish directly with `npm publish`, you need either:
-
+- Two-factor authentication (2FA) enabled on your account, or
+- A granular access token (GAT) with bypass 2FA enabled
+
+For more information, see the npm documentation on [requiring 2FA for package publishing](/requiring-2fa-for-package-publishing-and-settings-modification).
1. On the command line, navigate to the root directory of your package.
@@ -110,6 +113,42 @@ For more information, see the npm documentation on [requiring 2FA for package pu
For more information on the `publish` command, see the [CLI documentation][cli-publish].
+### Staged publishing
+
+Instead of publishing directly, you can stage your package and approve it later. Staging the package does not require 2FA, which allows CI workflows to submit a package to the staging area. Before the package is published to the registry, a maintainer must review and approve it with 2FA.
+
+A GAT with bypass 2FA does not bypass the 2FA check during staged package approval.
+
+1. On the command line, navigate to the root directory of your package.
+
+ ```
+ cd /path/to/package
+ ```
+
+2. To stage your package, run:
+
+ ```
+ npm stage publish
+ ```
+
+ This submits your package to a staging area.
+
+3. To check that your package has been staged, use either of the following methods:
+ - In the CLI, run `npm stage list ` to find the staged package and its stage ID.
+ - On [npmjs.com](https://www.npmjs.com), open the **Staged Packages** tab to review staged packages.
+
+4. To approve and publish the staged package, use one of the following methods:
+ - In the CLI, run the `npm stage approve ` command.
+ - On [npmjs.com](https://www.npmjs.com), review the staged package in the **Staged Packages** tab, then click **Approve**.
+
+
+
+ **Note:** You will be prompted for 2FA verification regardless of whether you approve the package in the CLI or on [npmjs.com](https://www.npmjs.com). Once approved, the package is published to the live registry.
+
+
+
+For the full staged publishing workflow, including reviewing, inspecting, and rejecting staged packages, see [Staged publishing][staged-publishing].
+
[scopes]: about-scopes
[private-pkgs]: about-private-packages
[user-signup]: https://www.npmjs.com/signup
@@ -123,3 +162,4 @@ For more information on the `publish` command, see the [CLI documentation][cli-p
[config-2fa]: /configuring-two-factor-authentication
[creating-token]: /creating-and-viewing-access-tokens
[requiring-2fa]: /requiring-2fa-for-package-publishing-and-settings-modification
+[staged-publishing]: /staged-publishing
diff --git a/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-scoped-public-packages.mdx b/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-scoped-public-packages.mdx
index 8a46633c297..a0a29353648 100644
--- a/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-scoped-public-packages.mdx
+++ b/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-scoped-public-packages.mdx
@@ -77,16 +77,19 @@ npm install /path/to/my-test-package
By default, scoped packages are published with private visibility. To publish a scoped package with public visibility, use `npm publish --access public`.
-
+There are two ways to publish your package to the npm registry:
-**Important:** Publishing to npm requires either:
+1. [Direct publishing](#direct-publishing)
+2. [Staged publishing](#staged-publishing)
-- Two-factor authentication (2FA) enabled on your account, OR
-- A granular access token with bypass 2FA enabled
+### Direct publishing
-For more information, see the npm documentation on [requiring 2FA for package publishing](/requiring-2fa-for-package-publishing-and-settings-modification).
+To publish directly with `npm publish --access public`, you need either:
-
+- Two-factor authentication (2FA) enabled on your account, or
+- A granular access token (GAT) with bypass 2FA enabled
+
+For more information, see the npm documentation on [requiring 2FA for package publishing](/requiring-2fa-for-package-publishing-and-settings-modification).
1. On the command line, navigate to the root directory of your package.
@@ -112,6 +115,42 @@ For more information, see the npm documentation on [requiring 2FA for package pu
For more information on the `publish` command, see the [CLI documentation][cli-publish].
+### Staged publishing
+
+Instead of publishing directly, you can stage your package and approve it later. Staging the package does not require 2FA, which allows CI workflows to submit a package to the staging area. Before the package becomes publicly available, a maintainer must review and approve it with 2FA.
+
+A GAT with bypass 2FA does not bypass the 2FA check during staged package approval.
+
+1. On the command line, navigate to the root directory of your package.
+
+ ```
+ cd /path/to/my-test-package
+ ```
+
+2. To stage your scoped public package, run:
+
+ ```
+ npm stage publish
+ ```
+
+ This submits your package to a staging area.
+
+3. To check that your package has been staged, use either of the following methods:
+ - In the CLI, run `npm stage list ` to find the staged package and its stage ID.
+ - On [npmjs.com](https://www.npmjs.com), open the **Staged Packages** tab to review staged packages.
+
+4. To approve and publish the staged package, use one of the following methods:
+ - In the CLI, run the `npm stage approve ` command.
+ - On [npmjs.com](https://www.npmjs.com), review the staged package in the **Staged Packages** tab, then click **Approve**.
+
+
+
+ **Note:** You will be prompted for 2FA verification regardless of whether you approve the package in the CLI or on [npmjs.com](https://www.npmjs.com). Once approved, the package is published to the live registry.
+
+
+
+For the full staged publishing workflow, including reviewing, inspecting, and rejecting staged packages, see [Staged publishing][staged-publishing].
+
[scopes]: /about-scopes
[user-signup]: https://www.npmjs.com/signup
[create-org]: https://www.npmjs.com/signup?next=/org/create
@@ -125,3 +164,4 @@ For more information on the `publish` command, see the [CLI documentation][cli-p
[config-2fa]: /configuring-two-factor-authentication
[creating-token]: /creating-and-viewing-access-tokens
[requiring-2fa]: /requiring-2fa-for-package-publishing-and-settings-modification
+[staged-publishing]: /staged-publishing
diff --git a/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-unscoped-public-packages.mdx b/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-unscoped-public-packages.mdx
index 608b53385d7..bea4824c653 100644
--- a/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-unscoped-public-packages.mdx
+++ b/content/packages-and-modules/contributing-packages-to-the-registry/creating-and-publishing-unscoped-public-packages.mdx
@@ -58,16 +58,19 @@ npm install path/to/my-package
## Publishing unscoped public packages
-
+There are two ways to publish your package to the npm registry:
-**Important:** Publishing to npm requires either:
+1. [Direct publishing](#direct-publishing)
+2. [Staged publishing](#staged-publishing)
-- Two-factor authentication (2FA) enabled on your account, OR
-- A granular access token with bypass 2FA enabled
+### Direct publishing
-For more information, see the npm documentation on [requiring 2FA for package publishing](/requiring-2fa-for-package-publishing-and-settings-modification).
+To publish directly with `npm publish`, you need either:
-
+- Two-factor authentication (2FA) enabled on your account, or
+- A granular access token (GAT) with bypass 2FA enabled
+
+For more information, see the npm documentation on [requiring 2FA for package publishing](/requiring-2fa-for-package-publishing-and-settings-modification).
1. On the command line, navigate to the root directory of your package.
@@ -91,6 +94,42 @@ For more information, see the npm documentation on [requiring 2FA for package pu
For more information on the `publish` command, see the [CLI documentation][cli-publish].
+### Staged publishing
+
+Instead of publishing directly, you can stage your package and approve it later. Staging the package does not require 2FA, which allows CI workflows to submit a package to the staging area. Before the package becomes publicly available, a maintainer must review and approve it with 2FA.
+
+A GAT with bypass 2FA does not bypass the 2FA check during staged package approval.
+
+1. On the command line, navigate to the root directory of your package.
+
+ ```
+ cd /path/to/package
+ ```
+
+2. To stage your package, run:
+
+ ```
+ npm stage publish
+ ```
+
+ This submits your package to a staging area.
+
+3. To check that your package has been staged, use either of the following methods:
+ - In the CLI, run `npm stage list ` to find the staged package and its stage ID.
+ - On [npmjs.com](https://www.npmjs.com), open the **Staged Packages** tab to review staged packages.
+
+4. To approve and publish the staged package, use one of the following methods:
+ - In the CLI, run the `npm stage approve ` command.
+ - On [npmjs.com](https://www.npmjs.com), review the staged package in the **Staged Packages** tab, then click **Approve**.
+
+
+
+ **Note:** You will be prompted for 2FA verification regardless of whether you approve the package in the CLI or on [npmjs.com](https://www.npmjs.com). Once approved, the package is published to the live registry.
+
+
+
+For the full staged publishing workflow, including reviewing, inspecting, and rejecting staged packages, see [Staged publishing][staged-publishing].
+
[pkg-viz]: package-scope-access-level-and-visibility
[user-signup]: https://www.npmjs.com/signup
[create-org]: https://www.npmjs.com/signup?next=/org/create
@@ -103,3 +142,4 @@ For more information on the `publish` command, see the [CLI documentation][cli-p
[config-2fa]: /configuring-two-factor-authentication
[creating-token]: /creating-and-viewing-access-tokens
[requiring-2fa]: /requiring-2fa-for-package-publishing-and-settings-modification
+[staged-publishing]: /staged-publishing
diff --git a/content/packages-and-modules/securing-your-code/staged-publishing.mdx b/content/packages-and-modules/securing-your-code/staged-publishing.mdx
new file mode 100644
index 00000000000..7f237b18e5e
--- /dev/null
+++ b/content/packages-and-modules/securing-your-code/staged-publishing.mdx
@@ -0,0 +1,122 @@
+---
+title: Staged publishing for npm packages
+---
+
+Staged publishing adds an approval step before packages go live on the npm registry. Instead of publishing directly with `npm publish`, you can submit packages to a staging area with `npm stage publish`. A maintainer must then review and explicitly approve the staged package — with two-factor authentication (2FA) via the CLI or [npmjs.com](https://www.npmjs.com) — before it becomes publicly available.
+
+Staged publishing is useful when you want an extra review step before a package version becomes available on the registry.
+
+
+
+**Note:** Staged publishing requires [npm CLI](https://docs.npmjs.com/cli/v11) version 11.15.0 or later and Node version 22.14.0 or higher.
+
+
+
+## How staged publishing works
+
+Staged publishing has three steps:
+
+1. [Stage a package](#stage-a-package)
+2. [Review a staged package](#review-a-staged-package)
+3. [Approve a staged package](#approve-a-staged-package)
+
+## Prerequisites
+
+Before using staged publishing, ensure the following:
+
+- You have **publish access** to the package
+- The package **already exists** on the npm registry — you cannot stage a brand-new package
+- **2FA is enabled** on your npm account
+
+## Stage a package
+
+1. On the command line, navigate to the root directory of your package.
+
+ ```
+ cd /path/to/package
+ ```
+
+2. To stage your package, run:
+
+ ```
+ npm stage publish
+ ```
+
+ This submits your package to a staging area.
+
+
+
+**Note:** `npm stage publish` does not require 2FA.
+
+
+
+## Review a staged package
+
+After you stage a package, you can inspect it in the CLI or on [npmjs.com](https://www.npmjs.com).
+
+#### Using the CLI
+
+To list staged packages you have access to:
+
+```
+npm stage list []
+```
+
+To view details for a specific staged package:
+
+```
+npm stage view
+```
+
+To download the staged package tarball for inspection:
+
+```
+npm stage download
+```
+
+#### Using npmjs.com
+
+Open the **Staged Packages** tab to review staged packages and find the package you want to approve.
+
+
+
+## Approve a staged package
+
+To publish a staged package to the registry, approve it with 2FA.
+
+#### Using the CLI
+
+To approve a staged package and publish it to the live registry:
+
+```
+npm stage approve
+```
+
+#### Using npmjs.com
+
+On [npmjs.com](https://www.npmjs.com), review the staged package in the **Staged Packages** tab, then click **Approve**.
+
+
+
+
+
+**Note:** You will be prompted for 2FA verification whether you approve the package in the CLI or on [npmjs.com](https://www.npmjs.com).
+
+
+
+## Using staged publishing with trusted publishers
+
+If you use [trusted publishing (OIDC)](/trusted-publishers) from CI/CD, you can use staged publishing to submit a package for review before it goes live. A maintainer must still review and approve the staged package with 2FA.
+
+For more information on configuring trusted publisher permissions, see "[Trusted publishing for npm packages](/trusted-publishers#configuring-allowed-actions)."
+
+## Learn more
+
+- [Trusted publishing for npm packages](./trusted-publishers)
+- [Generating provenance statements](./generating-provenance-statements)
diff --git a/content/packages-and-modules/securing-your-code/trusted-publishers.mdx b/content/packages-and-modules/securing-your-code/trusted-publishers.mdx
index 4c52609b4d5..b5497acc493 100644
--- a/content/packages-and-modules/securing-your-code/trusted-publishers.mdx
+++ b/content/packages-and-modules/securing-your-code/trusted-publishers.mdx
@@ -45,6 +45,7 @@ Configure the following fields:
- Must include the `.yml` or `.yaml` extension
- The workflow file must exist in `.github/workflows/` in your repository
- **Environment name** (optional): If using [GitHub environments](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) for deployment protection
+- **Allowed actions** (required): Select which actions this trusted publisher can perform — `npm publish`, `npm stage publish`, or both. At least one must be selected. See [Staged publishing for npm packages](/staged-publishing) for more information on staged publishing.
@@ -57,6 +58,7 @@ Configure the following fields:
- **Top-level CI file path** (required): The path to your CI file (e.g., `.gitlab-ci.yml`)
- Must include the `.yml` extension
- **Environment name** (optional): If using [GitLab environments](https://docs.gitlab.com/ee/ci/environments/)
+- **Allowed actions** (required): Select which actions this trusted publisher can perform — `npm publish`, `npm stage publish`, or both. At least one must be selected.
@@ -69,6 +71,7 @@ Configure the following fields:
- **Pipeline definition ID** (required): The pipeline definition ID (UUID format). You may find it from your CircleCI Project Settings under Project Setup page.
- **VCS origin** (required): The VCS origin URL for your project (e.g., `github.com/myorg/myrepo`)
- **Context IDs** (optional): Restrict publishing to jobs using specific CircleCI contexts. You may find them from your CircleCI Organization Settings Contexts.
+- **Allowed actions** (required): Select which actions this trusted publisher can perform — `npm publish`, `npm stage publish`, or both. At least one must be selected.
@@ -78,6 +81,12 @@ Configure the following fields:
+
+
+**Note:** Trusted publisher configurations created before May 20, 2026 are automatically set to allow `npm publish` only — no behavior change occurs for current workflows. Configurations created after May 20, 2026 require you to explicitly select at least one allowed action.
+
+
+
### Step 2: Configure your CI/CD workflow
#### GitHub Actions configuration
@@ -110,7 +119,7 @@ jobs:
- run: npm ci
- run: npm run build --if-present
- run: npm test
- - run: npm publish
+ - run: npm publish # Or: npm stage publish
```
The critical requirement is the `id-token: write` permission, which allows GitHub Actions to generate OIDC tokens. Learn more in [GitHub's OIDC documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect).
@@ -146,7 +155,7 @@ publish:
script:
- npm ci
- npm run build --if-present
- - npm publish
+ - npm publish # Or: npm stage publish
only:
- tags
```
@@ -185,7 +194,7 @@ jobs:
name: Publish to npm with OIDC
command: |
export NPM_ID_TOKEN=$(circleci run oidc get --claims '{"aud": "npm:registry.npmjs.org"}')
- npm publish
+ npm publish # Or: npm stage publish
workflows:
publish:
@@ -218,6 +227,8 @@ Once you've configured trusted publishers for your package, we strongly recommen
Trusted publishers use short-lived, scoped credentials that are generated on-demand during your CI/CD workflow, eliminating the need for long-lived tokens. By restricting traditional token access while using trusted publishers, you reduce potential security risks associated with credential management.
+For even stronger security, configure your trusted publisher with **stage-only permissions** (allow `npm stage publish` but not `npm publish`). This ensures all CI-automated publishes go through the [staged publishing](/staged-publishing) flow, requiring a maintainer to review and approve each package with 2FA via the CLI or [npmjs.com](https://www.npmjs.com) before it becomes publicly available. Combined with disallowing tokens, this provides the maximum security posture for your packages.
+
**Note:** The "disallow tokens" setting only affects traditional token authentication. Your trusted publishers will continue to work normally, as they use OIDC tokens.
### Migration tip
@@ -346,12 +357,13 @@ Some GitHub Actions workflows use `workflow_call` to invoke other workflows that
Trusted publishing currently supports only cloud-hosted runners. Support for self-hosted runners is intended for a future release. Each package can only have one trusted publisher configured at a time, though you can update this configuration as needed.
-OIDC authentication is currently limited to the publish operation. Other npm commands such as `install`, `view`, or `access` still require traditional authentication methods. The `npm whoami` command will not reflect OIDC authentication status since the authentication occurs only during the publish operation.
+OIDC authentication supports the `npm publish` and `npm stage publish` commands. Other stage subcommands (`npm stage list`, `npm stage view`, `npm stage approve`, `npm stage reject`) require interactive authentication and cannot use OIDC tokens, as these actions require proof of presence and can only be performed via the CLI or [npmjs.com](https://www.npmjs.com). Other npm commands such as `install`, `view`, or `access` still require traditional authentication methods. The `npm whoami` command will not reflect OIDC authentication status since the authentication occurs only during the publish or stage operation.
We intend to expand trusted publishing support to additional CI/CD providers and enhance the feature based on community feedback.
## Learn more
+- [Staged publishing for npm packages](./staged-publishing)
- [About npm provenance](./generating-provenance-statements)
- [OpenSSF Trusted Publishers specification](https://repos.openssf.org/trusted-publishers-for-all-package-repositories)
- [GitHub Actions OIDC documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
diff --git a/static/packages-and-modules/securing-your-code/staged-package-approve.png b/static/packages-and-modules/securing-your-code/staged-package-approve.png
new file mode 100644
index 00000000000..20df38256e4
Binary files /dev/null and b/static/packages-and-modules/securing-your-code/staged-package-approve.png differ
diff --git a/static/packages-and-modules/securing-your-code/staged-package-tab.png b/static/packages-and-modules/securing-your-code/staged-package-tab.png
new file mode 100644
index 00000000000..93f7be2b4ac
Binary files /dev/null and b/static/packages-and-modules/securing-your-code/staged-package-tab.png differ
diff --git a/static/packages-and-modules/securing-your-code/trusted-publisher-circleci.png b/static/packages-and-modules/securing-your-code/trusted-publisher-circleci.png
index a95f932abbf..cee586e724f 100644
Binary files a/static/packages-and-modules/securing-your-code/trusted-publisher-circleci.png and b/static/packages-and-modules/securing-your-code/trusted-publisher-circleci.png differ
diff --git a/static/packages-and-modules/securing-your-code/trusted-publisher-github-actions.png b/static/packages-and-modules/securing-your-code/trusted-publisher-github-actions.png
index b24f48e030a..1a56967aea2 100644
Binary files a/static/packages-and-modules/securing-your-code/trusted-publisher-github-actions.png and b/static/packages-and-modules/securing-your-code/trusted-publisher-github-actions.png differ
diff --git a/static/packages-and-modules/securing-your-code/trusted-publisher-gitlab.png b/static/packages-and-modules/securing-your-code/trusted-publisher-gitlab.png
index d21b9c24395..07c06abedf5 100644
Binary files a/static/packages-and-modules/securing-your-code/trusted-publisher-gitlab.png and b/static/packages-and-modules/securing-your-code/trusted-publisher-gitlab.png differ