Save policy
How LPM writes version ranges to package.json — caret default, explicit preservation, prerelease safety.
When lpm install <pkg> mutates package.json, the save policy decides what string lands in dependencies (or devDependencies). The decision is a pure function of: what you typed, the resolved version, your CLI flags, and your config.
This page is the reference for the rules. For the CLI flag surface, see lpm install. For the file format, see lpm.toml and ~/.lpm/config.toml.
The decision table
| You ran | package.json ends up with | Why |
|---|---|---|
lpm install zod | "zod": "^4.3.6" | Bare install — caret default |
lpm install zod@4.3.6 | "zod": "4.3.6" | Explicit exact — preserved verbatim |
lpm install zod@^4.3.0 | "zod": "^4.3.0" | Explicit range — preserved verbatim |
lpm install zod@~4.3.6 | "zod": "~4.3.6" | Explicit tilde — preserved verbatim |
lpm install zod@latest | "zod": "^4.3.6" | Dist-tag → caret default |
lpm install zod@beta | "zod": "4.4.0-beta.2" | Prerelease — saved exact for safety |
lpm install zod@* | "zod": "*" | Explicit wildcard — preserved (only allowed when explicit) |
lpm install zod@workspace:* | "zod": "workspace:*" | Workspace protocol — preserved verbatim |
lpm install --catalog zod | "zod": "catalog:" | Explicit default-catalog save when the root default catalog entry matches the resolved version |
lpm install --catalog=ui zod | "zod": "catalog:ui" | Explicit named-catalog save when catalogs.ui.zod matches the resolved version |
The throughline: what you typed wins. The default kicks in only when you didn't say anything.
Precedence
Seven layers, highest first:
- Explicit catalog save.
--catalogor--catalog=<name>writescatalog:orcatalog:<name>when the selected root catalog entry exists and satisfies the resolved version. - Explicit user spec in
pkg@spec. Exact, range, wildcard, workspace — preserved verbatim, never reinterpreted. - CLI flag override.
--exact,--tilde,--save-prefix '<p>'. Mutually exclusive (clap enforces). - Prerelease-exact safety. If the resolved version is a prerelease and no CLI flag forced something else, save the exact resolved version. Prereleases shouldn't auto-widen under a forgotten
save-prefixconfig setting. - Persistent config.
save-exact = true, thensave-prefix = "^|~|". Reads from./lpm.tomlfirst, then~/.lpm/config.toml. - Catalog mode.
package.json > lpm.catalogMode = "prefer"or"strict"can save matching default-catalog entries ascatalog:; strict mode fails on missing or mismatched default entries. - Default.
^resolvedVersion.
Each tier feeds the next only when the higher one is silent. Direct save-prefix controls do not override what you typed; --catalog is the deliberate exception because it explicitly asks LPM to save a catalog reference after verifying the selected catalog matches the resolved version.
CLI flags
lpm install zod --exact # save the exact resolved version, no prefix
lpm install zod --tilde # save with ~ prefix
lpm install zod --save-prefix '' # same as --exact (empty prefix)
lpm install zod --save-prefix '~' # same as --tilde
lpm install zod --save-prefix '^' # explicit caret (the default)
lpm install --catalog zod # save as catalog:
lpm install --catalog=ui zod # save as catalog:ui--exact, --tilde, --save-prefix, and --catalog[=<name>] are mutually exclusive save-policy flags. * is not a valid --save-prefix value — wildcards must be requested per-package via pkg@*.
Setting any of these flags forces a rewrite (see Bare reinstall below).
--catalog is a save policy, not a catalog editor. The target entry must already exist in the root catalog set (package.json > catalogs or pnpm-workspace.yaml > catalog / catalogs), and the resolved version must satisfy that catalog range. On mismatch or missing entry, LPM rolls package.json back and exits with an error.
Persistent config
Pin a default for one project:
save-prefix = "~"
save-exact = falseOr for every project on this machine:
save-prefix = "^"
save-exact = false| Key | Type | Default | Notes |
|---|---|---|---|
save-prefix | "^" | "~" | "" | "^" | Prefix used when nothing else applies |
save-exact | bool | false | Force exact saves regardless of save-prefix |
Manage ~/.lpm/config.toml via the CLI:
lpm config set save-prefix '~'
lpm config set save-exact trueInvalid values (save-prefix = "*", save-prefix = ">=", etc.) are rejected at load time with a clear error pointing at the file.
Prerelease safety
lpm install zod@next # resolves to e.g. 4.4.0-beta.2
# package.json now has: "zod": "4.4.0-beta.2"Prereleases are saved exact even when your config says save-prefix = "^". The reasoning: a ^4.4.0-beta.2 range matches 4.4.0-beta.3 (and every other 4.x prerelease + stable), which is almost never what you want. The next prerelease could fix nothing or break everything; you should opt into the bump explicitly.
Override per-invocation if you want the caret behavior anyway:
lpm install zod@next --save-prefix '^' # → "^4.4.0-beta.2"Bare reinstall doesn't rewrite
lpm install zod # already in package.json as "~4.3.6"
# → still "~4.3.6", refresh-onlyRe-installing an existing dep without a version or override flag refreshes the lockfile + store entry but doesn't rewrite the existing range. Your team's intentional ~4.3.6 pin survives a routine reinstall.
To force a rewrite, set any flag (--exact, --tilde, --save-prefix) — even one that matches the existing prefix.
Why no * default
* matches every version, including future major bumps that break your code. There is no save policy under which the default is "*". The only way to get "*" is to type it: lpm install zod@*. Wildcards are a deliberate per-package opt-in.
This is enforced at every layer:
--save-prefix '*'is rejected at the CLI parsersave-prefix = "*"is rejected at the TOML config loader- The bare-install default never produces
"*", regardless of resolved version
Workspace protocol
lpm install @my-co/shared@workspace:*
# package.json: "@my-co/shared": "workspace:*"workspace:*, workspace:^, workspace:~, and workspace:<range> are always preserved verbatim. Save policy doesn't touch them — workspace protocol semantics are out of scope.
Why this is in lpm.toml, not package.json
The save-policy config keys (save-prefix, save-exact) deliberately don't live under package.json > lpm. Reasons:
- Save policy is tool behavior, not publishable metadata. It doesn't ship to consumers of your package.
- It changes more often. Tweaking a personal preference shouldn't dirty the manifest.
- Teams disagree about whether to commit it.
lpm.tomllets each repo decide — commit it for project-shared policy,.gitignoreit for individual preference.
See also
lpm install— CLI flag referencelpm.toml— project-level config file~/.lpm/config.toml— user-level config filelpm config— manage user-level keys from the CLI