lpm.json
Configure project runtimes, tasks, development services, environments, and publish targets.
lpm.json stores project configuration for LPM CLI beside package.json.
Use it to share runtime versions, task behavior, development services, and publish targets with your team.
The file is optional. An empty object is valid.
{
"$schema": "https://cli.lpm.dev/schemas/lpm.json",
"runtime": { "node": "22" },
"tasks": {
"build": {
"command": "tsup",
"cache": true,
"outputs": ["dist/**"]
}
}
}lpm run build
lpm doctorKeep dependencies and ordinary scripts in package.json.
Use package.json > lpm for installation and dependency policy.
Use lpm.toml for project CLI defaults.
The similarly named lpm.config.json describes source packages for lpm add.
Editor autocomplete
Add $schema to get field completion and checks in editors that support JSON Schema.
The public URL is https://cli.lpm.dev/schemas/lpm.json.
The older https://lpm.dev/schemas/lpm.json URL remains a compatibility alias.
To use the schema from your installed CLI version:
lpm schema lpm.json -o lpm.schema.jsonlpm schema emits a schema. It does not check your project file.
The CLI also checks constraints that JSON Schema cannot express, such as service cycles and host conflicts.
Top-level shape
All top-level fields are optional. Omit fields that your project does not use.
An optional field does not necessarily accept null.
| Field | Purpose | Default |
|---|---|---|
$schema | Editor schema URL. | Unset. |
name | Display name for environment sync when package.json has no name. | Project directory name. |
runtime | Managed runtime selectors. | {}. |
tools | Managed tool version pins. | {}. |
tasks | Commands, dependencies, and task caching. | {}. |
remoteCache | Hosted task cache configuration. | Disabled. |
services | Development processes and readiness checks. | {}. |
https | Local HTTPS for development. | Unset. |
cert | Additional certificate constraints. | Unset. |
proxy | Local hostname and proxy listener defaults. | Unset. |
tunnel | Claimed tunnel domain for development. | Unset. |
env | Environment file mappings by script name. | {}. |
envSchema | Environment variable requirements. | Unset. |
environments | Named environment inheritance. | Unset. |
vault | Stable project identity for encrypted environments. | Created by lpm env. |
vaultSync | Env sync state from earlier releases. LPM CLI moves it to .lpm/env-sync.json. | Unset. |
publish | Registry targets and publication defaults. | LPM.dev Registry. |
LPM CLI reads configuration from the relevant project directory.
Project commands such as lpm env, lpm dev, and lpm run use the nearest directory at or above the current one that has a package.json or an lpm.json, stopping before your home directory.
Workspace inheritance differs by feature. The file is not a universal set of workspace defaults.
runtime
{ "runtime": { "node": "22", "bun": "1.3.14" } }| Field | Accepted selectors |
|---|---|
node | Exact versions, prefixes, semver ranges, latest, and lts. |
bun | Exact versions, prefixes, semver ranges, vX.Y.Z, bun-vX.Y.Z, and latest. Bun does not support lts. |
runtime.node takes precedence over .nvmrc, then .node-version.
package.json > engines.node checks the chosen Node version. It does not select or install one.
Bun uses only runtime.bun. The CLI does not enforce engines.bun.
See lpm use for managed runtime installation.
Malformed JSON or non-string runtime values fail runtime detection, including during lpm install.
An absent selector retains normal fallback behavior. A leading UTF-8 BOM is accepted.
Unknown runtime names with string values are accepted but do not select another managed runtime.
When both runtimes are selected, script PATH starts with node_modules/.bin, managed Node, then managed Bun.
The inherited PATH follows these entries.
A Bun selector exposes bun to scripts. It does not change lpm run into bun run.
Workspace members and development services resolve selectors from their own directories. Each local selector replaces the same root selector. Other runtimes inherit from the root.
tools
{ "tools": { "oxlint": "1.57.0", "biome": "2.4.8", "rolldown": "1.1.3" } }| Key | Consumer |
|---|---|
oxlint | lpm lint. |
biome | lpm fmt. |
rolldown | lpm bundle. |
Other keys are ignored with a warning once per command.
For lpm check, pin typescript in devDependencies.
The --engine tsgo mode uses the version bundled with LPM CLI.
Missing Oxlint and Biome versions download with upstream checksum checks.
A Rolldown pin must match the bundled version or an approved lpm plugin update version.
Normal bundle runs do not check upstream.
For lint and format commands, workspace members inherit the root pin unless they provide their own. Invalid configuration stops the affected run. See Built-in tools.
https
{ "https": true }https is an optional boolean.
A true value enables local HTTPS for lpm dev.
Use lpm dev --no-https to disable it for one run.
LPM CLI serves HTTPS in front of the verified HTTP application endpoint.
It does not inject framework HTTPS variables or certificate paths into the application.
cert
{ "cert": { "extraPermittedDns": ["myapp.local"], "allowPublicDns": false } }| Field | Type | Default | Purpose |
|---|---|---|---|
extraPermittedDns | string[] | [] | Additional DNS subtrees allowed by the project certificate intermediate. |
allowPublicDns | boolean | false | Permit public DNS names in certificate constraints and local-domain host fields. |
Use bare names with multiple labels, such as myapp.local.
Each entry permits that name and its subdomains.
These entries do not add browser certificate names or proxy routes.
For browser hostnames, use --host, proxy.host, or services.<name>.host.
See Local HTTPS.
tunnel
{ "tunnel": { "domain": "acme-api.lpm.llc" } }domain is an optional string for an already claimed tunnel domain.
A configured domain automatically enables tunneling during lpm dev.
Use lpm dev --no-tunnel for one run without a tunnel.
The --domain flag overrides the configured domain.
Claim a stable domain with lpm tunnel claim.
Stable domains require Pro or Organization access. If you run lpm dev --tunnel with no domain, a Pro account uses <username>.lpm.fyi. Organization tunnels need one of the claimed domains of the organization.
proxy
{ "proxy": { "host": "app.localhost", "port": 443, "httpRedirect": true } }| Field | Type | Default | Purpose |
|---|---|---|---|
host | string | Unset | Friendly hostname for the primary development endpoint. |
port | integer | 443 | HTTPS listener port for lpm proxy start. 0 requests a free port. |
httpRedirect | boolean | true | Also listen on port 80 for HTTP-to-HTTPS redirects. |
Explicit proxy listener flags override the listener defaults.
Hostnames are normalized to lowercase and must be unique within the file.
Allowed local suffixes are .localhost, .test, .local, .internal, and .home.arpa.
Other suffixes require cert.allowPublicDns: true.
With local-domain configuration, lpm dev starts a detached proxy if none is running.
An HTTPS proxy listener is required for route registration.
The development session prepares project certificates through the normal trust consent flow.
Non-localhost names also receive managed hosts-file entries.
System hosts changes can require sudo on Unix or Administrator elevation on Windows.
Routes use the final assigned service ports. Startup output shows the final URLs after registration.
Normal exit removes routes and managed hosts entries.
After an interrupted session, use lpm hosts clean to remove orphaned entries.
The proxy can refresh certificate chains but does not install the root certificate into the trust store.
vault
{ "vault": "7f3a1e2c-5b9d-4a8f-b6c1-9b1d2e3f4a5b" }This string identifies the project for encrypted environments and cloud sync.
lpm env creates a UUID when the field is absent. Safe legacy slug identifiers remain supported.
Commit the identifier. It is not a secret.
LPM CLI rejects empty values, path separators, .., leading ~, control characters, colons, and absolute drive prefixes.
If you remove the field, the next environment command creates a new identifier.
The old cloud data remains under the previous identifier.
lpm publish leaves vault and vaultSync out of the published lpm.json.
LPM_VAULT_ID overrides this value only for lpm env pull --oidc.
A blank value falls back to the local field. Other environment commands ignore the variable.
See Environment project identity.
Env sync state
Cloud operations record this checkout's sync state in .lpm/env-sync.json, not in lpm.json.
Git never sees the file, because the project's .lpm/ directory ignores itself. See Files LPM CLI creates.
If Git tracks it anyway, for example because a commit added it with git add -f, or a repository of .lpm/'s own tracks it, it came with the clone rather than from this checkout, and this checkout's state would go into the next commit. Cloud operations then stop and name the git rm --cached command that untracks it. When Git can't tell whether it tracks the file, they warn and use it: .lpm/ ignores itself, so the file is rarely tracked.
Each checkout keeps its own state. A fresh clone starts without one, and its first sync records its own account binding.
{
"vault": "7f3a1e2c-5b9d-4a8f-b6c1-9b1d2e3f4a5b",
"sync": {
"personalVersion": 7,
"personalSyncedAt": "2026-08-13T09:00:00Z",
"orgVersions": { "acme": 12 },
"orgSyncedAt": { "acme": "2026-08-13T09:01:00Z" },
"personalPlatformBindings": {
"https://registry.lpm.dev": {
"registryUrl": "https://registry.lpm.dev",
"principalId": "user_123"
}
},
"authorityCheckpoints": {
"personal": {
"https://registry.lpm.dev": {
"user_123": { "version": 7, "syncedAt": "2026-08-13T09:00:00Z" }
}
},
"organizations": {
"acme": {
"https://registry.lpm.dev": {
"org_123": { "version": 12, "syncedAt": "2026-08-13T09:01:00Z" }
}
}
}
}
}
}vault names the env ID that the state belongs to. The state applies only while lpm.json > vault names the same ID.
Field under sync | Type | Notes |
|---|---|---|
personalVersion | int | Non-authoritative summary of the last known personal cloud-vault version. |
personalSyncedAt | string | Non-authoritative summary of the last successful personal sync time in RFC 3339 format. |
orgVersions.<slug> | int | Non-authoritative summary of the last known org-shared cloud-vault version per organization slug. |
orgSyncedAt.<slug> | string | Non-authoritative summary of the last successful sync time per organization in RFC 3339 format. |
personalPlatformBindings.<registry> | object | Pins platform credentials to the canonical Registry URL and authenticated user ID that created them. |
authorityCheckpoints.personal.<registry>.<principal> | object | Authoritative personal-vault revision floor and optional last-sync time for one Registry and immutable user ID. |
authorityCheckpoints.organizations.<slug>.<registry>.<principal> | object | Authoritative organization-vault revision floor and optional last-sync time for one Registry, organization slug, and immutable organization ID. |
LPM CLI maintains every field and writes the file owner-only.
authorityCheckpoints records account bindings and minimum accepted cloud revisions.
personalPlatformBindings binds platform credentials to their original account and Registry.
The other version and timestamp fields are non-authoritative summaries for status display.
Do not edit or remove the file. Removal resets local rollback and account-binding protection. A later operation can then establish a new principal and revision checkpoint for that checkout.
vaultSync from earlier releases
Earlier releases kept the same object in lpm.json > vaultSync, where it was committed with the project.
While Git's index doesn't hold it, LPM CLI still reads it until the next lpm env pull or lpm env push moves it to .lpm/env-sync.json and removes it from lpm.json.
A committed vaultSync reached every checkout, so it is no one checkout's state: LPM CLI ignores it, the checkout syncs as a fresh one, and the next pull or push removes it from lpm.json.
The check reads lpm.json as Git tracks it, under any spelling that the file system treats as the same file. When Git can't answer, as when it can't be run in a project inside a repository, LPM CLI ignores the field too.
lpm doctor warns while lpm.json holds vaultSync. Commit lpm.json after the move.
If a version does not match, the server returns 409 VaultConflictError. Then pull the remote values, resolve the conflict, and push again.
Commands use a project-local lock when they update lpm.json. Each command preserves unrelated fields and atomically replaces the file. A sync updates lpm.json and .lpm/env-sync.json together under that lock.
LPM CLI rejects a symbolic-link or junction lpm.json or .lpm/env-sync.json during an update. This rule prevents a cloned project from redirecting the write.
env
{ "env": { "dev": ".env.development", "staging": ".env.staging" } }Map script names to environment files.
For example, lpm run dev loads .env.development.
An explicit --env overrides tasks.<name>.env, which overrides this mapping.
The flag also applies to lpm dev.
Paths such as .env.unit derive the canonical environment name from the suffix, so script aliases can contain colons.
Custom paths such as config/unit.env use the alias itself as the canonical environment name.
That alias must contain only letters, digits, hyphens, underscores, or dots, with at most 64 bytes.
For a script named test:unit, use tasks["test:unit"].env: "unit" and env.unit: "config/unit.env".
env check reports invalid canonical names alongside the remaining environments.
Configured files must remain inside the project directory, including after symlink resolution. See Environment loading for file order, encrypted values, and inherited variables.
envSchema
{
"envSchema": {
"vars": {
"DATABASE_URL": { "required": true, "format": "url" },
"API_KEY": { "required": true, "secret": true },
"LOG_LEVEL": { "default": "info", "enum": ["debug", "info", "error"] }
}
}
}This schema checks variables before scripts, development services, executable files, and lpm exec child processes start.
| Variable field | Type | Default | Purpose |
|---|---|---|---|
required | boolean | false | Require a non-empty value. |
format | string | Unset | url, email, port, boolean, integer, hostname, or ip. |
pattern | string | Unset | Regex pattern. Use ^ and $ for a complete-value match. |
enum | string[] | Unset | Allowed values. |
default | string | Unset | Value for a missing variable. Must pass every declared check, even when another value is set. |
empty | string | "missing" | Treat an empty value as missing, allow it through validation, or reject it. |
secret | boolean | false | Redact the value in schema errors and logs. |
client | boolean | false | Allow browser exposure under a public prefix. |
ci | string | "secret" | Select GitHub Actions secret or variable storage, independently of client. |
description | string | Unset | Details for errors and generated .env.example files. |
min, max | integer or decimal string | Unset | Inclusive bounds for integer or port values. |
minLength, maxLength | unsigned integer or decimal string | Unset | Bounds on the Unicode scalar count. |
protocols | string[] | Unset | Allowed lowercase URL schemes, without a colon. Requires format: "url". |
requiredWhen | object | Unset | Require a nonempty value when another declared variable matches a predicate. |
Browser-visible names must use NEXT_PUBLIC_, VITE_, PUBLIC_, REACT_APP_, EXPO_PUBLIC_, GATSBY_, or NUXT_PUBLIC_.
Add project prefixes through envSchema.clientPrefixes, for example ["APP_"].
Custom prefixes must be unique portable names ending in _; at most 32 are supported.
Framework prefixes remain enforced. The REACT_APP_ prefix is case-insensitive, matching Create React App. Names with a public prefix must declare client: true.
A rule cannot combine secret: true with client: true or ci: "variable".
Use lpm env print --client-only --format json to print declared browser-visible values.
This command requires a non-empty schema and excludes undeclared and server values.
Cloud metadata preserves root exposure policy under envSchemaConfig alongside the variable rules.
Variable names contain 1–256 ASCII letters, numbers, or underscores. The first character must be a letter or underscore. Unknown fields, duplicate variable definitions, invalid names, invalid patterns, and invalid defaults stop the command before a child process starts. A schema supports at most 4096 variables. Schema text permits tabs and line breaks. It rejects other C0 controls, DEL, and C1 controls.
Cloud project metadata must also fit within 256 KiB of UTF-8 JSON. Compact rules fit at the count limit; long descriptions, patterns, or literal values can reach the byte limit earlier.
Secret rules cannot contain literal default, defaultsIn, or enum values. Store those values with lpm env set or in environment files.
An empty default cannot satisfy a required variable.
With empty: "missing", an explicit empty value triggers a default. Without a default, it still overrides an inherited parent value.
With empty: "allow", the empty value passes through the declared format, pattern, and enum checks. Required values must still be non-empty.
With empty: "reject", an empty value fails even when a default exists.
The url format requires an absolute URL with a host and valid syntax. It accepts credential-bearing database URLs.
The email format accepts an ASCII dot-atom mailbox and a dotted DNS hostname. Quoted and international mailboxes are unsupported.
NUL bytes are invalid in all project environment values.
Errors escape terminal control characters.
Use --no-env-check to skip value checks and defaults for one invocation. Invalid declarations always stop the command.
The flag does not make invalid environment inheritance valid.
Constraints and relationships
Numeric bounds cover the signed 64-bit range.
Local bounds accept integer numbers or decimal strings, such as "9007199254740993".
min cannot exceed max. Port bounds must permit at least one value from 1 through 65535.
Length constraints count Unicode scalars. For example, é counts as one scalar; e plus a combining accent counts as two.
minLength cannot exceed maxLength. Each length bound ranges from 0 through 4294967295.
URL schemes are case-insensitive in values. A protocols declaration contains 1–32 unique lowercase schemes.
{
"envSchema": {
"vars": {
"AUTH_MODE": { "default": "password", "enum": ["password", "oauth"] },
"PASSWORD": { "secret": true, "minLength": 12, "requiredWhen": { "variable": "AUTH_MODE", "equals": "password" } },
"OAUTH_TOKEN": { "secret": true },
"CALLBACK_URL": { "format": "url", "protocols": ["https"] },
"RETRY_COUNT": { "format": "integer", "min": "0", "max": "10", "default": "3" }
},
"groups": {
"credentials": { "mode": "exactlyOne", "vars": ["PASSWORD", "OAUTH_TOKEN"] }
}
}
}A condition declares exactly one predicate: equals: "value" or present: true|false.
Its variable must refer to a declared variable.
Presence means a nonempty effective value. present: false matches absent and empty values.
Equality with "" matches an explicit empty value; it does not match absence.
Conditions can inspect secret presence. They cannot compare a secret source with a plaintext equality literal.
LPM CLI injects every valid default before it evaluates conditions or groups.
Self-references and cycles use these final values; conditions do not recursively require other variables.
Unconditional and conditional requirements both require nonempty values, including with empty: "allow".
A group supports allOrNone, exactlyOne, or atLeastOne.
Group membership counts nonempty values, including defaults. Every member must be unique and declared.
A schema permits 128 named groups and 4096 total group members.
Group names follow the variable-name rules.
Group definitions round-trip through cloud metadata under envSchemaConfig.groups.
lpm env check reports distinct failed variables in its valid count.
Its JSON errors identify each failed group with separate group and mode fields.
Generated examples describe scalar constraints, conditions, and groups.
Environment, stage, and service scopes
Use requiredIn for context-specific requirements and defaultsIn for context-specific defaults.
{
"envSchema": {
"vars": {
"API_TOKEN": {
"secret": true,
"requiredIn": [{ "environment": ["production"], "stage": ["runtime"], "service": ["api"] }]
},
"PORT": {
"format": "port",
"default": "3000",
"defaultsIn": [{ "when": { "stage": ["test"] }, "value": "4000" }]
}
}
}
}Each selector contains one or more dimensions: environment, stage, or service.
All declared dimensions must match. Within one dimension, any listed name can match.
An omitted dimension matches every value. Service selectors require a service context.
Environment names use the resolved canonical identity, after aliases.
When services contains configured names, the CLI requires each service selector to name one of those services.
This applies to requiredIn, defaultsIn, and command service selections.
Without configured services, names can describe abstract service contexts.
This admission rule uses project configuration in the CLI.
The shared app engine and server WASM validate selector names and structure, but do not receive the configured service inventory.
Stages are development, build, runtime, ci, and test.
dev and development scripts use development; build, ci, and test scripts use their named stage.
Colon suffixes preserve the stage: build:api uses build.
Pre-hooks and post-hooks use the original script stage. Other scripts, executable files, and lpm exec use runtime.
Auto-detected runners for lpm test use test, including workspace members.
Development services use development and their configured service name. CI print and export use ci.
required: true, requiredWhen, and requiredIn add requirements independently.
A matching scoped default takes precedence over the global default.
Two scoped defaults cannot match the same context, even if their values are equal.
Every default must satisfy its scalar rules. Empty defaults cannot satisfy an active requirement.
Supplied values must satisfy their rules even when no scope matches.
Secret rules cannot contain defaultsIn literals.
Each rule permits 32 required selectors and 32 scoped defaults.
Each environment or service dimension permits 32 unique names; a stage dimension permits five unique stages.
Names use ASCII letters, digits, hyphens, underscores, and dots, with a 64-character limit.
Empty names, .., and __index__ are invalid.
The complete schema permits 4096 selectors and 16384 dimension entries.
Equivalent required selectors are duplicates, regardless of list order.
lpm env check --env=production --stage=runtime --service=api --json
lpm env print --env=development --stage=test --format=jsoncheck and print merge the selected service's static env values before validation.
Development services also validate generated ports and peer URLs before they start.
Script validation includes inherited declared values, explicit overrides, runner metadata, and the child process PATH.
The CLI validates the main script environment before a pre-hook starts, then validates each hook's final values.
Without a selection or configured default, files load from .env and .env.local.
Explicit --env=default also loads .env.default and .env.default.local.
A configured default mapping or declared default environment selects its own files.
Generated examples describe scopes and keep scoped values in comments; they do not choose a context-specific assignment.
Reusable fragments and presets
extends imports schema fragments from this project or built-in presets.
Fragment paths are relative to the document that imports them.
Each fragment uses the same fields as envSchema.
{
"envSchema": {
"extends": ["preset:node", "schemas/database.json"],
"vars": {
"NEXT_PUBLIC_API_URL": { "format": "url", "client": true }
},
"overrides": {
"DATABASE_URL": { "format": "url", "secret": true, "required": true }
}
}
}The imported schemas/database.json can contain:
{
"vars": {
"DATABASE_URL": { "format": "url", "secret": true }
}
}overrides replaces the complete inherited rule.
Keep every required security and validation field in the replacement.
groupOverrides replaces complete inherited groups.
An override must name an inherited declaration.
Use vars and groups for new local declarations.
Different sources cannot declare the same variable or group without an explicit override. A shared fragment imported through multiple paths contributes each declaration once. Root client prefixes combine with imported prefixes. Repeated prefixes within one document are invalid.
Presets are bundled with the CLI and work offline.
preset:node declares the optional NODE_ENV enum.
preset:nextjs also declares the optional NEXT_RUNTIME enum.
preset:vite inherits the Node preset.
Presets contain no credentials, required values, or defaults.
Imports must stay inside the project directory.
Absolute paths, parent escapes, symbolic links, nonregular files, and import cycles are invalid.
Import paths use forward slashes. Backslashes, control characters, and : < > " | ? * are invalid.
Each path component must avoid trailing dots, trailing spaces, and Windows reserved names such as CON or NUL.
Reserved names are case-insensitive and remain invalid with extensions, including con.json and Nul.txt.
Different import paths that identify the same file, including hard links, fail with env.source_alias.
Dependencies under a symbolic-link node_modules directory cannot serve as fragments.
Copy the fragment into the project or use a dependency layout with regular directories.
Limits include 32 imports per document, 64 documents, 256 edges, and 16 import levels.
Each fragment and the effective schema permit 2 MiB.
The complete source graph permits 8 MiB of unique fragment content and 65,536 merge visits.
Existing variable, group, scope, and pattern limits still apply.
lpm env schema
lpm env schema --jsonschema checks declarations and imports without loading environment values.
JSON results include stable diagnostic codes, source pointers, declaration origins, and a content fingerprint.
Diagnostics omit value literals.
Imported files participate in task cache keys and watch reloads, including files outside configured task inputs.
Publishing and cloud sync use the resolved schema.
The package archive contains a flattened envSchema, even when package filters exclude its fragments.
Author files stay unchanged.
The native app uses the same Rust engine and rejects source changes during an edit.
Description edits update the authored file that declares the effective rule, including a local fragment.
These edits preserve unrelated bytes, indentation, and any UTF-8 BOM.
Preset declarations and installed fragments under node_modules are read-only in the app.
Add a root overrides entry to edit their descriptions.
The app cannot rename inherited keys or keys referenced by another fragment.
Edit those declarations in their source files first.
Typed runtime generation
lpm env generate creates typed JavaScript modules from the resolved schema.
The server module contains every declared rule. The client module contains only browser-visible rules.
Both modules expose createEnv(input) and getEnv().
They validate raw strings before conversion and use the selected environment, stage, and service context.
Integer output uses bigint, port output uses number, and boolean output uses boolean.
Other output uses strings. Optional values can be undefined.
Validation errors contain stable codes without input values.
See runtime generation for adapters, startup calls, and freshness checks.
environments
{
"environments": {
"base": ".env",
"staging": { "extends": "base", "file": ".env.staging" },
"preview": { "extends": "staging" },
"empty": {}
}
}Define reusable file chains for named environments.
A string is a file path. An object accepts optional extends and file strings.
An empty object is valid and adds no file.
LPM CLI loads the base first and the selected environment last.
Later files replace earlier values. An inherited environment without file adds no extra file.
Missing parent names, cycles, and unsafe file paths fail before the command starts.
For an undeclared environment name, normal dotenv loading or an explicit file mapping still applies.
For example, --env preview can load .env.preview without an environments.preview entry.
lpm run deploy --env stagingtasks.<name>
{
"tasks": {
"build": {
"command": "tsup",
"dependsOn": ["^build"],
"cache": true,
"outputs": ["dist/**"],
"inputs": ["src/**", "package.json"],
"env": "production"
}
}
}| Field | Type | Default | Purpose |
|---|---|---|---|
command | string | package.json > scripts.<name> | Command body. |
dependsOn | string[] | [] | Same-project tasks, or ^task for upstream workspace tasks. |
cache | boolean | false | Enable caching. Requires output patterns. |
outputs | string[] | [] | Files produced by the task. |
inputs | string[] | Project defaults | Files that affect cached results. An empty list uses the defaults. |
env | string | Unset | Environment name for this task. |
cacheEnv | string[] | All inherited variables | Exact inherited variable names included in the cache key. [] selects none. |
Default inputs:
[
"src/**", "lib/**", "app/**", "pages/**", "components/**", "scripts/**", "bin/**",
"*.js", "*.cjs", "*.mjs", "*.ts", "*.tsx", "*.jsx",
"package.json", "lpm.json", "tsconfig.json", "tsconfig.*.json",
"*.config.js", "*.config.ts", "*.config.mjs"
]Patterns must be project-relative and cannot contain parent traversal.
* matches one path component. ** spans directories.
Braces and a leading ! are literal characters. Use separate patterns for alternatives.
Input patterns also control task watch mode.
Cache keys include manifests, supported lockfiles, dependency task identities, and executable identity.
Workspace members also include root manifests and workspace configuration.
Project-loaded environment values always remain cache inputs, regardless of cacheEnv.
Include every inherited variable that affects the result.
A cache restore accepts only files matched by the current output patterns. It removes declared output files absent from the cached result. Keep output patterns limited to generated files. See Task runner for dependency ordering, cache keys, and workspace execution.
remoteCache
{
"remoteCache": {
"enabled": true,
"team": "acme",
"signature": true,
"readOnly": false,
"env": { "exclude": ["*_TOKEN", "*_SECRET", "DATABASE_URL"] }
}
}Hosted caching applies to tasks with cache: true and declared outputs.
LPM CLI checks the local cache, then the hosted cache, then runs the task.
Successful runs can upload their declared outputs.
| Field | Type | Default | Purpose |
|---|---|---|---|
enabled | boolean | false | Enable hosted cache use. |
team | string | Token namespace | Organization slug. Requires a token scoped to that organization. |
url | string | Configured Registry plus /v8 | Hosted cache endpoint. |
signature | boolean | false | Require artifact signatures with LPM_REMOTE_CACHE_SIGNATURE_KEY. |
readOnly | boolean | false | Download artifacts without uploads. |
env.include | string[] | [] | Variable-name patterns permitted for uploads. |
env.exclude | string[] | [] | Variable-name patterns that prevent uploads. |
env.allowSecrets | boolean | false | Explicit project permission for secret-looking variable names. |
LPM_REMOTE_CACHE overrides enabled, including an explicit false value.
Nonblank LPM_REMOTE_CACHE_URL and LPM_REMOTE_CACHE_TEAM override their matching fields.
Read-only mode applies if either readOnly or LPM_REMOTE_CACHE_READ_ONLY is true.
An environment false value cannot disable a project read-only requirement.
Endpoints outside the configured Registry origin require LPM_REMOTE_CACHE_TOKEN and LPM_REMOTE_CACHE_SIGNATURE_KEY.
Invalid artifact signatures produce cache misses.
Secret-looking environment names prevent uploads by default. Local caching remains available.
See Remote task cache for setup and the full upload policy.
services.<name>
{
"services": {
"db": { "command": "docker compose up postgres", "readyPort": 5432, "readyTimeout": 60 },
"api": { "command": "node server.js", "port": 4000, "dependsOn": ["db"] },
"web": { "command": "next dev", "port": 3000, "primary": true, "host": "web.app.localhost" }
}
}Start these processes with lpm dev.
The file supports at most 256 services.
Names contain 1–64 ASCII letters, numbers, underscores, or hyphens, and start with a letter.
Names must also produce distinct peer environment prefixes.
| Field | Type | Default | Purpose |
|---|---|---|---|
command | string | Required | Nonblank shell command. |
port | integer | Unset | Preferred service port, from 1 to 65535. |
dependsOn | string[] | [] | Services that must become ready first. |
readyPort | integer | port | TCP readiness port, from 1 to 65535. |
readyUrl | string | Unset | Loopback HTTP readiness URL. The first 2xx response succeeds. Redirects are not followed. |
readyTimeout | integer | 30 | Readiness deadline, from 1 to 3600 seconds. |
env | object of strings | {} | Environment values for this service. |
restart | boolean | false | Restart after a nonzero exit. |
primary | boolean | false | Use this endpoint for HTTPS, tunneling, network access, and browser opening. |
host | string | Unset | Local hostname routed through an HTTPS proxy. |
cwd | string | Project root | Working directory relative to the project root. |
Host-routed services and explicit primaries can receive automatic ports.
A recognized sole development server can also receive a port.
Generic workers remain portless unless primary endpoint features require a port.
LPM CLI injects assigned ports through PORT.
Readiness URLs must use localhost, 127.0.0.1, or [::1].
The readiness deadline covers listener ownership and application readiness.
Missing dependencies, duplicate names, and dependency cycles stop startup.
Restart delays grow from 1 to 30 seconds, with at most 10 attempts.
More than 60 seconds continuously ready resets this budget.
See lpm dev for endpoint checks and service recipes.
publish
{
"publish": {
"registries": ["npm"],
"npm": { "name": "@acme/widgets", "access": "public", "tag": "latest" }
}
}Use lpm publish to publish to npm, LPM.dev Registry, GitHub Packages, GitLab Packages, or a custom registry.
The CLI is registry-agnostic.
Top-level
registries is an array of lpm, npm, github, gitlab, or HTTPS registry URLs.
An absent or empty array falls back to LPM.dev Registry.
Explicit target flags replace the configured selection: --lpm, --npm, --github, --gitlab, and --publish-registry.
Loopback HTTP URLs are supported for local registries.
publish.lpm
| Field | Type | Purpose |
|---|---|---|
name | string | Package name override in @lpm.dev/owner.pkg format. |
publish.npm
| Field | Type | Default | Purpose |
|---|---|---|---|
name | string | Manifest name | npm package name. Required as an override for an @lpm.dev/* manifest name. |
access | string | public | public or restricted. npm provenance requires public access. |
tag | string | latest | Distribution tag. Prereleases require an explicit tag. |
registry | string | https://registry.npmjs.org | npm-compatible endpoint. |
otpRequired | boolean | false | Request an OTP before the first publish attempt. |
GitHub and GitLab names fall back to publish.npm.name, then the manifest name.
Their access values fall back to publish.npm.access, then public.
Custom npm-compatible targets use the npm name and access defaults.
All npm-compatible targets use publish.npm.tag.
For prereleases, set an explicit tag such as next.
Custom endpoints require an exact registry token saved by lpm login.
NPM_TOKEN and npm login credentials apply only to the default npm registry.
publish.github
| Field | Type | Purpose |
|---|---|---|
name | string | Scoped package name, such as @owner/pkg. |
access | string | public or restricted. |
publish.gitlab
| Field | Type | Default | Purpose |
|---|---|---|---|
name | string | npm name, then manifest name | GitLab package name. |
access | string | npm access, then public | public or restricted. |
projectId | string | Required | GitLab project identifier. |
registry | string | https://gitlab.com | GitLab instance URL. |
Precedence with other config
Precedence differs by feature:
| Workflow | Selection order |
|---|---|
| Node runtime | runtime.node, .nvmrc, .node-version, system PATH. |
| Task command | tasks.<name>.command, then package.json > scripts.<name>. |
| Task environment | --env, tasks.<name>.env, env.<script>, normal dotenv loading. |
| Development HTTPS | --no-https disables it; otherwise --https or https: true enables it. |
| Development tunnel | --no-tunnel disables it; otherwise a domain or --tunnel enables it. |
| Publish targets | Explicit target flags, configured nonempty target list, LPM.dev Registry. |
| Hosted cache | Per-field environment rules in remoteCache. |
Do not assume that every field inherits from a workspace root or user configuration.
Validation and recovery
Configuration files have a 16 MiB size limit.
Malformed JSON and invalid typed fields fail the command that reads them.
Ordinary unknown fields are ignored for forward compatibility.
lpm doctor reports unknown nested paths, such as proxy.httpRediect.
CLI-managed env sync state, including a legacy vaultSync, uses stricter field checks.
If a command reports an invalid field, correct that field and run the command again. For inheritance errors, check parent names and remove cycles. For service errors, check names, port bounds, readiness timeouts, and dependency names. For editor checks that differ from your CLI, emit the schema from that installed version.