# How External Libraries Are Managed in Kaneo: A Complete Guide to PNPM Workspaces and Monorepo Dependency Control

> Discover how Kaneo manages external libraries with PNPM workspaces in its monorepo. Learn about tiered dependency control for root tooling, app runtime needs, and shared internal packages.

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: how-to-guide
- Published: 2026-08-10

---

**Kaneo uses a PNPM monorepo with workspace references to manage external libraries across three tiers—root tooling, app-specific runtime dependencies, and internal shared packages linked via `workspace:*` specifiers.**

This guide breaks down exactly how the `usekaneo/kaneo` repository structures its dependencies, from global dev tooling to per-app library installation and automated version updates.

## PNPM Workspace Architecture

Kaneo's dependency management rests on a **PNPM workspace** defined in [`pnpm-workspace.yaml`](https://github.com/usekaneo/kaneo/blob/main/pnpm-workspace.yaml). This configuration partitions the repository into three distinct layers:

| Layer | Location | Purpose |
|-------|----------|---------|
| **Root** | [`package.json`](https://github.com/usekaneo/kaneo/blob/main/package.json) | Shared dev-dependencies, scripts, and workspace definition |
| **Apps** | [`apps/api/package.json`](https://github.com/usekaneo/kaneo/blob/main/apps/api/package.json), [`apps/web/package.json`](https://github.com/usekaneo/kaneo/blob/main/apps/web/package.json) | Runtime dependencies for deployable services |
| **Packages** | `packages/*/package.json` | Reusable internal libraries consumed by apps |

The workspace file at [`pnpm-workspace.yaml`](https://github.com/usekaneo/kaneo/blob/main/pnpm-workspace.yaml) declares which directories belong to the monorepo:

- `apps/*` — contains the API server and web client
- `packages/*` — houses shared libraries like `@kaneo/email`, `@kaneo/libs`, and `@kaneo/permissions`

## Root Package.json: Global Tooling Control

The root [`package.json`](https://github.com/usekaneo/kaneo/blob/main/package.json) centralizes **development dependencies** that every workspace needs. Rather than repeating Biome, Turborepo, TypeScript, or Husky across multiple files, they're declared once at the top level.

Key characteristics of the root configuration:

- **Package manager lock**: PNPM 10.32.1 enforced via `"packageManager": "pnpm@10.32.1"`
- **Single runtime dependency**: `dotenv-mono` for environment loading
- **Shared scripts**: Build orchestration and quality checks through Turborepo pipelines

This design avoids version drift in tooling while keeping per-package [`package.json`](https://github.com/usekaneo/kaneo/blob/main/package.json) files focused on production requirements.

## Per-App Dependencies: API and Web

Each application maintains its own dependency manifest scoped to its runtime needs.

### API Application ([`apps/api/package.json`](https://github.com/usekaneo/kaneo/blob/main/apps/api/package.json))

```json
{
  "dependencies": {
    "@hono/node-server": "^2.1.0",
    "hono": "^4.13.0",
    "drizzle-orm": "^0.45.2",
    "valibot": "^1.4.2"
  }
}

```

The API layer relies on **Hono** for HTTP handling, **Drizzle ORM** for database operations, and **Valibot** for schema validation.

### Web Application ([`apps/web/package.json`](https://github.com/usekaneo/kaneo/blob/main/apps/web/package.json))

```json
{
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "@tanstack/react-query": "^5.0.0",
    "tailwindcss": "^4.0.0"
  }
}

```

The web client uses **React 19**, **TanStack Query** for server state, and **Tailwind CSS v4** for styling.

## Internal Package Linking with Workspace References

Kaneo avoids publishing internal utilities to npm by using **workspace references**. When the API needs the email package, it declares:

```json
{
  "dependencies": {
    "@kaneo/email": "workspace:*"
  }
}

```

The `workspace:*` specifier instructs PNPM to create a symlink to the local package at `packages/email/` rather than fetching from a registry. This enables:

- Immediate reflection of changes across consuming apps
- No version bumping required during development
- Atomic releases when packages do need publishing

Internal libraries follow the same pattern—`@kaneo/libs`, `@kaneo/permissions`, and `@kaneo/mcp` all connect via workspace links.

## Installing External Libraries: Scoped Commands

Adding external libraries requires PNPN's filter syntax to target the correct workspace.

**Add to a specific app:**

```bash
pnpm add axios -F @kaneo/api

```

This updates [`apps/api/package.json`](https://github.com/usekaneo/kaneo/blob/main/apps/api/package.json) and [`pnpm-lock.yaml`](https://github.com/usekaneo/kaneo/blob/main/pnpm-lock.yaml) in one operation.

**Add to root (all workspaces):**

```bash
pnpm add dayjs -w

```

Root additions become available everywhere through PNPM's hoisting behavior.

**Example usage after installation:**

```typescript
// apps/api/src/health/controllers/get-status.ts
import axios from "axios";

export async function getStatus() {
  const { data } = await axios.get("https://example.com/health");
  return { ok: true, upstream: data };
}

```

```typescript
// apps/web/src/hooks/use-current-time.ts
import dayjs from "dayjs";

export function useCurrentTime() {
  return dayjs().format("YYYY-MM-DD HH:mm:ss");
}

```

## Automated Dependency Maintenance

Kaneo uses **Dependabot** (configured in [`.github/dependabot.yml`](https://github.com/usekaneo/kaneo/blob/main/.github/dependabot.yml)) to propose version bumps for each [`package.json`](https://github.com/usekaneo/kaneo/blob/main/package.json) in the monorepo. The bot opens individual pull requests per dependency, allowing targeted review and rollback.

For packages that do publish to npm—like `@kaneo/mcp`—the repository includes a dedicated workflow at [`.github/workflows/publish-mcp.yml`](https://github.com/usekaneo/kaneo/blob/main/.github/workflows/publish-mcp.yml) that handles versioning and registry publication through GitHub Actions.

## Lockfile Consistency and Reproducibility

All workspaces share a **single [`pnpm-lock.yaml`](https://github.com/usekaneo/kaneo/blob/main/pnpm-lock.yaml)** at the repository root. After any dependency change, running `pnpm install` recomputes the lockfile to ensure:

- Identical resolution across all developer machines
- Deterministic builds in CI/CD pipelines
- Deduplicated dependency trees through PNPM's content-addressable store

The lockfile captures exact versions, tarball URLs, and peer dependency resolutions for the entire monorepo.

## Summary

- **PNPM workspaces** in [`pnpm-workspace.yaml`](https://github.com/usekaneo/kaneo/blob/main/pnpm-workspace.yaml) define the monorepo boundary and enable cross-package linking
- **Root [`package.json`](https://github.com/usekaneo/kaneo/blob/main/package.json)** consolidates dev-tooling to prevent version fragmentation
- **`workspace:*` specifiers** connect internal packages without registry publication
- **Scoped `pnpm add -F`** commands install external libraries to specific apps
- **Dependabot and CI workflows** automate version updates and package publishing
- **Single lockfile** guarantees reproducible builds across all environments

## Frequently Asked Questions

### What package manager does Kaneo use and why?

Kaneo uses **PNPM 10.32.1** exclusively. According to the root [`package.json`](https://github.com/usekaneo/kaneo/blob/main/package.json), the `"packageManager"` field enforces this version to ensure consistent behavior across developer machines and CI environments. PNPM's workspace features and disk-efficient content-addressable store make it well-suited for monorepos with multiple interdependent packages.

### How do I add a library that's shared across all apps in Kaneo?

Use the `-w` (workspace) flag: `pnpm add <package> -w`. This installs to the root [`package.json`](https://github.com/usekaneo/kaneo/blob/main/package.json) and makes the library available through PNPM's hoisting mechanism. However, for runtime dependencies, prefer installing directly in each app to keep dependency graphs explicit and bundler-friendly.

### What happens when I change code in `packages/email`?

Changes reflect immediately in consuming apps like `apps/api` because `@kaneo/email` is linked via `workspace:*`. No `pnpm install` or version bump is required during development. The symlink points directly to the source, so builds and dev servers pick up modifications in real time.