# Workspaces (/docs/workspaces)



snpm has first-class workspace support: a single lockfile at the workspace root, fan-out commands, dependency-graph aware selectors, and shared catalogs.

Discovery [#discovery]

snpm discovers a workspace by walking up from the current directory and looking for, in order:

1. `snpm-workspace.yaml`
2. `pnpm-workspace.yaml`
3. `package.json` with a `workspaces` field (string array or `{ packages: [...] }` form)

Whichever is found first wins, and its parent directory becomes the workspace root.

snpm-workspace.yaml [#snpm-workspaceyaml]

```yaml title="snpm-workspace.yaml"
packages:
  - "packages/*"
  - "apps/*"

# Optional shared catalogs
catalog:
  react: ^18.2.0
  typescript: ^5.4.0

catalogs:
  build:
    vite: ^5.0.0
  testing:
    vitest: ^1.0.0

# Optional: hoisting override for the workspace
hoisting: single-version

# Optional: dependency script allow/deny lists
onlyBuiltDependencies:
  - esbuild
  - sharp
ignoredBuiltDependencies:
  - fsevents

# Optional: packages that must stay in the project-local virtual store
disableGlobalVirtualStoreForPackages:
  - next
  - vite
```

`pnpm-workspace.yaml` is parsed with the same shape, plus `catalog` / `catalogs` aliases.

Example layout [#example-layout]

```
my-monorepo/
├── snpm-workspace.yaml
├── package.json
├── snpm-lock.yaml           ← one lockfile at the root
├── packages/
│   ├── ui/package.json
│   └── utils/package.json
└── apps/
    └── web/package.json
```

workspace: protocol [#workspace-protocol]

Local packages can reference each other with `workspace:`:

```json title="apps/web/package.json"
{
  "dependencies": {
    "@acme/ui": "workspace:*",
    "@acme/utils": "workspace:^"
  }
}
```

| Version           | Meaning                                            |
| ----------------- | -------------------------------------------------- |
| `workspace:*`     | Any version of the local package                   |
| `workspace:^`     | Caret range of the local package's current version |
| `workspace:~`     | Tilde range of the local package's current version |
| `workspace:1.2.3` | Specific local version                             |

On publish, these specifiers are rewritten to concrete versions.

Common commands [#common-commands]

```bash
# install everything from the workspace root
snpm install

# install for a single workspace project
snpm install -w @acme/api

# run a script in every workspace project
snpm run build -r

# run a script in a subset (see selector syntax below)
snpm run test --filter "@acme/*"

# add/remove across workspaces
snpm add -r -D vitest
snpm remove -r lodash

# inspect across workspaces
snpm outdated -r
snpm why typescript -r
snpm list -r
```

Every workspace-aware command supports the same selectors: `add`, `remove`, `run`, `exec`, `upgrade`, `outdated`, `list`, `why`, and `publish`.

Selector syntax [#selector-syntax]

`--filter` and `--filter-prod` accept a dependency-graph-aware selector grammar. Multiple `--filter` values combine — positive selectors union, negative selectors subtract.

| Pattern                                 | Meaning                                                                                                   |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `pkg-name`                              | Exact project name                                                                                        |
| `@scope/*`, `foo-?`                     | Glob over project names                                                                                   |
| `./packages/api`, `../lib`, `/abs/path` | Match by project path (relative to the workspace root, or absolute)                                       |
| `pkg...`                                | The package and everything it transitively depends on                                                     |
| `...pkg`                                | The package and everything that transitively depends on it                                                |
| `^pkg...` / `pkg^...`                   | Same as above, but exclude the seed itself                                                                |
| `[<git-ref>]`                           | Projects with files changed since `<git-ref>` (runs `git diff --name-only <ref>` from the workspace root) |
| `!pkg`                                  | Exclude (must be combined with at least one positive selector)                                            |

`--filter-prod` uses the same grammar but walks only `dependencies` / `optionalDependencies` for graph operators, so it ignores devDependency edges.

**Examples**

```bash
# every project under packages/api
snpm run test --filter ./packages/api

# api plus its dependencies
snpm run build --filter api...

# everything that depends on the shared utils package
snpm run test --filter ...@acme/utils

# everything affected since the last main commit
snpm run test --filter "[origin/main]"

# everything in the @acme scope except docs
snpm run lint --filter "@acme/*" --filter "!@acme/docs"
```

Catalogs [#catalogs]

Define dependency versions once at the workspace root and reference them with the `catalog:` protocol:

```yaml title="snpm-catalog.yaml"
catalog:
  react: ^18.2.0
  typescript: ^5.4.0

catalogs:
  build:
    vite: ^5.0.0
  testing:
    vitest: ^1.0.0
```

```json title="apps/web/package.json"
{
  "dependencies": {
    "react": "catalog:"
  },
  "devDependencies": {
    "vite": "catalog:build",
    "vitest": "catalog:testing"
  }
}
```

When `snpm-workspace.yaml` and `snpm-catalog.yaml` both define a key, the workspace file wins. See [Catalog](/docs/catalog) for the full reference.

Overrides [#overrides]

Pin transitive dependency versions for the whole workspace via either `snpm-overrides.yaml` or `package.json`:

```yaml title="snpm-overrides.yaml"
overrides:
  lodash: ^4.17.21
  "@babel/core": ^7.23.0
```

```json title="package.json"
{
  "snpm": {
    "overrides": { "lodash": "^4.17.21" }
  },
  "pnpm": {
    "overrides": { "axios": "^1.6.0" }
  }
}
```

Both `snpm.overrides` and `pnpm.overrides` are honored.

Per-project install [#per-project-install]

You can also `cd` into a workspace project and run `snpm install` from there. snpm will still discover the workspace root and write a single lockfile at the top.

Best practices [#best-practices]

* **Scope your packages** (`@acme/ui`, `@acme/utils`) so selectors and link names stay tidy.
* **Use catalogs** for shared deps to eliminate version drift across packages.
* **Reach for `[git-ref]` selectors** in CI to only build what changed: `snpm run test --filter "[origin/main]"`.
* **Pin the snpm version** in the workspace root `package.json`:

  ```json
  { "packageManager": "snpm@2026.5.16" }
  ```

  Combined with `snpm-switch`, every contributor and CI job will run the same `snpm` build.
