Migrating from pnpm to vlt
Quick Start
# Install vlt globally
$ npm install -g vlt
# Configure a registry -- vlt has no default
$ vlt config set registry=https://registry.npmjs.org/
# In your existing project, run:
$ vlt install $ vlt buildUnlike pnpm, vlt has no default registry — commands that resolve
or fetch packages fail with a config error until one is set. The
snippet above points it at the public npm registry; if you have a
vlt.io account, run
vlt setup
instead to configure your account's registry aliases. See
Registries.
vlt reads your existing package.json files and resolves
dependencies. The pnpm-lock.yaml file is not migrated — vlt performs
a fresh resolution and creates vlt-lock.json.
Command Mapping
| pnpm | vlt | Notes |
|---|---|---|
pnpm install | vlt install | Does not run lifecycle scripts |
pnpm add <pkg> | vlt install <pkg> | |
pnpm add -D <pkg> | vlt install -D <pkg> | |
pnpm remove <pkg> | vlt uninstall <pkg> | |
pnpm run <script> | vlt run <script> | |
pnpm <script> | vlt run <script> | See fallback-command |
pnpm dlx <pkg> | vlx <pkg> | Run remote packages |
pnpm exec <cmd> | vlt exec <cmd> | |
pnpm init | vlt init | |
pnpm pack | vlt pack | |
pnpm publish | vlt publish | |
pnpm login | vlt login | |
pnpm whoami | vlt whoami | |
pnpm list | vlt list | |
pnpm why <pkg> | vlt query '#<pkg>' | DSS query; see Selectors |
pnpm install --frozen-lockfile | vlt install --frozen-lockfile | |
pnpm audit | vlt query ':malware' | More powerful; see Security |
pnpm config set <key> <val> | vlt config set <key>=<val> |
Shorthand Script Execution
pnpm lets you run scripts without run (e.g., pnpm build). vlt
supports this via the fallback-command config:
$ vlt config set fallback-command=run-execAfter setting this, vlt build will first check for a vlt command
named build, and if none matches, look for a package.json script.
Configuration
.npmrc + pnpm-workspace.yaml → vlt.json
pnpm reads registry config from .npmrc and workspace config from
pnpm-workspace.yaml. vlt consolidates everything into vlt.json.
pnpm configuration:
# .npmrc (pnpm)
registry=https://registry.internal.company.com/
@mycompany:registry=https://npm.mycompany.com/
auto-install-peers=true# pnpm-workspace.yaml
packages:
- "packages/*"
- "apps/*"vlt equivalent (single file):
{
"registry": "https://registry.internal.company.com/",
"scoped-registries": {
"@mycompany": "https://npm.mycompany.com/"
},
"workspaces": [
"packages/*",
"apps/*"
]
}Registry Configuration
Scoped Registries
pnpm's scoped registries (from .npmrc) map directly to vlt's
scoped-registries:
{
"scoped-registries": {
"@mycompany": "https://npm.mycompany.com/"
}
}Named Registry Aliases
vlt also supports named registry aliases which remove the ambiguity of scope-based mapping:
{
"registries": {
"internal": "https://npm.mycompany.com/"
}
}Then reference packages explicitly in package.json:
{
"dependencies": {
"@mycompany/utils": "internal:@mycompany/utils@^2.0"
}
}Authentication
pnpm stores auth tokens in .npmrc, same as npm. vlt stores them in
an XDG-compliant keychain file, keeping secrets out of project config
files.
# Log in to default registry
$ vlt login
# Log in to custom registry
$ vlt login --registry=https://npm.mycompany.com/CI Environments
# pnpm
NPM_TOKEN=abc123 pnpm install
# vlt
VLT_TOKEN=abc123 vlt installSee Authentication for full details.
Lockfile
pnpm uses pnpm-lock.yaml. vlt uses vlt-lock.json.
When you first run vlt install, vlt creates vlt-lock.json from a
fresh resolution of your package.json files. The pnpm-lock.yaml is
not read.
What to do:
- Run
vlt installto generatevlt-lock.json - Commit
vlt-lock.json - Optionally remove
pnpm-lock.yamlonce you've fully switched
Install Script Protection
pnpm runs lifecycle scripts by default during install (same as npm).
pnpm v9+ added onlyBuiltDependencies in package.json as an
allowlist, but scripts still run by default for listed packages.
vlt takes a different approach: vlt install runs no scripts at
all by default. Building is a separate, explicit step:
# Phase 1: Install (no code executes)
$ vlt install
# Phase 2: Build (runs scripts, skipping known malware)
$ vlt buildBy default, vlt build uses the target
:scripts:not(:built):not(:malware) — it automatically skips packages
flagged as malware by Socket.
pnpm onlyBuiltDependencies vs vlt build --target
{
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"node-gyp"
]
}
}$ vlt build --target='#esbuild, #node-gyp'You can persist the target:
$ vlt config set "command.build.target=#esbuild, #node-gyp"See vlt build for full details.
Workspaces
pnpm
pnpm defines workspaces in pnpm-workspace.yaml:
packages:
- "packages/*"
- "apps/*"vlt
vlt defines workspaces in vlt.json:
{
"workspaces": [
"packages/*",
"apps/*"
]
}vlt also supports named workspace groups:
{
"workspaces": {
"apps": "apps/*",
"libs": [
"packages/*",
"shared/*"
]
}
}Workspace Commands
| pnpm | vlt |
|---|---|
pnpm --filter <name> <cmd> | vlt <cmd> -w <path> or cd <path> && vlt <cmd> |
pnpm --filter ./packages/* <cmd> | vlt <cmd> -w packages/* |
pnpm -r <cmd> | vlt <cmd> --recursive |
pnpm -r run test | vlt run test --recursive |
Note: vlt's --workspace (-w) flag takes paths or glob
patterns, not package names.
# Run tests in a specific workspace
$ vlt run test -w packages/core
# Run build across a workspace group
$ vlt run build -g libsSee Workspaces for full details.
Catalogs
If you use pnpm's catalog feature (pnpm-workspace.yaml), vlt has
direct support for catalogs with compatible
syntax.
pnpm:
# pnpm-workspace.yaml
packages:
- "packages/*"
catalog: typescript: "^5.0.0" eslint: "^8.0.0"
catalogs: testing: vitest: "^1.0.0"vlt:
{
"workspaces": [
"packages/*"
],
"catalog": {
"typescript": "^5.0.0",
"eslint": "^8.0.0"
},
"catalogs": {
"testing": {
"vitest": "^1.0.0"
}
}
}The catalog: protocol in package.json works the same way:
{
"devDependencies": {
"typescript": "catalog:",
"vitest": "catalog:testing"
}
}See Catalogs for full details.
Overrides → Graph Modifiers
pnpm uses pnpm.overrides in package.json. vlt uses
Graph Modifiers in vlt.json.
{
"pnpm": {
"overrides": {
"lodash": "^4.17.21",
"express>qs": "6.10.0"
}
}
}{
"modifiers": {
"#lodash": "^4.17.21",
":root > #express > #qs": "=6.10.0"
}
}Graph Modifiers use DSS selectors, giving you more precise control (e.g., target by path, workspace, or semver range).
node_modules Layout
pnpm uses a content-addressable store with symlinks to create a strict
node_modules layout where packages can only access their declared
dependencies.
vlt also creates a node_modules directory but uses a different
internal layout (under node_modules/.vlt/). The result is similar in
that packages resolve correctly at runtime — your application code
doesn't need to change.
Features Not in vlt
Some pnpm-specific features don't have direct equivalents:
- Side-effects cache — Not available in vlt
pnpm patch— Not yet available in vltpnpm deploy— Not available; use standard deployment toolingpnpm.peerDependencyRules— vlt handles peer dependencies automatically with context isolation
Migration Checklist
- Install vlt:
npm install -g vlt - Create
vlt.jsoncombining your.npmrcregistry config andpnpm-workspace.yamlworkspace definitions - Move scoped registry config from
.npmrctovlt.json - Move catalogs from
pnpm-workspace.yamltovlt.json(catalogandcatalogsfields) - Move
pnpm.overridesfrompackage.jsontomodifiersinvlt.json - Run
vlt installthenvlt build - Commit
vlt-lock.json - Update CI scripts: replace
pnpm install --frozen-lockfilewithvlt install --frozen-lockfile && vlt build - Update CI auth: replace
NPM_TOKENwithVLT_TOKEN - Update any
pnpm dlxusage tovlx - Remove
pnpm-workspace.yamland pnpm-specific.npmrcsettings