> ## Documentation Index
> Fetch the complete documentation index at: https://ship.paralect.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Overview

> Everything is a plugin. Every plugin is your code. Features merge into your repo like shadcn/ui — yours to edit, extend, or delete.

A plugin is a feature delivered as **source you own**. Auth, the admin panel, file storage, email, AI chat — none of them are framework internals. Each is a folder of resources, routes, and packages that **merges into your codebase** when you install it. From that point on, the files are yours to edit, extend, or delete.

> **Everything is a plugin. Every plugin is your code.** Like shadcn/ui, but for your entire stack.

## How merging works

Installing a plugin doesn't add a dependency you import from `node_modules` — it copies files into the three places your code already lives:

* `apps/api/src/resources/<name>/` — endpoints, methods, handlers, middlewares, schemas
* `apps/web/src/routes/<name>/` — [TanStack Router](https://tanstack.com/router) routes, auto-discovered by file path
* `packages/<pkg>/` — any shared package the plugin ships (e.g. `@ship/ai`, `@ship/emails`)

The merge is **copy-new-files-only**: your curated files always win, so a plugin can only contribute *new* files, never overwrite yours. Its declared dependencies are appended to the right `package.json`, and any infra it needs (a `docker-compose.<plugin>.yml`, an `infra:<plugin>` script, extra `.env` lines) is wired in too.

After files land, codegen runs so the new resources and routes are live:

```bash theme={null}
pnpm --filter api codegen
```

That regenerates `src/router.ts`, `src/contract.ts`, and `src/db.ts` to include the plugin's endpoints and tables. For a fresh scaffold, the CLI runs this for you after install.

## Installing plugins

Pick plugins during scaffolding — the [`init`](/docs/packages/create-ship-app) flow shows a multiselect, then merges your choices into the new project:

```bash theme={null}
npx @paralect/ship init
```

You can also merge a plugin into an existing project from its source:

```bash theme={null}
pnpm plugin:install <git-url>
pnpm plugin:uninstall <name>
pnpm plugin:list
```

<Info>Auth is plugin-delivered. The base `apps/web` ships only a landing page; the **Auth** plugin (`auth-starter`) adds the API wiring, the sign-in/up and reset pages, the authenticated app shell, and the typed oRPC client. A full-stack project that needs accounts starts with this plugin.</Info>

## The catalog

| Plugin            | What it adds                                                                                                                    | Requires                        |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| **auth-starter**  | better-auth wiring — email/password, verification, reset, Google OAuth — plus the web auth pages, app shell, and oRPC client.   | postgres, mailer, cloud-storage |
| **admin**         | An admin dashboard with a paginated user list at `/app/admin`. The canonical TanStack Router plugin example.                    | postgres, auth-starter          |
| **notes**         | A small notes CRUD resource and UI — the minimal end-to-end plugin example.                                                     | postgres                        |
| **ai-chat**       | Streaming AI chat via the `@ship/ai` package (Google Gemini): conversations, messages, AI responses.                            | postgres, auth-starter          |
| **mailer**        | Transactional email — [Resend](https://resend.com/) + [React Email](https://react.email/) templates, exposed as `@ship/emails`. | —                               |
| **cloud-storage** | S3-compatible file storage with a local [Garage](https://garagehq.deuxfleurs.fr/) dev server, exposed as `@ship/cloud-storage`. | —                               |

A plugin's `requires` are merged in automatically — selecting **admin** pulls in `auth-starter`, which pulls in `mailer` and `cloud-storage`.

## Anatomy of a plugin

A plugin mirrors the [resource-owns-everything](/docs/how-ship-works) layout of the app it merges into:

```text theme={null}
my-plugin/
  plugin.json                 # name, version, requires, dependencies
  api/
    src/resources/things/
      things.schema.ts         # pgTable + Zod → picked up by codegen-db
      endpoints/*.ts           # oRPC endpoints → picked up by codegen-router
      methods/*.ts             # business logic
      handlers/*.ts            # mutation-event handlers (side effects)
      middlewares/*.ts         # per-resource gates (can-edit-*.ts)
  web/
    routes/_authenticated/app/things/
      index.tsx                # → apps/web/src/routes/...
      -components/             # private, ignored by the router
  packages/
    my-lib/                    # → packages/my-lib (workspace:*)
```

The plugin's API resources build on the same `@/endpoint` base and the same `@/middlewares/*` gates (`is-authorized`, `is-admin`, `can-access`, `can-edit`) as the rest of your code — there's nothing plugin-specific to learn. Web routes go under `web/routes/**` and TanStack Router discovers them by path.

### `plugin.json`

The manifest names the plugin, lists what it `requires`, and declares which dependencies go to which app:

```json plugin.json theme={null}
{
  "name": "ai-chat",
  "version": "1.0.0",
  "description": "AI chat with Google Gemini",
  "dependencies": {
    "api": { "@ship/ai": "workspace:*" }
  }
}
```

`dependencies.api` is appended to `apps/api/package.json`; `dependencies.web` to `apps/web/package.json`.

## Why this matters

Because a plugin is just your code in the standard layout, there's no abstraction boundary to fight. You can read every line, set a breakpoint in it, refactor a handler, or delete the feature outright. Coding agents see the same one-obvious-way structure they see everywhere else in the repo — the plugin is indistinguishable from code you wrote by hand, because after install, it is.
