lpm.json
Canonical schema reference for the lpm.json project file.
lpm.json sits next to package.json and provides LPM-specific configuration that doesn't fit npm's manifest shape — runtime pinning, task graph, dev services, local-domain proxy config, tunnel domain, env-file mapping, env-var schema, and multi-registry publish settings.
The file is optional. Every field has a default. When lpm.json is missing, LPM falls back to package.json semantics (scripts, engines, etc.).
This page is the schema-by-field reference. For a usage-oriented overview of what lpm dev reads from lpm.json, see lpm dev.
Editor autocomplete
LPM publishes a JSON Schema for lpm.json at https://lpm.dev/schemas/lpm.json — auto-derived from the typed Rust struct that the CLI consumes. Add a $schema line to get inline validation and field completion in any editor that supports JSON Schema (VS Code, Cursor, JetBrains):
{
"$schema": "https://lpm.dev/schemas/lpm.json",
"runtime": { "node": ">=22.0.0", "bun": "1.3.14" }
}Or emit the schema yourself from the locally-installed CLI:
lpm schema lpm.json # to stdout
lpm schema lpm.json -o lpm.schema.json # to a fileBoth lpm schema lpm.json and the published copy are regenerated from the same Rust struct that parses your manifest at install time, so they describe the surface the CLI actually accepts. The published copy is updated alongside CLI releases — between releases, lpm schema lpm.json is the source of truth for whichever version is on your machine.
When stdout is an interactive terminal, schema output may use syntax color. Redirected or piped output remains plain pretty-printed JSON, including when color is forced globally.
Top-level shape
{
"$schema": "https://lpm.dev/schemas/lpm.json",
"vault": "<uuid-v4>",
"runtime": { "<runtime>": "<semver>" },
"tools": { "<tool>": "<version>" },
"https": true,
"cert": { "extraPermittedDns": ["myapp.local"], "allowPublicDns": false },
"tunnel": { "domain": "<full-domain>" },
"proxy": { "host": "<local-hostname>", "port": 443, "httpRedirect": true },
"env": { "<script>": "<.env-file>" },
"envSchema": { "vars": { "<NAME>": { "required": true, "format": "url" } } },
"environments": { "<name>": { "extends": "<base>", "file": "<.env-file>" } },
"tasks": { "<name>": { /* TaskConfig */ } },
"services": { "<name>": { /* ServiceConfig */ } },
"publish": { /* PublishConfig */ }
}Every top-level field is optional and defaults to empty / null.
runtime
{ "runtime": { "node": ">=22.0.0", "bun": "1.3.14" } }| Field | Type | Default | Notes |
|---|---|---|---|
node | string | — | Managed Node version spec. Exact versions, prefixes, semver ranges, latest, and lts are accepted. |
bun | string | — | Managed Bun version spec. Exact versions, vX.Y.Z, bun-vX.Y.Z, latest, prefixes, and semver ranges are accepted. lts is rejected because Bun has no LTS channel. |
runtime.node takes precedence over package.json > engines.node and .nvmrc. runtime.bun is the only Bun runtime detection source; package.json > engines.bun is not enforced. Both are auto-installed via lpm use.
When both runtimes are selected, script PATH is prepended as node_modules/.bin, managed Node bin, managed Bun bin, inherited PATH. runtime.bun exposes bun to scripts; it does not make lpm run invoke bun run.
tools
{ "tools": { "oxlint": "1.57.0", "biome": "2.4.8" } }| Field | Type | Notes |
|---|---|---|
<tool> | string | Pin a built-in tool to a specific version. Honored by lpm lint and lpm fmt only — those are the plugin-backed tools. |
Only oxlint and biome are plugin-backed today. Any other key (tools.typescript, tools.tsgo, tools.eslint, …) is dropped silently by the resolver — lpm warns once per command invocation that the pin is ignored. To pin the lpm check engines, declare typescript or @typescript/native-preview in package.json > devDependencies like any other project dep.
When the pinned version isn't already in ~/.lpm/cache/, the next invocation of the tool downloads it. Pinned versions verify against the upstream <asset_url>.sha256 sidecar; see Built-in tools for the trust model.
https
{ "https": true }| Type | Default | Notes |
|---|---|---|
bool | null (unset) | When true, lpm dev serves over HTTPS by default. CLI --no-https overrides. |
cert
{ "cert": { "extraPermittedDns": ["myapp.local"], "allowPublicDns": false } }| Field | Type | Default | Notes |
|---|---|---|---|
extraPermittedDns | string[] | [] | Extra validated DNS subtrees to permit on the constrained project intermediate. These are policy constraints, not extra browser SANs and not proxy routes. |
allowPublicDns | bool | false | Allows public DNS names in extraPermittedDns and local-domain host fields. Leave off for purely local names. |
extraPermittedDns entries must be bare multi-label names such as myapp.local; LPM derives both the exact subtree and subdomain subtree for the project intermediate. Use --host or proxy.host / services.<name>.host for actual browser hostnames.
tunnel
{ "tunnel": { "domain": "acme-api.lpm.llc" } }| Field | Type | Notes |
|---|---|---|
domain | string | Stable tunnel domain (must already be claimed via lpm tunnel claim). Pro/Org only — free users get an ephemeral random domain. |
CLI flags: lpm dev --tunnel, lpm dev --domain <NAME>, lpm tunnel <port> <domain>.
proxy
{ "proxy": { "host": "app.localhost", "port": 443, "httpRedirect": true } }| Field | Type | Default | Notes |
|---|---|---|---|
host | string | — | Friendly top-level local hostname registered by lpm dev when the proxy daemon has an HTTPS listener. lpm dev also prepares constrained project cert-chain coverage and updates the hosts file when this host is not under .localhost. |
port | number | 443 | HTTPS proxy listen port used by lpm proxy start when no explicit listener flags are passed. 0 lets the OS pick a free port. |
httpRedirect | bool | true | Whether lpm proxy start also binds an HTTP-to-HTTPS redirect listener on port 80 when no explicit listener flags are passed. |
proxy.host and services.<name>.host are normalized to lowercase, must use a local TLD (.localhost, .test, .local, .internal, .home.arpa) unless cert.allowPublicDns is true, and cannot duplicate another local-domain host in the same file.
When local-domain config is present and no daemon is running, lpm dev starts lpm proxy start --detach automatically. With no explicit listener flags, that start path uses proxy.port and proxy.httpRedirect. With a running HTTPS listener, lpm dev prepares the project certificate chain for configured hosts through the normal trust consent flow, writes managed hosts-file entries for hosts that are not localhost / *.localhost, registers routes against final assigned service ports, and releases both route and hosts-file entries when the dev session exits. On Unix, LPM uses sudo for the system hosts file when the current process lacks permission; on Windows, it asks for Administrator elevation through UAC. Use lpm hosts clean to remove orphaned managed blocks after interrupted sessions. The TLS daemon also prepares or refreshes project certificate chains during route registration, but it does not install the root CA into the trust store. The startup banner shows configured proxy hosts, exact route lines print after registration, and dashboard service rows include the host URLs. Proxy-only cert preparation does not inject framework HTTPS env vars into the app; app HTTPS remains controlled by https / --https.
vault
{ "vault": "7f3a1e2c-5b9d-4a8f-b6c1-9b1d2e3f4a5b" }| Type | Notes |
|---|---|
| string | UUID v4. Stable opaque identifier that links this project to a vault row on lpm.dev. Generated and written back by the CLI on first lpm env use if missing. Commit it to git — it's a pointer, not a secret. |
The value is path-traversal-validated: empty strings, /, \, .. substrings, leading ~, control characters, and absolute-drive prefixes are rejected (the value is joined into ~/.lpm/vaults/<id>.enc on machines using the file fallback). Removing the field regenerates a fresh UUID on next use, which decouples the working directory from the previous vault without deleting server-side data.
LPM_VAULT_ID overrides per-invocation. See Secrets vault — Per-project identity.
vaultSync
{
"vaultSync": {
"personalVersion": 7,
"orgVersions": { "acme": 12 }
}
}| Field | Type | Notes |
|---|---|---|
personalVersion | int | Last known personal cloud-vault version this checkout has seen. |
orgVersions.<slug> | int | Last known org-shared cloud-vault version per org slug. |
Written by lpm env push / lpm env pull for conflict-detection (CAS) against the server. Don't edit by hand; mismatches surface as 409 VaultConflictError and the CLI tells you to pull, reconcile, and re-push.
env
{
"env": {
"dev": ".env.development",
"staging": ".env.staging",
"prod": ".env.production"
}
}Map of script name → env file. lpm run dev loads .env.development; lpm run staging loads .env.staging. CLI --env=<MODE> overrides per-invocation (and applies to lpm dev too).
envSchema
{
"envSchema": {
"vars": {
"DATABASE_URL": { "required": true, "format": "url" },
"API_KEY": { "required": true, "secret": true },
"LOG_LEVEL": { "default": "info", "pattern": "^(trace|debug|info|warn|error)$" }
}
}
}Schema for env-var validation. Drives the env check that runs before lpm run, lpm dev, and lpm exec. Per-var fields:
| Field | Type | Notes |
|---|---|---|
required | bool | Fail if not set |
format | string | Built-in validator. One of: url, email, port, boolean, integer, hostname, ip |
pattern | string | Regex the value must match |
default | string | Default value if unset |
secret | bool | Treat as sensitive — masked in logs and lpm env list |
Skip the check per-invocation with --no-env-check.
environments
{
"environments": {
"base": { "file": ".env" },
"staging": { "extends": "base", "file": ".env.staging" },
"preview": { "extends": "staging", "file": ".env.preview" }
}
}Named environment definitions with inheritance. Useful when you have many .env.* variants and want to avoid duplicating shared keys. extends chains resolve transitively.
tasks.<name>
{
"tasks": {
"build": {
"command": "tsup",
"dependsOn": ["^build"],
"cache": true,
"outputs": ["dist/**"],
"inputs": ["src/**", "package.json"],
"env": "production"
}
}
}| Field | Type | Default | Notes |
|---|---|---|---|
command | string | falls back to package.json > scripts.<name> | Override the script body |
dependsOn | string[] | [] | Other tasks that must finish first. "build" = same package; "^build" = the same task in upstream workspace deps. |
cache | bool | false | Enable task caching. Requires outputs. |
outputs | string[] | [] | Globs of files this task produces (e.g. ["dist/**"]). Required when cache: true. |
inputs | string[] | sensible defaults* | Globs that invalidate the cache. |
env | string | — | Env mode for this task ("production" loads .env.production). |
*Default inputs: src/**, lib/**, app/**, pages/**, components/**, package.json, tsconfig.json, tsconfig.*.json, *.config.{js,ts,mjs}.
remoteCache
{
"remoteCache": {
"enabled": true,
"team": "acme",
"url": "https://lpm.dev/v8",
"signature": true,
"readOnly": false,
"env": {
"include": ["CI", "NODE_ENV"],
"exclude": ["*_TOKEN", "*_SECRET", "DATABASE_URL"]
}
}
}Hosted cache settings for tasks that already opt in with tasks.<name>.cache: true and outputs. LPM checks the local task cache first, then the hosted cache, then runs the task and uploads the result when the run succeeds.
| Field | Type | Default | Notes |
|---|---|---|---|
enabled | bool | false | Enable hosted cache reads and writes |
team | string | personal namespace | Organization slug/team namespace |
url | string | configured registry + /v8 | Remote cache endpoint. Hosts outside the configured registry origin require LPM_REMOTE_CACHE_TOKEN and LPM_REMOTE_CACHE_SIGNATURE_KEY. |
signature | bool | false | Require LPM_REMOTE_CACHE_SIGNATURE_KEY; downloads without a valid HMAC tag are treated as misses. Third-party cache hosts always require a signing key. |
readOnly | bool | false | Read hosted artifacts but skip uploads |
env.include | string[] | [] | Variable-name patterns explicitly allowed for remote uploads |
env.exclude | string[] | [] | Variable-name patterns that block remote uploads |
env.allowSecrets | bool | false | Permit secret-looking env var names after project opt-in |
Secret-looking loaded env vars (TOKEN, SECRET, PASSWORD, DATABASE_URL, *_KEY, etc.) block remote uploads by default. If a blocked value appears in captured stdout/stderr, the upload is also skipped. Local task caching still works.
services.<name>
{
"services": {
"db": { "command": "docker compose up postgres", "readyPort": 5432, "readyTimeout": 60 },
"api": {
"command": "node server.js",
"port": 4000,
"dependsOn": ["db"],
"env": { "DATABASE_URL": "postgres://localhost:5432/myapp" }
},
"web": { "command": "next dev", "port": 3000, "primary": true, "host": "web.app.localhost" }
}
}| Field | Type | Default | Notes |
|---|---|---|---|
command | string | required | Shell command to run |
port | number | — | Port the service listens on. If omitted for a host-only service, lpm dev auto-assigns and persists one |
dependsOn | string[] | [] | Services that must be ready before this one starts |
readyPort | number | falls back to port | TCP port to poll for readiness |
readyUrl | string | — | HTTP URL to poll for readiness — first 2xx wins |
readyTimeout | number | 30 | Seconds to wait before failing |
env | object | {} | Extra env vars injected into this service only |
restart | bool | false | Auto-restart on crash with exponential backoff |
primary | bool | false | Receives --https / --tunnel / --network flags from lpm dev, plus the browser-open |
host | string | — | Friendly local hostname registered by lpm dev against the final assigned service port when the proxy daemon has an HTTPS listener. lpm dev also prepares constrained project cert-chain coverage and updates the hosts file when the host is not under .localhost. With port, localhost:<port> keeps working; without port, lpm dev injects the assigned port as PORT. |
cwd | string | project root | Working directory, relative to project root |
publish
{
"publish": {
"registries": ["lpm", "npm"],
"lpm": { "name": "@lpm.dev/owner.pkg" },
"npm": { "name": "@scope/pkg", "access": "public", "tag": "latest", "otpRequired": true },
"github": { "name": "@owner/pkg", "access": "public" },
"gitlab": { "name": "pkg", "access": "public", "projectId": "12345", "registry": "https://gitlab.com" }
}
}Top-level
| Field | Type | Default | Notes |
|---|---|---|---|
registries | string[] | ["lpm"] | Targets a single lpm publish invocation hits. Values are lpm, npm, github, gitlab, or an https:// custom registry URL. CLI flags (--lpm, --npm, --github, --gitlab, --publish-registry) override. |
publish.lpm
| Field | Type | Notes |
|---|---|---|
name | string | Override the lpm.dev package name. Must match @lpm.dev/owner.pkg. |
publish.npm
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | package.json > name | Override the npm package name (e.g., "@scope/pkg"). Required if package.json > name starts with @lpm.dev/. |
access | "public" | "restricted" | scope-based* | Visibility |
tag | string | "latest" | dist-tag for the published version |
registry | string | https://registry.npmjs.org | Custom npm-compatible registry URL. npm Trusted Publishing/OIDC is attempted only for the default npm registry; custom registries use token auth. |
otpRequired | bool | false | Prompt for OTP before the first publish attempt (saves a round-trip) |
*Scoped packages default to "restricted", unscoped to "public".
publish.github
| Field | Type | Notes |
|---|---|---|
name | string | Override the GitHub Packages name. Must be scoped (@owner/pkg). |
access | "public" | "restricted" | Visibility |
publish.gitlab
| Field | Type | Default | Notes |
|---|---|---|---|
name | string | — | Override the GitLab Packages name |
access | "public" | "restricted" | — | Visibility |
projectId | string | required | GitLab project ID — needed to construct the registry URL |
registry | string | https://gitlab.com | GitLab instance URL |
Precedence with other config
For fields with multiple sources, the precedence chain is:
- CLI flag (highest)
lpm.jsonpackage.json(where applicable —engines,scripts)~/.lpm/config.toml(user-level, where applicable)- Built-in default (lowest)
See also
lpm dev— the most common consumer, with usage-oriented exampleslpm run— readstasksandenvlpm publish— readspublishpackage.json"lpm" key — sibling config blocklpm.toml— project-level CLI defaults~/.lpm/config.toml— user-level CLI defaults