Skip to main content
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 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:
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 flow shows a multiselect, then merges your choices into the new project:
You can also merge a plugin into an existing project from its source:
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.

The catalog

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 layout of the app it merges into:
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:
plugin.json
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.