vlt install
Usage:
$ vlt install [packages ...]Install dependencies into node_modules, updating package.json and
vlt-lock.json appropriately.
By default, vlt install downloads and extracts packages without
executing lifecycle scripts—enhancing security by preventing
arbitrary code execution during installation. Use
vlt build after installation to run
scripts selectively.
What is a Package?
- A folder containing a
package.jsonfile - A gzipped tarball containing a folder with a
package.jsonfile - A URL that resolves to a gzipped tarball
- A
<name>@<version>that is published to a configured registry - A
<name>@<semver-range>that is published to a configured registry - A
<name>@<tag>that points to a package in a configured registry - A
<name>by itself, which defaults to using thelatesttag from a registry - A
<custom>:<name>@<version>that points to a custom registry configured invlt.json - A
registry:URL#<name>@<version>that points to a custom registry url - A
<git remote url>that resolves to a folder with apackage.jsonfile
Basic Usage
Install all dependencies
$ vlt installInstalls all dependencies from package.json without running scripts.
Adding new packages
Using positional arguments that use the valid package specification described above.
Registry packages
Installing by name (a bare spec) requires a registry to be
configured — the npm alias has no built-in URL. Run
vlt setup once
to configure your account's registries. vlt install (and
vlt update, vlt uninstall, vlt ci, vlx/vlt exec) require
registries.npm specifically — or, if you changed
default-registry-alias, the alias it names — and fail with a config
error when it is missing.
# Latest version
$ vlt install express
# Scoped packages using semver ranges
$ vlt install @vltpkg/graph@^1.0.0Aliases
Install multiple versions of the same package side-by-side:
# Install react v18 as my-react
$ vlt install my-react@npm:react@18
# Install both major versions under aliased names
$ vlt install react18@npm:react@18 react19@npm:react@19In the vlt cli the traditional aliases also enable installing from
custom registries:
# Install from vlt.json configured custom registry
$ vlt install custom:@org/pkg@^1.0.0
# Install using an aliased name, pointing to a custom registry
$ vlt install pkg@custom:@org/pkgGit repositories
# GitHub shorthand
$ vlt install github:user/repo
$ vlt install github:user/repo#v1.0.0
$ vlt install github:user/repo#semver:^1.0
# Full git URL
$ vlt install git+ssh://git@github.com:user/repo.git
# OR
$ vlt install git+https://github.com/user/repo.git#main
# GitLab
$ vlt install gitlab:user/repo
# Bitbucket
$ vlt install bitbucket:user/repoWhen using #semver:<range>, vlt looks for matching tags/refs in the
remote repository.
Tarballs
# Local tarball
$ vlt install ./package.tgz
# Remote tarball
$ vlt install https://example.com/package.tgzLocal directories
# Relative path
$ vlt install ./my-local-package
# Install from parent directory
$ vlt install ../shared-libWorkspaces
When a name with no range resolves to a workspace, vlt links it and
saves workspace:* to package.json. Use my-lib@workspace:* to
require the workspace.
$ vlt install my-libOptions
Dependency type flags
| Flag | Short | Description |
|---|---|---|
--save-prod | -P | Add to dependencies (default) |
--save-dev | -D | Add to devDependencies |
--save-optional | -O | Add to optionalDependencies |
--save-peer | Add to peerDependencies | |
--save-types | Add matching @types/* to devDependencies |
--save-types skips packages that
ship their own types:
# express ships no types: adds @types/express to devDependencies too
$ vlt install express --save-types
# zod ships its own types: adds zod only
$ vlt install zod --save-typesConfig options
| Flag | Description |
|---|---|
--save-config | Save registry and git host options given on the command line or via env to the project vlt.json (user one with --config=user), see Registries |
Lockfile options
| Flag | Description |
|---|---|
--frozen-lockfile | Fail if lockfile is missing or out of sync with package.json |
--expect-lockfile | Fail if lockfile is missing or outdated |
--lockfile-only | Update only vlt-lock.json and package.json, skip node_modules |
Script execution
| Flag | Description |
|---|---|
--allow-scripts=<selector> | Allow specific packages to run lifecycle scripts during install; saved to vlt.json |
# Allow specific packages to run scripts during install
$ vlt install --allow-scripts="#esbuild, #node-gyp"
# Allow only direct dependencies to run scripts
$ vlt install --allow-scripts=":root > *"vlt install and vlt update save --allow-scripts (or
VLT_ALLOW_SCRIPTS) to the project vlt.json after a successful
install. When another query is stored, it is kept and a hint says so:
change it with vlt build.
--lockfile-only saves nothing. An invalid query fails before
anything is installed.
Agent skills
| Flag | Description |
|---|---|
--allow-skills=<selector> | Link the agent skills of matching packages into the agent skill dirs after install (default: none); saved to vlt.json |
--skills-dir=<path> | Dirs to link agent skills into, replacing detection (--skills-dir= links nothing) |
# Link the agent skills of direct dependencies
$ vlt install --allow-skills=":root > *"
# Link one skill of one package
$ vlt install --allow-skills="#agent-browser:skill(agent-browser)"See vlt skills.
Registry options
| Flag | Description |
|---|---|
--tag=<tag> | Default dist-tag to use (default: latest) |
--before=<date> | Install relative to the provided ISO 8601 date |
# Install from beta channel
$ vlt install express --tag=beta
# Install versions published before a specific date
$ vlt install lodash --before="2024-01-01"Workspace options
| Flag | Short | Description |
|---|---|---|
--workspace=<ws> | -w | Limit operation to specific workspaces |
--workspace-group=<group> | Operate on named workspace groups | |
--no-migrate-workspaces | Don't save workspaces to vlt.json |
# Install in specific workspace
$ vlt install -w packages/core lodash
# Install in workspace group
$ vlt install --workspace-group apps expressGlobal options
| Flag | Short | Description |
|---|---|---|
--global | -g | Install into the global project, see Global installs |
--global-dir=<path> | Location of the global project (default: vlt/global in the XDG data dir) | |
--global-bin-dir=<path> | Where bins of global packages are linked (default: next to vlt) |
Global installs
Install command line tools once, for use anywhere, with -g:
$ vlt install -g eslint
# install a local tool
$ vlt install -g ./my-toolFor a walkthrough and how to version your global tools, see Global Installs and its best practices.
Global packages live in a regular vlt project at --global-dir
(vlt/global in the XDG data directory by default, e.g.
~/.local/share/vlt/global on Linux):
- each package gets its own workspace,
packages/<name>-ws(packages/@scope+name-wsfor scoped packages), with the package as its only dependency - the project has its own
vlt-lock.jsonandnode_modules - bins of global packages are linked next to
vltitself (the folder your shell foundvltin), so they run right away. If that folder isn't writable, vlt uses~/.local/binwhen it's in yourPATH(not on Windows), else (or whenvltwasn't run from yourPATH)<global-dir>/bin, and prints the line that adds it to yourPATH. Set--global-bin-dirto pick the folder. Or runvlt setup --path(vlt setupoffers it at its end); in a terminal,vlt install -goffers to do it once (answernand it won't ask again).--yesadds it without asking.
The current project is never read or changed by a global install,
including its vlt.json. global, global-dir and global-bin-dir
can be set on the command line, via env (VLT_GLOBAL,
VLT_GLOBAL_DIR, VLT_GLOBAL_BIN_DIR) or in the user config.
With Node.js from the nodejs.org macOS installer, vlt is in a
root-owned /usr/local/bin, so bins usually go to
~/Library/Application Support/vlt/global/bin. Run
vlt setup --path, use Node.js from Homebrew, nvm or fnm, or set
--global-bin-dir to a folder in your PATH. A --global-bin-dir
with spaces needs no escaping in the user config (quote it on the
command line as usual).
A package name with no version installs the latest dist-tag (or
--tag), like npm install -g, so re-running it upgrades. Use a
version or range to pin one instead.
# upgrade to latest
$ vlt install -g eslint
# pin a version range
$ vlt install -g eslint@9
# list global packages
$ vlt ls -g
# run their install scripts
$ vlt build -g
# remove a global package and its bins
$ vlt uninstall -g eslintRunning vlt install -g with no packages re-installs the global
project and re-links all bins into the current bin folder, removing
the ones vlt linked into folders it used before (e.g. under another
Node.js version).
A bin name already in the bin folder (linked by another global
package, or a file not created by vlt) is skipped with a warning. Use
--force to overwrite it. Outside <global-dir>/bin, --force only
replaces links, never plain files or the running vlt.
Like any install, scripts of global packages are not run, use
vlt build -g to run them.
--workspace, --workspace-group and --lockfile-only are not
supported with -g, and the dependency type flags (--save-dev,
--save-types, etc.) are ignored. -g is supported by install,
uninstall, list, query and build.
Updating vlt
vlt install -g vlt updates the vlt your shell runs (the one in
your PATH), the way it was installed:
- installed with npm (e.g. by the install script): runs that npm,
npm install -g vlt@<version> - installed as a global package: re-installs it like any global package
- installed some other way: says how to update it instead (e.g.
brew upgrade vlt)
If vlt can't write where it is installed, it installs itself as a
global package instead and links its bins like any other. The old
vlt keeps running until that folder comes before it in your PATH.
A bare vlt never downgrades: when latest is older than the running
vlt, it says so. Use vlt@latest or a version to install it anyway.
$ vlt install -g vlt
$ vlt install -g vlt@latestPhased Package Installations
Traditional package managers run lifecycle scripts automatically during install—a security risk that allows malicious packages to execute arbitrary code.
vlt separates installation into two distinct phases:
- Install phase: Downloads and extracts packages (no scripts)
- Build phase: Runs lifecycle scripts for selected packages
# Phase 1: Install packages safely
$ vlt install
# Phase 2: Build with control over what runs
$ vlt buildThis separation enables you to:
- Inspect dependencies before any code execution
- Selectively allow scripts using DSS queries
- Protect against supply chain attacks
By default, vlt build uses :scripts:not(:built):not(:malware)
which excludes packages with known malware alerts. vlt build <query>
saves the query as allow-scripts, so later installs run those
scripts.
For more details, see the vlt build
documentation.
Graph Modifiers
Graph Modifiers provide precise control over dependency resolution
using Dependency Selector Syntax (DSS).
Configure them in your vlt.json:
{
"modifiers": {
"#react": "^19",
":root > #webpack > #browserslist": "^4.23.0"
}
}Override all instances
Pin a package version across the entire dependency graph:
{
"modifiers": {
"#lodash": "^4.17.21"
}
}Override transitive dependencies
Target specific dependency paths:
{
"modifiers": {
":root > #webpack > #terser-webpack-plugin > #terser": "^5.31.0"
}
}When multiple selectors match, the most specific one wins.
For more details on Graph Modifiers, see the Graph Modifiers documentation.
Peer Dependencies
vlt handles peer dependencies by isolating their contexts, allowing multiple versions of the same package to coexist when necessary. When different parts of your dependency tree require incompatible peer versions, vlt automatically subdivides the graph into separate peer dependency contexts.
Key behavior: vlt always attempts to use a single version of a
package for peer dependencies. When that's not possible, it duplicates
packages as needed, storing them in separate locations within
node_modules/.vlt/ and linking appropriately so each package
resolves to its correct peer version.
This happens automatically during vlt install—no configuration
required.
For more details, see the Peer Dependencies documentation.
Notes
- Running
vlt installwith no arguments installs all dependencies frompackage.json - The lockfile (
vlt-lock.json) ensures reproducible installs across environments - Workspaces read from
package.jsonorpnpm-workspace.yamlare saved tovlt.json(not with--frozen-lockfile/--expect-lockfile), see Workspaces - Use
vlt ciin CI pipelines: removesnode_modules, installs exactly fromvlt-lock.json(like--expect-lockfile) and, likevlt install, runs no dependency lifecycle scripts unless--allow-scriptsis set; runvlt buildafter to build packages - With
vlt-lock.jsonand every registry package cached,--frozen-lockfilemakes no registry requests (git deps may still clone). Registry and cache come fromvlt.json,VLT_*env vars or flags;.npmrcandnpm_config_*aren't read. Telemetry is still sent unless disabled. - Peer dependencies are handled automatically with context isolation when version conflicts arise
Local directories (Linked Folders)
# Relative path
$ vlt install ./my-local-package
# Install from parent directory
$ vlt install ../shared-libLocal directories can be symlinked to a project via the file:
protocol. Note that these differ from local tarballs
(file:./pkg.tgz) which are extracted and treated as immutable.
Installing from within a Linked Folder
When you cd into a linked folder and run vlt install, the vlt cli
allows you to add dependencies to that folder's package.json:
# Project has: "my-lib": "file:./libs/my-lib"
$ cd libs/my-lib $ vlt install lodash@^4.17.0
# Adds lodash to libs/my-lib/package.jsonvlt detects your current working directory relative to the project
root and targets the appropriate package.json.
Note: to use vlt install or vlt uninstall to manage dependencies
of a local linked directory, that folder needs to be already in the
chain of dependencies of either the root project or one of its
workspaces in order for the vlt cli to properly manage the
operation.
Relative paths in Linked Folders
File specs resolve relative to the linked folder's location:
# From libs/my-lib, reference ../shared
$ cd libs/my-lib
$ vlt install file:../sharedAliases
i, add