# lpm.json (/docs/reference/lpm-json)



`lpm.json` sits next to `package.json` and provides LPM CLI-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 CLI falls back to `package.json` semantics (scripts, engines, etc.).

LPM CLI reads `lpm.json` with a 16 MiB limit enforced before JSON parsing. Missing retains the optional fallback above; an oversized file is a path-specific error and aborts before a configured script, service, or child process is spawned. See [local configuration size limits](/docs/project-setup#local-configuration-size-limits).

This page is the schema-by-field reference. For a usage-oriented overview of what `lpm dev` reads from `lpm.json`, see [`lpm dev`](/docs/dev/dev#configuration-lpmjson).

## Editor autocomplete [#editor-autocomplete]

LPM CLI publishes a JSON Schema for `lpm.json` at [`https://cli.lpm.dev/schemas/lpm.json`](https://cli.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):

```json title="lpm.json"
{
  "$schema": "https://cli.lpm.dev/schemas/lpm.json",
  "runtime": { "node": ">=22.0.0", "bun": "1.3.14" }
}
```

Or emit the schema yourself from the locally-installed CLI:

```bash
lpm schema lpm.json                       # to stdout
lpm schema lpm.json -o lpm.schema.json    # to a file
```

Both `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.

The older `https://lpm.dev/schemas/lpm.json` URL remains a compatibility alias for existing repositories.

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 [#top-level-shape]

```json title="lpm.json"
{
  "$schema":      "https://cli.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]

```json
{ "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`](/docs/dev/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]

```json
{ "tools": { "oxlint": "1.57.0", "biome": "2.4.8", "rolldown": "1.1.3" } }
```

| Field    | Type   | Notes                                                                                                                                                           |
| -------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<tool>` | string | Pin a built-in managed plugin to a specific version. Honored by [`lpm lint`](/docs/dev/lint), [`lpm fmt`](/docs/dev/fmt), and [`lpm bundle`](/docs/dev/bundle). |

Only `oxlint`, `biome`, and `rolldown` are managed plugin keys today. Any other key (`tools.typescript`, `tools.tsgo`, `tools.eslint`, …) is ignored 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 an Oxlint or Biome pin isn't already in `~/.lpm/plugins/`, the next invocation downloads and verifies it against the upstream `<asset_url>.sha256` sidecar. A Rolldown pin must already be the bundled floor or an approved version from `lpm plugin update rolldown`; `lpm bundle` does not check upstream during normal execution. See [Built-in tools](/docs/dev/builtin-tools#checksum-verification) for the trust model.

## `https` [#https]

```json
{ "https": true }
```

| Type   | Default        | Notes                                                                                             |
| ------ | -------------- | ------------------------------------------------------------------------------------------------- |
| `bool` | `null` (unset) | When `true`, [`lpm dev`](/docs/dev/dev) serves over HTTPS by default. CLI `--no-https` overrides. |

## `cert` [#cert]

```json
{ "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 CLI 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]

```json
{ "tunnel": { "domain": "acme-api.lpm.llc" } }
```

| Field    | Type   | Notes                                                                                                                                                  |
| -------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `domain` | string | Stable tunnel domain (must already be claimed via [`lpm tunnel claim`](/docs/infra/tunnel)). 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]

```json
{ "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 CLI 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`](/docs/infra/hosts) 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 marks proxy endpoints as resolving until assignment; exact route lines print after registration, and dashboard service rows update to the final ports. LPM CLI does not inject certificate paths or framework HTTPS variables into the app; `https` / `--https` enables a separate LPM CLI-owned TLS frontend over the verified HTTP child endpoint.

## `vault` [#vault]

```json
{ "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`](/docs/reference/env-vars#auth-and-routing) overrides per-invocation. See [Secrets vault — Per-project identity](/docs/infra/secrets-vault#per-project-identity).

## `vaultSync` [#vaultsync]

```json
{
  "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]

```json
{
  "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`](/docs/dev/dev) too).

## `envSchema` [#envschema]

```json
{
  "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`, `lpm <file>`, 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]

```json
{
  "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>` [#tasksname]

```json
{
  "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]

```json
{
  "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 CLI 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>` [#servicesname]

```json
{
  "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 or the implicit or explicit primary 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`              | Its verified endpoint receives HTTPS, tunnel, network, and browser-open handling                                                                                                                                                                                                                                                                                                       |
| `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]

```json
{
  "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 [#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. Custom URL entries require an exact token from `lpm login --login-registry <URL> --token <T>`. CLI flags (`--lpm`, `--npm`, `--github`, `--gitlab`, `--publish-registry`) override. |

### `publish.lpm` [#publishlpm]

| Field  | Type   | Notes                                                               |
| ------ | ------ | ------------------------------------------------------------------- |
| `name` | string | Override the lpm.dev package name. Must match `@lpm.dev/owner.pkg`. |

### `publish.npm` [#publishnpm]

| 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"` | `"public"`                   | Visibility. npm provenance requires effective access to be `"public"`.                                                                                                                                                    |
| `tag`         | string                       | `"latest"`                   | dist-tag for the published version                                                                                                                                                                                        |
| `registry`    | string                       | `https://registry.npmjs.org` | Custom npm-compatible registry URL. Non-default registries require an exact token from `lpm login --login-registry <URL> --token <T>`; `NPM_TOKEN` and `lpm login --npm` auth are only used for the default npm registry. |
| `otpRequired` | bool                         | `false`                      | Prompt for OTP before the first publish attempt (saves a round-trip)                                                                                                                                                      |

### `publish.github` [#publishgithub]

| Field    | Type                         | Notes                                                             |
| -------- | ---------------------------- | ----------------------------------------------------------------- |
| `name`   | string                       | Override the GitHub Packages name. Must be scoped (`@owner/pkg`). |
| `access` | `"public"` \| `"restricted"` | Visibility                                                        |

### `publish.gitlab` [#publishgitlab]

| 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 [#precedence-with-other-config]

For fields with multiple sources, the precedence chain is:

1. **CLI flag** (highest)
2. **`lpm.json`**
3. **`package.json`** (where applicable — `engines`, `scripts`)
4. **`~/.lpm/config.toml`** (user-level, where applicable)
5. **Built-in default** (lowest)

## See also [#see-also]

* [`lpm dev`](/docs/dev/dev) — the most common consumer, with usage-oriented examples
* [`lpm run`](/docs/dev/run) — reads `tasks` and `env`
* [`lpm publish`](/docs/packages/publish) — reads `publish`
* [`package.json` "lpm" key](/docs/reference/package-json-lpm) — sibling config block
* [JSON Schemas](/docs/reference/schemas) — published schema URLs and `lpm schema`
* [`lpm.toml`](/docs/reference/lpm-toml) — project-level CLI defaults
* [`~/.lpm/config.toml`](/docs/reference/config-toml) — user-level CLI defaults
