# Registries (/docs/registries)



LPM CLI is registry-agnostic. It is **not** a client only for LPM.dev Registry — it is a general-purpose package manager with LPM.dev Registry support.

If you have an existing `package.json` full of `react`, `lodash`, `typescript`, and friends, LPM CLI installs them from `registry.npmjs.org` exactly like npm, pnpm, yarn, or bun would. Nothing has to change.

## Default routing [#default-routing]

LPM CLI resolves each package by name and routes accordingly:

| Package                                              | Goes to                                                   |
| ---------------------------------------------------- | --------------------------------------------------------- |
| `react`, `lodash`, `typescript`, …                   | `registry.npmjs.org`                                      |
| `@scope/anything` (except built-in scopes below)     | `registry.npmjs.org`, unless `.npmrc` overrides the scope |
| `jsr:@std/path` manifest specs (`@jsr/*` internally) | `https://npm.jsr.io`, unless `.npmrc` maps `@jsr`         |
| `@lpm.dev/*`                                         | LPM.dev Registry (`https://lpm.dev`; auth + monetization) |
| Anything that matches a registry mapping in `.npmrc` | The registry you mapped                                   |

The `@lpm.dev/*` scope is the **only** scope that always goes to LPM.dev Registry. JSR packages use JSR's npm-compatible registry. Everything else is npm by default.

## One dependency, one source [#one-dependency-one-source]

LPM CLI chooses a package's registry route before resolving it. A dependency does not fall through from a configured private index to npm, or from npm to LPM.dev Registry, when lookup fails. Missing, unauthorized, or invalid metadata from the selected registry is a hard error. This prevents dependency-confusion behavior where the same name silently resolves from a lower-priority index.

The selected logical source is recorded on the resolved package in `lpm.lock`, for example `registry+https://npm.my-company.com`. Scoped `.npmrc` mappings, the configured npm registry, and the active LPM.dev Registry origin are preserved exactly. The package identity used by lockfile evidence includes this source, so evidence from one registry cannot be replayed for a same-name package from another.

LPM CLI may use the LPM Worker as a transport proxy for public npm metadata and fall back to a direct npm request when that transport is unavailable. Both paths represent the same configured logical npm origin, so this is transport failover—not index fallback—and the lockfile records the npm origin rather than the Worker URL.

## JSR packages [#jsr-packages]

LPM CLI accepts `jsr:` dependency specs in `package.json` and resolves them through JSR's npm-compatible registry. The JSR package name stays as the local import name, while the metadata fetch uses JSR's folded npm package name under `@jsr`.

```json title="package.json"
{
  "dependencies": {
    "@std/path": "jsr:@std/path@^1.1.0",
    "@std/fs": "jsr:^1.0.0"
  }
}
```

`"@std/path": "jsr:@std/path@^1.1.0"` fetches `@jsr/std__path` from `https://npm.jsr.io` and links it at `node_modules/@std/path`. Version-only specs such as `"@std/fs": "jsr:^1.0.0"` borrow the package name from the dependency key.

To mirror or intercept JSR traffic, map the `@jsr` scope explicitly:

```ini title=".npmrc"
@jsr:registry=https://npm-jsr-mirror.example.com
```

## Pointing at a private registry [#pointing-at-a-private-registry]

LPM CLI reads `.npmrc` project-local first, then user-level (`~/.npmrc`). Keep committed project `.npmrc` files to literal routing data, and put env-backed secrets in user or CI-owned config.

```ini title=".npmrc"
# A private registry for one scope
@my-company:registry=https://npm.my-company.com

# Override the default registry for everything else (optional)
registry=https://my-mirror.example.com
```

```ini title="~/.npmrc"
//npm.my-company.com/:_authToken=${NPM_TOKEN}
//my-mirror.example.com/:_authToken=${MIRROR_TOKEN}
```

`@my-company/internal-tool` will be fetched from `npm.my-company.com` with the configured auth token. Public packages still flow through the default registry. No CLI-specific config is required.

For per-environment tokens, prefer `${VAR}` expansion in trusted config over hardcoded secrets. LPM CLI refuses `${VAR}` expansion for registry URLs, auth values, proxy URLs, and TLS paths in project-local `.npmrc` because those files can be controlled by the repo you cloned. Move those entries to `~/.npmrc`, have CI generate literal-token config at runtime, or use the matching login command.

