Wire a backend or infrastructure module (auth, database, payments, email, file storage, PDF/document generation, content CMS, deploy config) into an already-scaffolded app. Reads `.workflow/meta.json#stack` for the chosen tech (better-auth, drizzle+neon, stripe, resend, vercel, etc.) and modifies the codebase at the project root to install + configure the module end-to-end. Always idempotent — running twice does not duplicate config. Use when the user says "add auth", "wire up the database", ...
Installs into .claude/skills of the current project.
Are you the author of Module Add?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/lukedj78-module-add)
---
name: module-add
description: 'Wire a backend or infrastructure module (auth, database, payments, email, file storage, PDF/document generation, content CMS, deploy config) into an already-scaffolded app. Reads `.workflow/meta.json#stack` for the chosen tech (better-auth, drizzle+neon, stripe, resend, vercel, etc.) and modifies the codebase at the project root to install + configure the module end-to-end. Always idempotent — running twice does not duplicate config. Use when the user says "add auth", "wire up the database", "set up Stripe", "add file uploads", "generate a PDF invoice/report", "genera un PDF", "add a CMS", "set up Sanity", "expose this as an MCP server", "usare il prodotto da Claude Code", "configure deploy", "aggiungi gli analytics", "add error tracking", "monitoring", "aggiungi sentry", "osservabilità", "module aggiungi auth", or the orchestrator routes here from phase `scaffolded` / `page_generated`. Not for: scaffolding the app (`design-md-to-app`) building UI (`screenshot-to-page`), or an Expo/RN app (`rn-module-add`).'
---
# module-add — wire a backend/infra module into the scaffold
This skill is the bridge between a styled-but-empty app and a functional product. Every "module" follows the same shape: install packages, generate config, write a small reference implementation (one route or one server function), update `meta.json#stack` to record the choice, and bump phase.
**Modules supported in v1**:
| Module | Default tech | Reference file | Status |
|---|---|---|---|
| `auth` | better-auth (email/password + magic link; `@better-auth/mcp` + `@better-auth/cimd` when the product **is** an MCP server) | `references/module-auth.md` | ✅ implemented |
| `db` | Drizzle ORM + Neon Postgres | `references/module-db.md` | ✅ implemented |
| `payments` | Stripe (subscriptions + one-time) | `references/module-payments.md` | ✅ implemented |
| `email` | Resend + React Email | `references/module-email.md` | ✅ implemented |
| `test` | Vitest + Testing Library + Playwright | `references/module-test.md` | ✅ implemented |
| `ci` | husky + lint-staged + GitHub Actions | `references/module-ci.md` | ✅ implemented |
| `motion` | Motion (rebranded framer-motion) + opinionated wrappers | `references/module-motion.md` | ✅ implemented |
| `storage` | Vercel Blob (UploadThing / S3 as alternatives) | `references/module-storage.md` | ✅ implemented |
| `deploy` | Vercel (project config — `vercel-deploy` ships it) | `references/module-deploy.md` | ✅ implemented |
| `voice` | AI Gateway realtime (`@ai-sdk/gateway` + `experimental_useRealtime`) | `references/module-voice.md` | ✅ implemented — experimental API |
| `realtime` | Vercel Functions WebSockets (`experimental_upgradeWebSocket`) | `references/module-realtime.md` | ✅ implemented — experimental API |
| `cms` | Sanity via `next-sanity` (`sanityFetch` in Server Components; Studio as a `cms/` package — the admin panel) | `references/module-cms.md` | ✅ implemented |
| `pdf` | Forme (or Takumi) + the [pdfcn](https://www.pdfcn.dev) shadcn registry | `references/module-pdf.md` | ✅ implemented — renderers pre-1.0 |
| `mcp` | `@modelcontextprotocol/sdk` + `@better-auth/mcp` — the product published **as an MCP server**, one tool per service function | `references/module-mcp.md` | ✅ implemented — requires `auth` with its MCP section |
| `analytics` | **opt-in, by level** — traffic: Vercel Web Analytics + Speed Insights · product: an `analytics_events` table in the project's database + `track()` + activation/D7/D30 SQL · advanced (on request): PostHog EU, OpenPanel for `eu-sovereign` | `references/module-analytics.md` | ✅ implemented |
| `observability` | **by level, proposed before the first production deploy** — errors: `@sentry/nextjs` (Sentry EU in Frankfurt, GlitchTip for `eu-sovereign`) on `instrumentation.ts` `onRequestError`, the client, `error.tsx` and `global-error.tsx`, with the release and no personal data · logs: `lib/log.ts` with `x-request-id`, a drain on Pro · uptime: `/api/health` and a monitor. Proven against a local fake ingest before any account exists; `scripts/observability_check.py` checks it | `references/module-observability.md` | ✅ implemented |
**Provider notes** (not modules — where an alternative vendor slots into the modules above):
| Provider | What it can replace | Reference |
|---|---|---|
| **Scaleway** | Nothing, by default — it **adds**: a GPU for a model eve cannot run itself, a batch job that outlives a turn, and an EU home for data that must not leave. eve reaches it **through a tool**, never through config. Start at the file's runtime diagram and worked example | `references/provider-scaleway.md` |
A provider note is a decision layer, never a second module: it says which slot the vendor fills, what the swap costs, and when the default stays. Read it before offering the vendor, not after.
The user can override any default. If they say "add auth with Clerk", read `references/module-auth.md` for the Clerk variant if present; otherwise refuse and explain — better-auth is the default and adding new variants is a contract change.
Every module in the table now has a full reference file. What's still open is **alternative variants inside** a module (Clerk instead of better-auth, Prisma instead of Drizzle, Fly.io instead of Vercel) — `references/module-stubs.md` tracks those. Implement a variant on first request: copy `module-auth.md` or `module-db.md` as the structural template, ground every identifier in the vendor's current docs, then run.
## When this skill applies
- A `<project-root>/` exists (`phase >= scaffolded`).
- The user names a module: "add auth", "wire the db", "add stripe", "set up resend".
- The orchestrator routes here from `scaffolded` or `page_generated`.
## Contract
This skill follows the dev-flow contract — see `references/contracts.md`. Key facts:
- Reads `<root>/.workflow/meta.json#stack` to honor existing choices.
- Modifies `<project-root>/` (installs deps, writes config, writes one example route or function).
- Updates `meta.json#stack` to record the module's tech choice (e.g., `stack.auth = "better-auth"`).
- Sets `phase = "module_added"` (only if current phase is earlier in the enum).
- **Idempotent**: re-running the same module check whether it's already wired and skips/updates instead of duplicating.
## Monorepo awareness
Before Step 1: check `meta.json#stack.framework`.
- If `"monorepo"`: this skill operates inside `apps/web/` (cwd = `<project-root>/apps/web/`) for web-specific modules (motion, email server actions, queries), AND inside `packages/api/` for modules consumed by both apps (auth, db, storage, realtime, payments — these are backend client wiring, not web-specific UI). Run `monorepo-sync-types` after `db` and `auth` modules to propagate types to `packages/shared/`.
- If `"next"` / `"vite-react"` / `"remix"` / `"astro"` / etc.: operate at project root (current behavior, unchanged).
- If `"expo-rn"`: refuse — use `rn-module-add` instead.
For the monorepo case, "install + generate" means: install npm packages into the appropriate workspace (`pnpm add --filter @<slug>/web` or `pnpm add --filter @<slug>/api`), and generate code into the right sub-folder. Use the workspace-relative paths in all references below.
## Workflow
### Step 1 — Determine the target module + tech
The user tells you which module (one per invocation: `auth`, `db`, etc.). Sub-decision: which tech?
- If `meta.json#stack.<module>` is set, use that tech. Don't ask again.
- If not set, propose the default from the table above with a one-line rationale ("Suggerisco better-auth perché è leggero, self-hosted, e si integra naturalmente con Drizzle"), then accept whatever the user picks.
If the user picks a tech the skill doesn't have a reference file for, refuse and offer to add it (i.e., they want a new variant — that's a contract update, not a runtime choice).
### Step 1b — Where this module's data goes (every module that adds a provider)
Read `meta.json#stack.data_residency` before wiring anything (`dev-flow/references/eu-data-sovereignty.md`):
- **`"eu"` / `"eu-sovereign"`** — set the provider's **EU region explicitly** in the config you write (a region left to the vendor default is usually a US one), using the ids in the reference's service table. Under `"eu-sovereign"`, offer the European alternative from that table first and note when the chosen vendor is US-controlled.
- **The provider has no EU region, or the user keeps a non-EU option** — do not refuse and do not stop. Say it in one line, wire it, and record it with its transfer basis (DPF, SCC, adequacy, or `unknown`) — the register flags it for `compliance-audit`.
- **Always** add or update the row: `python3 <dev-flow>/scripts/data_residency.py add <root> --name <vendor> --service <what it does> --data <personal data it receives> --region <region> --transfer <basis> [--dpa <url>] [--eu-alternative <name>] [--us-controlled] --by module-add`. A library that keeps data in the app's own database (better-auth, Drizzle) adds no sub-processor — the database provider is the row.
- **`null`** — the decision was skipped: ask the four questions now (`data_residency.py decide`) instead of guessing a region.
### Step 2 — Read the variant reference
Open `references/module-<name>.md` for the chosen module. The reference is the source of truth for the install steps and the file paths. Read it end-to-end before doing anything.
Each reference describes:
1. **Install commands** — packages to add, optional dev deps, environment variables to set.
2. **Config files** — paths and templates (e.g., `<project-root>/lib/auth.ts`, `drizzle.config.ts`).
3. **Schema or migration setup** — for `db`, the Drizzle schema folder layout; for `payments`, the webhook secret rotation.
4. **Reference implementation** — exactly one route or server function showing the module in use (e.g., a `/sign-in` page for auth, a `/api/stripe/webhook` for payments). The user can copy-paste-evolve from there.
5. **Idempotency check** — how to detect the module is already installed (typically a `package.json` dependency check + a config file check). On re-run, skip install + config but offer to regenerate the reference implementation if the user asks.
6. **Environment variables required** — list with descriptions; write a `.env.local.example` entry, never the real values.
### Step 3 — Apply the changes
Run install commands. Write config files from the templates. Write the reference implementation. Update `.env.local.example`.
If the framework-specific path conventions differ (Next App Router vs. Vite vs. Remix), the variant reference handles it — don't hand-edit paths in the skill body.
**Never run `npm install` for a non-reference module.** Stick to what the variant reference declares. If the user wants additional libraries, that's a separate `module-add` invocation or a manual step.
### Step 4 — Verify
After install + config:
- Run `npm run build` in `<project-root>/`. If it fails, the most common cause is a missing env var (placeholders accepted) — fix the placeholder and retry once. If still failing, stop and tell the user what's missing.
- For modules that need a connection (db, stripe, email), do **not** try to actually connect — that requires real credentials the user will set in `.env.local`. Just verify the code compiles and imports resolve.
- For `db`, run `drizzle-kit push` against a placeholder URL to confirm the schema is valid syntactically. If `drizzle-kit` requires a real URL, skip this and tell the user "run `npm run db:push` once your `DATABASE_URL` is set".
### Step 5 — Update state and report
Update `.workflow/meta.json`:
- if current phase is earlier than `module_added`, set `phase = "module_added"`. (Phase is monotonic — running `module-add` multiple times keeps the phase, just appends to history.)
- record the chosen tech: `stack.<module> = "<tech>"`.
- bump `updated_at`.
- append history:
```json
{
"skill": "module-add",
"ran_at": "<now>",
"inputs": {"module": "auth", "tech": "better-auth"},
"outputs": ["lib/auth.ts", "lib/auth-client.ts", "app/api/auth/[...all]/route.ts", "app/sign-in/page.tsx", ".env.local.example"],
"phase_before": "<prev>",
"phase_after": "module_added"
}
```
Tell the user:
- What was installed and where (paths).
- Required environment variables, with a pointer to `.env.local.example`.
- The reference implementation route/function (so they know where to start customizing).
- Any out-of-band steps the user must do themselves (create a Neon project, get a Stripe API key, configure DNS for Resend, etc.) — be concrete and link to the relevant dashboard.
- Next-step proposal: another `module-add` for a different module, or `screenshot-to-page` to keep building UI.
## Important constraints
- **One module per invocation.** "Add auth and db" is two runs — keep history clean.
- **Idempotent.** Re-running `module-add auth` on a project that already has it should detect, skip install, and offer to regenerate examples or rotate env vars — not double-install.
- **No real credentials.** Always use placeholders in `.env.local.example`. The user fills `.env.local` themselves.
- **Don't pick alternative tech silently.** If the variant reference doesn't cover the user's tech of choice, refuse and explain.
- **Preserve user code.** If the user has already written something at the path the reference implementation would land (e.g., they made a custom `/sign-in` page), don't overwrite. Ask, or write to `<route>-example/` instead.
- **Stay within `<project-root>/`.** Don't touch other parts of `.workflow/`. Other skills own those.
- **UI a module adds obeys golden rule 3.** Sign-in and settings screens, upload widgets, billing tables, realtime feeds: before writing any of it, search `components/ui/` for the primitive (`Field`, `Input`, `Button`, `Table`, `Dialog`, `Sonner`, `Skeleton`, `Empty`…) and use it. The reference snippets show the wiring, not a licence to render raw `<input>`/`<button>`; never pull in a provider's own component kit (a second component library) when the declared primitives can render it. A truly new primitive is a recorded exception (`meta.json#stack_config.primitive_exceptions`).
## Cross-module dependencies
Some modules depend on others. If the user runs them out of order, the skill must:
- `auth` requires `db`. If `stack.db` is `null`, ask: "auth needs a database to store sessions. Set up `db` first?" and route there before `auth`.
- `analytics` at the `product` level requires `db` (the events live in the project's database) and a signed-in user id (`auth`); `traffic` requires only a Vercel deploy. `analytics` is opt-in: never wire it unless the user asked to measure.
- `payments` requires `auth` and `db`. Same pattern.
- `email` is independent.
- `storage` (Vercel Blob) strongly wants `auth` **and** `db`: the client-upload token route authenticates in `onBeforeGenerateToken` — skip it and the store is world-writable — and the `files` table is what makes an upload ownable, queryable and erasable (DSAR). Ask before wiring it without them.
- `test` is independent — best run after `db` so server-action smoke tests have a schema to mock against, but works without it.
- `ci` is mostly independent — works best after `test` (so CI has tests to run) but writes a workflow that gracefully skips test steps when none exist.
- `motion` is fully independent — pure UI concern, no backend dependency. Run only when a project genuinely needs JS-driven motion (gestures, magnetic hover, shared-element transitions); for plain reveal-on-scroll, `tw-animate-css` (already installed via shadcn) is enough and lighter.
- `voice` requires `auth` when the voice session needs to be scoped to a signed-in user (most products); independent only for anonymous/demo voice agents — ask before skipping auth.
- `realtime` is mostly independent (a raw WebSocket channel doesn't need auth to exist), but requires `auth` the moment messages are scoped per-user/per-room — ask the same way as `voice`.
- `deploy` should be last — it reads the configured stack and produces deploy config.
When dependencies are missing, **ask, don't block silently.** "I see you're trying to add auth but there's no database yet. Want me to set up `db` (Neon + Drizzle) first, then come back to auth?"
## Folder structure rules (canonical)
When this skill wires a module, respect the canonical folder structure (spec: `docs/superpowers/specs/2026-06-06-folder-structure-refactor.md`):
- **`auth` module**: UI components (sign-in form, sign-up form) go in `app/(auth)/_components/`. Auth client + helpers go in `lib/auth/`. The `<AuthGuard>` redirect lives in `app/(app)/layout.tsx`.
- **`db` module**: client + schema in `lib/db/`. No components written by this module.
- **`payments` module**: client wiring in `lib/payments/`. UI (paywall, checkout button) suggested in `app/(app)/<context>/_components/` or page-specific. Pricing comparison table eventually promotes to `components/shared/billing/PricingTable.tsx`.
- **`storage` module**: client in `lib/storage/`. Upload UI co-located with the page that uses it.
- **`email` module**: server-side only; no components.
- **`voice` module**: client component in `components/voice/` (e.g., `components/voice/voice-agent.tsx`). No `lib/` wiring beyond the AI Gateway client already covered by whatever LLM setup the project has.
- **`realtime` module**: client hook in `lib/realtime/` (e.g., `lib/realtime/use-socket.ts`). UI that consumes the hook is co-located with the page/component that uses it, same as any other hook consumer.
- **`deploy` module**: config files at project root only.