# Project setup (/docs/project-setup)





LPM CLI splits project configuration across three files. Each owns a different kind of setting, by design:

| File                           | What it owns                                                                                                                                                          | Commit?     |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| [`package.json`](#packagejson) | Standard npm fields (dependencies, scripts, engines) **plus** an `"lpm"` block for project-shared LPM CLI behavior (script policy, trust, linker, sandbox, overrides) | Yes         |
| [`lpm.json`](#lpmjson)         | Dev-server, task-runner, env-file mapping, env schema, services, publish targets — runtime infrastructure that isn't publishable metadata                             | Yes         |
| [`lpm.toml`](#lpmtoml)         | Per-project CLI defaults and reviewable local policy exceptions — tool behavior, not publishable                                                                      | Team's call |

The split is deliberate: anything that ships to consumers of your package lives in `package.json`. Anything that's about running the project locally lives in `lpm.json`. Anything that's about which way you personally (or the team) prefer the CLI to behave lives in `lpm.toml`.

## `package.json` [#packagejson]

Most LPM CLI behavior keys live under the `"lpm"` block — committed alongside dependencies so every contributor picks them up:

```json title="package.json"
{
  "name": "my-app",
  "version": "1.0.0",
  "engines": { "node": ">=22.0.0", "lpm": ">=0.40.0" },
  "workspaces": ["packages/*"],

  "dependencies": { "react": "^19.0.0" },

  "lpm": {
    "linker": "isolated",
    "scriptPolicy": "deny",
    "trustedDependencies": ["esbuild", "sharp"],
    "scripts": {
      "autoBuild": false,
      "sandboxWriteDirs": ["build/"]
    }
  }
}
```

Full field reference: [`package.json` "lpm" key](/docs/reference/package-json-lpm).

## `lpm.json` [#lpmjson]

Optional. Sits next to `package.json` and configures runtime / dev infrastructure:

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

  "env": {
    "dev":  ".env.development",
    "prod": ".env.production"
  },
  "envSchema": {
    "vars": {
      "DATABASE_URL": { "required": true, "format": "url" }
    }
  },

  "tasks": {
    "build": {
      "command": "tsup",
      "dependsOn": ["^build"],
      "cache": true,
      "outputs": ["dist/**"]
    }
  },

  "services": {
    "db":  { "command": "docker compose up postgres", "readyPort": 5432 },
    "web": { "command": "next dev", "port": 3000, "primary": true }
  },

  "publish": {
    "registries": ["lpm", "npm"]
  }
}
```

Full field reference: [`lpm.json`](/docs/reference/lpm-json).

The `$schema` line is optional but enables inline validation and field completion in any JSON-schema-aware editor.

## `lpm.toml` [#lpmtoml]

Optional. Sits next to `package.json` and pins per-project CLI defaults. It owns the [save policy](/docs/packages/save-policy), workspace/test defaults, tidy ignores, sandbox defaults, and reviewable local policy exceptions:

```toml title="lpm.toml"
save-prefix = "~"     # team prefers tilde over caret
save-exact  = false

[[policy.typosquat.allow]]
package = "crossenv"
similar-to = "cross-env"
reason = "Internal migration package kept for compatibility"
```

Commit it for team-shared CLI defaults; `.gitignore` it if save policy should stay per-developer. The keys here override `~/.lpm/config.toml` (user-level) but lose to CLI flags.

Full field reference: [`lpm.toml`](/docs/reference/lpm-toml).

## Local configuration size limits [#local-configuration-size-limits]

LPM CLI bounds local configuration before UTF-8 decoding or parsing. The limit applies to the bytes read from the opened file, including the target of a supported symlink.

| Input                                                                                                                                                                                                                                                          | Limit                         |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| JSON, TOML, YAML, and dotenv configuration or manifests — including `package.json`, `lpm.toml`, `~/.lpm/config.toml`, `lpm.json`, `lpm.config.json`, `pnpm-workspace.yaml`, `.env*`, security/trust policy, and CLI-managed MCP, proxy, or Swift registry JSON | 16 MiB per file               |
| `.npmrc`                                                                                                                                                                                                                                                       | 1 MiB per configuration layer |
| File-referenced CA bundles, client certificates, and private keys                                                                                                                                                                                              | 1 MiB per file                |

A missing optional file keeps its normal default or fallback behavior. A file that exists but exceeds its limit is not treated as missing: LPM CLI returns an error naming the path and byte limit. Install-time configuration fails before dependency resolution or registry access, and run-time configuration fails before a script or child process is spawned. This prevents an oversized higher-precedence file from silently changing registry, source, or security-policy selection.

These small-file limits do not apply to `lpm.lock` / `lpm.lockb`, package tarballs and downloaded archives, package source or build outputs, patch and skill source payloads, README content, growing task/audit/webhook logs, or operating-system pseudo-files such as `/proc`. Those inputs retain their own streaming, integrity, rotation, or format-specific controls.

## Which file owns what [#which-file-owns-what]

Quick decision table when you're not sure where a setting belongs:

| You want to…                                                  | Goes in                                                             |
| ------------------------------------------------------------- | ------------------------------------------------------------------- |
| Pin a runtime dep with a version range                        | `package.json > dependencies`                                       |
| Pin Node version for the project                              | `lpm.json > runtime.node` (or `package.json > engines.node`)        |
| Make Bun available to scripts                                 | `lpm.json > runtime.bun`                                            |
| Block dependency lifecycle scripts for everyone on the team   | `package.json > lpm.scriptPolicy = "deny"`                          |
| Approve a specific package to run scripts                     | `package.json > lpm.trustedDependencies` (or `lpm approve-scripts`) |
| Define a custom build task with caching                       | `lpm.json > tasks.<name>`                                           |
| Map `lpm run dev` to a specific `.env` file                   | `lpm.json > env.dev`                                                |
| Configure dev services with readiness checks                  | `lpm.json > services`                                               |
| Set a save-prefix that overrides every developer's preference | `lpm.toml > save-prefix`                                            |
| Allow an intentional suspicious package name                  | `lpm.toml > policy.typosquat.allow`                                 |
| Set your personal default save prefix machine-wide            | `~/.lpm/config.toml > save-prefix`                                  |
| Publish to multiple registries from one `lpm publish`         | `lpm.json > publish.registries`                                     |

## `.gitignore` essentials [#gitignore-essentials]

`lpm init` creates a `.gitattributes` entry for the binary lockfile automatically:

```text title=".gitattributes"
lpm.lockb binary
```

You probably want to ignore a few LPM CLI artifacts:

```text title=".gitignore"
node_modules/
.lpm/                  # local install hash, security caches, skills, tunnel state
.env*.local            # local-only env overrides
```

`.lpm/` holds project-local install state (`install-hash`, captured tunnel webhooks, agent skill markdown, etc.). The global store lives in `~/.lpm/`, completely separate — nothing project-specific belongs there.

## See also [#see-also]

* [`package.json` "lpm" key](/docs/reference/package-json-lpm) — every field under the `"lpm"` block
* [`lpm.json`](/docs/reference/lpm-json) — runtime / task / publish config
* [`lpm.toml`](/docs/reference/lpm-toml) — project-level CLI defaults
* [`~/.lpm/config.toml`](/docs/reference/config-toml) — user-level CLI defaults
* [Save policy](/docs/packages/save-policy) — full precedence chain across these files