## Registry configuration size limits [#registry-configuration-size-limits]

Each `.npmrc` layer has a 1 MiB limit, and each file referenced by `cafile`, `certfile`, or `keyfile` has a 1 MiB limit. LPM CLI enforces these limits before parsing or building an HTTP client. Missing optional layers are still skipped, but an oversized or invalid UTF-8 `.npmrc` is a fatal configuration error: it is not ignored in favor of a lower-precedence registry. An oversized TLS file likewise fails with its path and byte limit, without logging tokens, certificates, keys, or file contents.

For installs, these errors surface before dependency resolution or any registry request. Inline `ca` / `cert` / `key` values are part of the containing `.npmrc` and therefore covered by that file's 1 MiB layer limit. See [local configuration size limits](/docs/project-setup#local-configuration-size-limits) for non-registry config and explicit exclusions.

## Redirect transport policy [#redirect-transport-policy]

LPM CLI checks every automatically followed redirect before sending the next request. Once a request chain has used HTTPS, a redirect to HTTP is refused — including redirects to `localhost`, `127.0.0.1`, or `[::1]`. The command fails with a network or registry error containing `refused HTTPS-to-HTTP redirect`.

HTTPS-to-HTTPS redirects and HTTP-to-HTTPS upgrades remain allowed. HTTP-to-HTTP development flows can still begin from a directly configured loopback URL. For registry operations, `--insecure` can admit an explicitly configured HTTP endpoint outside loopback, but it never permits an HTTPS request to downgrade through a redirect.

`strict-ssl=false` changes certificate verification only; it does not weaken the redirect rule. This policy covers outbound HTTP clients used by LPM CLI itself. It does not make claims about arbitrary application URLs opened by project code or installed packages.

## TLS, custom CAs, and mTLS [#tls-custom-cas-and-mtls]

LPM CLI honors the same `.npmrc` TLS keys npm does. All settings can be set globally or per-origin (`//host/:key=value`); per-origin scoping wins for the matching host.

### Trusting a corporate CA [#trusting-a-corporate-ca]

```ini title=".npmrc"
# Add a CA to the trust store (file path or inline PEM).
cafile=/etc/ssl/corp-ca.pem
ca="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"

# Per-origin form: only this host trusts the extra CA.
//npm.internal/:cafile=/etc/ssl/corp-ca.pem
```

`cafile` and `ca` are **additive** — they extend the system trust store, never replace it. A globally-trusted CA still validates `registry.npmjs.org` normally.

### Disabling cert verification (escape hatch) [#disabling-cert-verification-escape-hatch]

```ini title=".npmrc"
strict-ssl=false
```

Disables hostname, expiry, and CA validation **process-wide**. It does not allow an HTTPS redirect to downgrade to HTTP. LPM CLI logs a loud warning at install start whenever `strict-ssl=false` is in effect — this is the kind of setting that should never accidentally land in CI.

Per-origin `//host/:strict-ssl=false` is **not** supported in v1; the parser warns and ignores it. Use `cafile` to scope a custom-CA trust addition instead.

### Mutual TLS (client certificates) [#mutual-tls-client-certificates]

For private registries that require client-cert authentication, point LPM CLI at a PEM cert and key:

```ini title=".npmrc"
# Global mTLS identity — applies to every request from the default client.
certfile=/etc/ssl/client.pem
keyfile=/etc/ssl/client.key

# Per-origin mTLS — only this host gets the client cert.
//npm.internal/:certfile=/etc/ssl/client.pem
//npm.internal/:keyfile=/etc/ssl/client.key
```

`certfile` and `keyfile` must always be set together — half-configured global identities abort the install with a cited error. Per-origin half-configurations only fail when that origin is actually used.

#### Path resolution [#path-resolution]

Relative paths in `certfile=` / `keyfile=` / `cafile=` resolve against the `.npmrc` file that contained them, not against the working directory you ran `lpm install` from. So in `~/.npmrc`:

```ini title="~/.npmrc"
certfile=client.pem      # → ~/client.pem
keyfile=client.key       # → ~/client.key
```

