Skip to main content
Ship is a pnpm + Turborepo monorepo, so code shared between apps/api and apps/web lives in one place: the packages/ folder. Define a constant, a UI library, or a config once, depend on it as workspace:*, and every app stays in sync.

What’s in packages/

A scaffolded project ships these workspace packages:
There is no shared package. The typed API client is not a package — it lives in the API workspace and reaches the web app through TypeScript declarations. See Typed client, no shared package below.

Using a package

Workspace packages are referenced with the workspace:* protocol. Add one to the dependencies of the app that needs it:
apps/api/package.json
Then import it like any module:
Run pnpm install once to link the workspace, and the import resolves to your local source — no publish, no build step for the consumer.

Typed client, no shared package

End-to-end type safety does not flow through a shared package. It flows through the API workspace itself:
  1. Endpoints declare their shape with .input(zodSchema).output(zodSchema).
  2. pnpm --filter api build:types emits .d.ts files for the API package.
  3. The web app depends on "api": "workspace:*" and imports the typed oRPC client:
apps/web — typed client
Inside the API, codegen keeps the oRPC router, contract, and DbService in lockstep with the filesystem. After adding or removing an endpoint or schema file, run:
That regenerates src/router.ts, src/contract.ts, and src/db.ts from the resources on disk — the types an agent (or you) reads are always real. See How Ship works for the resource model and Schemas for the schema layer.

Adding your own package

Create a folder under packages/, give it a package.json with a workspace:*-friendly name, and add it to whichever app depends on it.
1

Scaffold the package

packages/billing/package.json
2

Depend on it

apps/api/package.json
3

Link the workspace

Read more about internal packages in the Turborepo documentation.