# Migrating a JavaScript project to Chainguard Libraries

URL: https://chainguard-docs-preview-git-uv-frozen-installs.chainguard.app/chainguard/libraries/javascript/migration.md
Last Modified: October 16, 2026
Tags: Chainguard Libraries, JavaScript

How to migrate an existing JavaScript project to pull dependencies from Chainguard Libraries


Chainguard Libraries for JavaScript provides a curated registry of npm packages
rebuilt from source, scanned for malware, and verified against the
[OSV database](https://osv.dev/). Because Chainguard Libraries uses the standard
npm registry protocol, switching an existing project requires only a registry
configuration change — no changes to your application code, `package.json`, or
dependency versions.

This guide walks through migrating an existing JavaScript project to Chainguard
Libraries, covering the two most common setups:

- **Direct access** — your build tool connects directly to `libraries.cgr.dev`.
  This option is faster for initial evaluation and smaller-scale setups.
- **Repository manager** — your build tool connects to a repository manager
  (such as JFrog Artifactory or Sonatype Nexus), which proxies requests to
  Chainguard Libraries. This option is recommended for teams and organizations.

To follow along with a ready-made project instead of your own, use the [Chainguard Libraries for JavaScript demo repository](https://github.com/chainguard-demo/chainguard-libraries-javascript). It provides example projects for npm, pnpm, Yarn, and Bun, each with a `demo.sh` script that configures access and installs sample packages.

For a reference of the configuration options for each supported build tool, check out [Configure JavaScript build tools](/chainguard/libraries/javascript/build-configuration/).

## Prerequisites

Before you begin, you'll need:

- An existing JavaScript project with a `package.json` and a lockfile
  (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, or `bun.lock`)
- [`chainctl` installed and authenticated](/platform/chainctl-usage/how-to-install-chainctl/)
- An [entitlement to Chainguard Libraries](/chainguard/libraries/introduction/access/#entitlement)
  for JavaScript

### Entitlement and upstream fallback

You must have an [entitlement to Chainguard Libraries](/chainguard/libraries/introduction/access/#entitlement) for JavaScript. To add an entitlement for your organization, contact your Chainguard account team.

Once your organization is entitled to JavaScript, enable upstream fallback, which includes a default 7-day cooldown, by running:

```shell
chainctl libraries entitlements create --ecosystems=JAVASCRIPT --policy=CHAINGUARD_AND_UPSTREAM
```

You can also configure [cooldown policies](#packages-not-available-in-chainguard-libraries)
after you enable upstream fallback. For example, to create and enforce a
policy for a 14-day cooldown:

```shell
chainctl libraries policy create --name=js-cooldown-14d --cooldown-days=14
chainctl libraries policy enable js-cooldown-14d --ecosystem=JAVASCRIPT --mode=ENFORCE
```

It can take up to 30 minutes for the fallback and cooldown policies to take effect. Learn more about cooldown and other policies in the [Libraries policies documentation](/chainguard/chainguard-repository/library-policies/).

### Create a pull token

If you plan to use a repository manager, or a non-interactive environment such
as CI/CD, you will need a pull token. You must be an Owner or have the
`libraries.javascript.pull_token_creator` permission to create one.

You can [create a pull token in the Chainguard Console](/chainguard/libraries/introduction/access/#creating-pull-tokens-with-the-chainguard-console), or via `chainctl`:

```shell
chainctl auth pull-token --repository=javascript --name=my-js-token
```

This outputs an identity ID and token named `my-js-token`, with a default expiration of 30 days. To configure the expiration, use the `--ttl` flag. Learn more about command options in the [chainctl documentation](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token/).

Set the identity ID and token as environment variables:

```shell
export CHAINGUARD_JAVASCRIPT_IDENTITY_ID="<identity-id>"
export CHAINGUARD_JAVASCRIPT_TOKEN="<pull-token>"
```

> **Do not commit credentials to version control.** Configuration files
> generated with a literal token value — such as `.npmrc` files written by
> `chainctl auth configure-npm --pull-token` — should not be committed to a
> repository, especially a public one. Add these files to `.gitignore`, and
> store tokens as CI secrets referenced via environment variables instead. If
> you accidentally commit credentials, [delete the exposed
> token](/chainguard/libraries/introduction/access/#pull-token-management).

### Audit your current registry configuration

Before making changes, identify where your current registry is configured. npm
and other JavaScript build tools can read registry settings from several
locations — a project-level `.npmrc`, a user-level `~/.npmrc`, environment
variables, or a repository manager — and you may have configuration in more than
one place.

To see all currently active npm configuration and where each value comes from:



**npm**



```shell
npm config list
```




**pnpm**



```shell
pnpm config list
```




**Yarn Berry**



```shell
yarn config
```




**Yarn Classic**



```shell
yarn config list
```




**Bun**



```shell
cat bunfig.toml
```

If there is no `bunfig.toml`, or if that file does not contain a specific registry, then it is using the standard npm registry by default.







The `registry` line shows where packages are currently being fetched from. If no
registry is set, npm fetches from `https://registry.npmjs.org` by default.

If you don't have an `.npmrc` file yet, that's fine; you'll create one in the
next step.

## Step 1: Configure authentication and registry

How you configure the registry depends on your environment. Follow the
instructions that match your setup.

> **Choosing what account to use:** For individual evaluation, your personal
> credentials are sufficient. For shared environments, CI/CD pipelines, or team
> rollouts, use a dedicated service account or machine identity rather than a
> personal account. This simplifies token rotation, provides cleaner audit logs,
> and avoids builds breaking if someone leaves the organization.

### Direct access

Session-based authentication is the preferred option when your environment can
use your current Chainguard session or an assumable identity. This keeps
credentials short-lived and avoids passing around static tokens.

#### Local development

For npm and Bun, run the following command to write a project-level `.npmrc` with the
registry URL and credentials from your current Chainguard session:

```shell
chainctl auth configure-npm
```

Because [this
command](/platform/chainctl/chainctl-docs/chainctl_auth_configure-npm/) uses a
session-backed bearer token, you will need to re-run it when the token expires.
If the command returns an error, ensure you are using the [latest version of
chainctl](/platform/chainctl-usage/how-to-install-chainctl/#updating-chainctl).

For pnpm and Yarn, [configure the .npmrc
manually](#manual-registry-configuration). `chainctl auth configure-npm`
generates path-scoped credentials (`//libraries.cgr.dev/javascript/:_auth`) that
do not cover pnpm's upstream-fallback path, causing HTTP 401 errors on packages
Chainguard has not yet rebuilt.

#### CI/CD and automated environments

For CI/CD environments, [assumable
identities](/platform/administration/assumable-ids/assumable-ids/) are the
recommended approach. Rather than managing a static token, an assumable identity
lets your CI/CD workload authenticate directly with Chainguard using its own
platform identity; no long-lived credentials to rotate or accidentally expose.

Once configured, authenticate and write the registry configuration in your
pipeline. For example, in a GitHub Actions workflow using npm:

```yaml
steps:
  - uses: chainguard-dev/setup-chainctl@main
    with:
      identity: "<identity-id>"
  - run: chainctl auth configure-npm
```

For pnpm, Yarn, and Bun, authenticate the workload then apply the
[tool-specific registry configuration](#manual-registry-configuration).

#### Pull token authentication

If your platform does not support assumable identities, you can use a static pull token.

**Configure the .npmrc with `chainctl`**:

```shell
chainctl auth configure-npm --pull-token
```

This generates a project-level `.npmrc` with a pull token. It is supported for npm and Bun. For pnpm, [configure the registry manually](#manual-registry-configuration) to ensure host-level credentials are used.

<a name="manual-registry-configuration"></a>

**Configure the .npmrc manually**:

First create a pull token as described in the [prerequisites](#prerequisites), then configure your build tool manually using the instructions below.

**Configure registry manually for pnpm, Yarn, and Bun**



> **Note:** The following commands use environment variables to supply
> credentials at runtime. Writing credentials to a local configuration file
> should still be treated as sensitive regardless of the form used. Generate
> credentials at build time where possible, add configuration files to
> `.gitignore` if needed, and store secrets in your CI/CD platform rather than
> in version control.

**pnpm**

> Note: The Chainguard Repository [upstream fallback](/chainguard/libraries/javascript/overview/#upstream-fallback-policy-and-controls) has been tested with pnpm v11. We recommend using pnpm v11 or newer.

```shell
export token=$(echo -n "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0)

pnpm config set registry https://libraries.cgr.dev/javascript/ --location=project
pnpm config set //libraries.cgr.dev/:_auth "${token}" --location=project
```

The authentication entry is keyed to the whole `libraries.cgr.dev` host
(`//libraries.cgr.dev/`) so that it also covers packages served through the
[upstream fallback](/chainguard/libraries/javascript/overview/#upstream-fallback-policy-and-controls)
path (`/javascript-upstream/`). A credential scoped only to `/javascript/` causes
pnpm to send no auth header for upstream-fallback packages, resulting in HTTP 401
errors.

Alternatively, use `username` and `_password` instead of `_auth`. Note that the
`_password` value must still be base64-encoded; pnpm does not accept a raw token
here:

```shell
pnpm config set //libraries.cgr.dev/:username "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}" --location=project
pnpm config set //libraries.cgr.dev/:_password "$(echo -n "${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0)" --location=project
```

**Yarn Berry (2.x+)**

```shell
export authInfo="${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}"

yarn config set npmRegistryServer https://libraries.cgr.dev/javascript
yarn config set 'npmRegistries["//libraries.cgr.dev/javascript"].npmAuthIdent' "${authInfo}"
yarn config set 'npmRegistries["//libraries.cgr.dev/javascript"].npmAlwaysAuth' "true"
```

**Yarn Classic (1.x)**

```shell
export token=$(echo -n "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0)
cat > .npmrc << EOF
registry=https://libraries.cgr.dev/javascript/
//libraries.cgr.dev/javascript/:_auth="$token"
//libraries.cgr.dev/javascript/:always-auth=true
EOF
```

**Bun**

```shell
cat > bunfig.toml << EOF
[install.registry]
url = "https://libraries.cgr.dev/javascript/"
username = "$CHAINGUARD_JAVASCRIPT_IDENTITY_ID"
password = "$CHAINGUARD_JAVASCRIPT_TOKEN"
EOF
```




### Repository manager

If your organization uses a repository manager, configure Chainguard Libraries
as an upstream source in that proxy first. Follow the [global configuration
documentation](/chainguard/libraries/javascript/global-configuration/) for your
repository manager.

Once configured, point your build tool at your repository manager URL. In this
setup, the credentials your build tool uses are your repository manager
credentials — not a Chainguard pull token.



**npm or Yarn Classic**



```shell
cat > .npmrc << EOF
registry=https://repo.example.com:8443/repository/javascript-all
EOF
```

Learn more in the build configuration docs for [npm](/chainguard/libraries/javascript/build-configuration/#using-a-repository-manager) and for [Yarn Classic](/chainguard/libraries/javascript/build-configuration/#using-a-repo-manager).




**pnpm**



```shell
pnpm config set registry https://<your-repo-manager-url>/repository/javascript-all/ --location=project
```

Learn more in the [build configuration docs](/chainguard/libraries/javascript/build-configuration/#using-a-repository-manager-1).




**Yarn Berry**



```shell
yarn config set npmRegistryServer https://<your-repo-manager-url>/repository/javascript-all
```

Learn more in the [build configuration docs](/chainguard/libraries/javascript/build-configuration/#using-a-repository-manager-2).




**Bun**



Add the registry configuration to the `bunfig.toml` file of your project:

```toml
[install]
# set default registry as a string
registry = "https://repo.example.com:8443/repository/javascript-all/"
```

Learn more in the [build configuration docs](/chainguard/libraries/javascript/build-configuration/#using-a-repository-manager-3).







Example URLs by repository manager:

| Repository manager | URL pattern |
|---|---|
| JFrog Artifactory | `https://example.jfrog.io/artifactory/javascript-all/` |
| Sonatype Nexus | `https://repo.example.com:8443/repository/javascript-all/` |

For authentication details specific to your repository manager, refer to the [global
configuration documentation](/chainguard/libraries/javascript/global-configuration/).

## Step 2: Verify authentication

Before reinstalling packages, confirm your credentials are valid. For direct
access with npm or pnpm, run:

```shell
npm ping --userconfig .npmrc
```

A successful response looks like:

```
npm notice PING https://libraries.cgr.dev/javascript/
npm notice PONG 1065ms
```

For repository manager setups, authentication is handled by your organization's
proxy configuration.

## Step 3: Update your lockfile

Your existing lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, or `bun.lock`)
contains checksums and resolved URLs pointing to the original registry. Running
your package manager's install command after changing the registry does **not**
re-fetch already-resolved packages — the lockfile entries are treated as
satisfied, and source URLs will still point to `registry.npmjs.org`.

You can update your lockfile in one of two ways. Update the checksums in place to
keep your existing pinned versions, or regenerate the lockfile if you also want to
refresh your dependency versions.



**Update in place**



Use [`chainctl libraries update-hashes`](/chainguard/libraries/javascript/build-configuration/#updating-lockfile-hashes)
to rewrite only the integrity hashes in your existing lockfile to match
Chainguard's artifacts, without regenerating the lockfile from scratch. This
preserves your pinned dependency versions. Supported formats include `package-lock.json` (npm v2/v3), `yarn.lock` (Yarn
Classic and Berry), `pnpm-lock.yaml`, and `bun.lock`.

Run the command in the directory containing the lockfile:

```shell
chainctl libraries update-hashes
```

> **Note**: Running `chainctl libraries update-hashes` requires the `libraries.javascript.pull` permission or the Owner role.

If your build tool appends the Chainguard hashes to your lock file, include the flag `--replace` to ensure the hashes are replaced with Chainguard hashes. When using a repo manager, pass the full repository URL with `--registry-url`.

You can also specify a lockfile path directly:

```bash
chainctl libraries update-hashes path/to/lockfile
```

After running the command,
ensure your `.npmrc` is configured with Chainguard credentials. The updated hashes are applied when you reinstall. The `chainctl libraries update-hashes` command will
output a "Next steps" section that includes the tool-specific command for
reinstalling.

Learn about using this command with repo managers in the [Global
configuration](/chainguard/libraries/javascript/global-configuration/) page.




**Regenerate lockfile**



Regenerating the lockfile is another valid approach, and many teams use the
migration as an opportunity to update dependencies at the same time. Before you
regenerate, consider the following:

- Pinning versions is a security best practice. When you regenerate, your package
manager re-resolves all dependencies from scratch using the version constraints
in `package.json`. If a constraint uses `^` or `~` (the npm default), the
resolver picks the newest matching version, so you can lose your existing pins
and pick up unintended upgrades that change your application's behavior.
- Whether Chainguard has a resolved version available depends on the policies
you've configured. For example, a version that was published upstream within your configured cooldown period will return an error. Check the cooldown period you've set before you regenerate.

To regenerate — for example, to intentionally refresh your dependency
versions — use the following commands:

| Tool | Command |
|---|---|
| npm | `rm -rf node_modules package-lock.json && npm install` |
| pnpm | `rm -rf node_modules pnpm-lock.yaml && pnpm install` |
| Yarn Berry | `rm -rf .yarn/cache yarn.lock && yarn` |
| Yarn Classic | `rm -rf node_modules yarn.lock && yarn` |
| Bun | `rm -rf node_modules bun.lock && bun install` |

If you hit 404 errors after regenerating, the most likely cause is that the
resolved version is not yet available in Chainguard Libraries or is still within
the cooldown window. Refer to [Packages not available in Chainguard
Libraries](#packages-not-available-in-chainguard-libraries) for next steps.







## Step 4: Delete node_modules and clear caches



**npm**



```bash
rm -rf node_modules && npm cache clean --force
```




**pnpm**



First, remove `node_modules`:

```bash
rm -rf node_modules
```

Then, clear the caches. pnpm has three separate layers of cached data; remove them in the following order:

**1. Metadata (packuments)**

There is an [experimental command](https://pnpm.io/cli/cache-delete) to delete the metadata cache:

```bash
pnpm cache delete
```

**2. HTTP cache**

pnpm maintains a separate HTTP cache at `~/.cache/pnpm` (or `$XDG_CACHE_HOME/pnpm` if that variable is set). Delete it with:

```bash
rm -rf "${XDG_CACHE_HOME:-$HOME/.cache}/pnpm"
```

**3. Content-addressable store (tarballs)**

To find and manually remove the full store:

```bash
pnpm store path
```

Then delete the directory that command outputs:

On macOS/Linux -

```bash
rm -rf "$(pnpm store path)"
```

On Windows -

Use the path returned by `pnpm store path` and delete it via File Explorer or `rmdir`.

> Note: `pnpm prune` removes unused tarballs but does not remove packument metadata. If you are seeing 404 errors after switching to or updating the Chainguard registry endpoint, use the commands above rather than `pnpm prune`.




**Yarn Berry**



```bash
rm -rf node_modules .yarn/cache
```




**Yarn Classic**



```bash
rm -rf node_modules && yarn cache clean
```




**Bun**



```bash
rm -rf node_modules && bun pm cache rm
```

If this command is not available on your version of Bun, you can instead delete Bun's cache directory manually.







## Step 5: Reinstall dependencies

Reinstall dependencies and confirm that the lockfile reflects Chainguard as the source. Resolved URLs should point to `libraries.cgr.dev/javascript` (direct access) or
your repository manager host, not `registry.npmjs.org`.

> Note: When using the `update-hashes` command, some package managers and tool versions default to appending the hash rather than replacing it. In some cases, the resulting dual-hash format fails on install. If you encounter integrity errors, [run the command again](#step-3-update-your-lockfile) and include the `--replace` flag.



**npm**



Install dependencies:

```shell
npm install
```

Then check the `resolved` field in `package-lock.json`:

```shell
grep "resolved" package-lock.json | head -5
```




**pnpm**



Install dependencies:

```bash
pnpm install
```

For pnpm versions prior to v9, check the `tarball` field in `pnpm-lock.yaml`:

```shell
grep "tarball" pnpm-lock.yaml | head -5
```

For pnpm v9+, check the `integrity` field:

```shell
grep "integrity" pnpm-lock.yaml | head -5
```




**Yarn**



Install dependencies:

```shell
yarn
```

Then check the `archiveUrl` field in `yarn.lock`:

```shell
grep "archiveUrl" yarn.lock | head -5
```




**Bun**



Install dependencies:

```shell
bun install
```

Then check the `packages` section in `bun.lock` to verify that package source URLs reference Chainguard (or your repository manager) rather than npm.







## Step 6: Verify your libraries

After reinstalling, you can verify which of your JavaScript dependencies are built by
Chainguard by scanning your local package manager cache or `node_modules`
directory. `chainctl libraries verify` auto-detects npm and pnpm caches by
their directory structure.

You can also verify your lockfile directly, without reinstalling. Run `chainctl libraries verify` against a lockfile (`package-lock.json`, `npm-shrinkwrap.json`, `pnpm-lock.yaml`, `yarn.lock`, or `bun.lock`) to check which of its entries resolve to Chainguard-built packages. This reports what the lockfile claims will be installed, not the package bytes on disk. See [Verify JavaScript lockfiles without installing dependencies](/chainguard/libraries/policies-and-security/verification/#verify-javascript-lockfiles-without-installing-dependencies).

When upstream fallback is enabled, [packages that aren't built by Chainguard](#packages-not-available-in-chainguard-libraries) are subject to Chainguard's security controls.



**npm**



Scan the npm cache:

```shell
chainctl libraries verify ~/.npm
```

Verify an npm tarball:

```shell
chainctl libraries verify PACKAGE-VERSION.tgz
```




**pnpm**



Use `--store-dir` to install to an explicit path and verify that location:

```shell
pnpm install --store-dir /tmp/my-pnpm-store
chainctl libraries verify /tmp/my-pnpm-store
```

Learn more about verifying a pnpm store in the [Verification page](/chainguard/libraries/policies-and-security/verification/#analyze-javascript-packages).




**Yarn Classic**



Verify a Yarn Classic cache:

```shell
chainctl libraries verify yarn:
```




**node_modules**



Scan the project directory:

```shell
chainctl libraries verify ./node_modules
```







> **Note**: Running `chainctl libraries verify` requires the `libraries.javascript.pull` permission or the Owner role.

A successful result shows what percentage of your project's dependencies were built by Chainguard. For example:

```
Artifact: ./node_modules
Verification Coverage: 100.00%
```

For full details on verification options and output, refer to [Verification: Analyze
JavaScript packages](/chainguard/libraries/policies-and-security/verification/#analyze-javascript-packages).

## Step 7: Commit and roll out

Commit the updated lockfile and any registry configuration files that do not
contain literal credentials. Apply the same registry, cache, and hash update
steps to other developer workstations and build servers as you migrate them - including Jenkins, TeamCity, GitHub or other infrastructure
that builds the applications or downloads dependencies.

For organization-wide rollout using a repository manager, refer to the [global
configuration documentation](/chainguard/libraries/javascript/global-configuration/).

## Packages not available in Chainguard Libraries

Chainguard Libraries covers a large and growing collection of popular npm
packages, but not every package in the npm Registry is available. If a package
is missing, your install will fail with a 404 unless you have [configured
upstream fallback](/chainguard/libraries/javascript/overview/#upstream-fallback-policy-and-controls).

- For direct access, the Chainguard Repository includes a built-in, configurable
npm upstream fallback. When fallback is enabled, packages not available from
Chainguard are proxied from npm and subject to a configurable cooldown period
and malware scanning before being served. Refer to the [JavaScript Libraries
overview](/chainguard/libraries/javascript/overview/#upstream-fallback-policy-and-controls)
for details.
- For repository manager setups, Chainguard recommends using the built-in fallback
rather than configuring a separate public registry fallback in your repository
manager, to preserve Chainguard's security controls.

If you have private scoped packages published to npm under a prefix like
`@your-org/`, you can route them back to npm using per-scope registry
configuration. Refer to [Using private npm packages alongside Chainguard
Repository](/chainguard/libraries/javascript/build-configuration/#using-private-npm-packages-alongside-chainguard-repository)
for details.

## Troubleshooting

**Packages still resolving from `registry.npmjs.org` after migration**  
Running the install command after changing the registry does not re-fetch
packages already satisfied by the lockfile. Follow
[Step 3](#step-3-update-your-lockfile) to update checksums in place or
regenerate the lockfile.

**404 errors during install**  
The requested package or version may not yet be available in Chainguard
Libraries, or may still be within the cooldown window. Check the [Chainguard
Console](https://console.chainguard.dev/libraries/javascript), try an earlier
version, or enable upstream fallback. Refer to [Packages not available in Chainguard
Libraries](#packages-not-available-in-chainguard-libraries). Also note that
upstream fallback and cooldown policies can take up to 30 minutes to go into
effect.

**Authentication errors or `npm ping` failure**  
Confirm your environment variables are set and the token has not expired.
Re-run `chainctl auth pull-token --repository=javascript` to generate a new
token if needed.

**`npm config list` shows an unexpected registry**  
You may have registry configuration at the user level (`~/.npmrc`) or in
environment variables that is overriding your project-level settings. Run
`npm config list` to identify conflicting sources.

**Stale or corrupted package data after switching registries**  
If packages seem to be served from a cache rather than re-fetched, you may need
to clear additional cache layers beyond `node_modules`. See the "Apply registry
changes" sections for your build tool on the [build configuration
page](/chainguard/libraries/javascript/build-configuration/).

## Next steps

- To apply this configuration across your whole organization using a repository
  manager, refer to the [global configuration](/chainguard/libraries/javascript/global-configuration/)
  documentation.
- To verify downloaded packages were built by Chainguard, see the
  [verification](/chainguard/libraries/policies-and-security/verification/) documentation.
- For full per-tool configuration reference, refer to the [build
  configuration](/chainguard/libraries/javascript/build-configuration/)
  documentation.
- To keep pinned versions stable when Chainguard publishes new builds, see the
  [build pinning](/chainguard/libraries/policies-and-security/build-pinning/) documentation.