You can drop secrets next to the `.npmrc` and reference them with bare names.

#### Encrypted private keys [#encrypted-private-keys]

LPM CLI accepts encrypted PKCS#8 keys. The passphrase is resolved in this order:

1. `LPM_KEY_PASSPHRASE` environment variable (the CI-friendly path).
2. Interactive TTY prompt — only when both stdin AND stdout are a terminal.
3. Otherwise, hard error citing the keyfile line.

```bash
LPM_KEY_PASSPHRASE='hunter2' lpm install
```

The passphrase is cached in memory for the lifetime of the install — multiple per-origin clients sharing the same key file prompt at most once.

If your key uses the legacy PKCS#1 `Proc-Type: 4,ENCRYPTED` format, convert it first:

```bash
openssl pkcs8 -topk8 -in legacy.key -out key.pem
```

#### PKCS#12 (.p12 / .pfx) is not supported [#pkcs12-p12--pfx-is-not-supported]

LPM CLI uses rustls, which doesn't ingest PKCS#12 archives directly. Convert with openssl and point `certfile=` / `keyfile=` at the resulting PEMs:

```bash
openssl pkcs12 -in identity.pfx -clcerts -nokeys -out cert.pem
openssl pkcs12 -in identity.pfx -nocerts  -nodes  -out key.pem
```

Then:

```ini title=".npmrc"
certfile=/path/to/cert.pem
keyfile=/path/to/key.pem
```

If you point `certfile=` or `keyfile=` directly at a `.p12`/`.pfx` file, LPM CLI emits a cited error with this recipe inline.

## When does LPM.dev Registry come in [#when-does-lpmdev-registry-come-in]

LPM.dev Registry is a registry **plus** an auth and monetization layer. Use it when:

* You want **private packages** without standing up your own registry. Every published package starts private — only the publisher can install it. (Same shape as npm private packages.)
* You publish under your scope and want to flip a package to **pool** distribution. Metadata becomes public, but installs are gated to pool subscribers — you earn a share of the pool's revenue.
* You publish a paid package via **marketplace** distribution. Installs require a license purchase.

Setting `distribution` is a per-package opt-in on the publisher's side. As an installer, you simply `lpm install @lpm.dev/owner.package` — LPM CLI handles the auth, license, and download through LPM.dev Registry.

If you're not publishing, ignore LPM.dev Registry entirely — LPM CLI is a strict superset of npm-the-client and will never send your `react` install through it.

## Private packages on LPM.dev Registry [#private-packages-on-lpmdev-registry]

```bash
lpm login
lpm install @lpm.dev/acme.internal
```

`lpm login` stores a token in your OS keychain (Keychain on macOS, libsecret on Linux, Credential Manager on Windows). The token is scoped to LPM.dev Registry — no other registry sees it.

To generate `.npmrc` without relying on `lpm login` in the current shell:

```bash
lpm setup ci npmrc # generates an .npmrc for CI/CD use
lpm setup local  # mints a read-only .npmrc token for local development (auto-gitignored)
```

## Mixing registries in one project [#mixing-registries-in-one-project]

A `package.json` can pull from npm, LPM.dev Registry, and a private registry simultaneously — LPM CLI resolves each dep against the correct origin and writes the resolution to the lockfile.

```json title="package.json"
{
  "dependencies": {
    "react": "^19.0.0",
    "@my-company/internal-tool": "^2.1.0",
    "@lpm.dev/acme.private-utils": "^1.0.0"
  }
}
```

```ini title=".npmrc"
@my-company:registry=https://npm.my-company.com
```

```ini title="~/.npmrc"
//npm.my-company.com/:_authToken=${NPM_TOKEN}
```

```bash
lpm install
# react                          → registry.npmjs.org
# @my-company/internal-tool      → npm.my-company.com  (via .npmrc)
# @lpm.dev/acme.private-utils    → lpm.dev             (via stored token)
```

## See also [#see-also]

* [Installation](/docs/installation) — get the CLI on your machine
* [Authentication](/docs/infra/authentication) — token storage, scopes, rotation
* [`lpm install`](/docs/packages/install) — the install command
* [`lpm publish`](/docs/packages/publish) — publishing to LPM.dev Registry or npm
