wpmoo core is a domain-free, single-tenant SaaS skeleton. It provides the boring foundation a product usually needs before domain logic exists: authentication, guarded settings, an empty admin dashboard shell, email, S3-compatible storage, one-time Stripe payments, pg-boss jobs, i18n, GDPR-oriented privacy controls, tests, CI, and deployment notes.
This repository is intentionally not an event, project, mentor, jury, PDF, subscription, or multi-tenant app.
apps/playground: Next.js playground app with public pages, auth pages, guarded dashboard/settings, i18n, payment/privacy API routes, and a necessary-cookie banner.apps/worker: long-running pg-boss worker for welcome emails and data export emails.packages/db: Drizzle schema and database ownership for auth, RBAC, files, payments, and supporting tables.packages/auth: Better Auth wrapper for email/password, magic link, and two-factor authentication.packages/ui: Base UI-backed shared component foundation and empty admin shell.packages/email: SMTP/Mailpit and Brevo email providers plus auth templates.packages/storage: S3-compatible storage service with DB-backed ownership checks.packages/payment: one-time Stripe Checkout and webhook handling; subscription mode is interface-only.packages/i18n: EN/DE routing and locale config throughnext-intl.packages/jobs: pg-boss service wrapper, registry, enqueue helpers, and core job payloads.packages/privacy: redacted data export snapshots, account anonymization, and cookie-consent helpers.
This is a single-tenant skeleton. It does not include organizations, workspaces, tenant-scoped membership, tenant billing, or per-tenant RBAC.
If a downstream product needs multi-tenancy, add it deliberately in that product or in a future scoped package. Do not reuse the current generic RBAC tables as an implicit tenant model.
The auth stack is Better Auth, not Auth.js/NextAuth. When merging this skeleton into an app that already uses Auth.js:
- Treat
packages/authandpackages/db/src/schema/user.tsas the target auth boundary. - Migrate session, account, verification, and two-factor data intentionally; do not assume Auth.js tables are drop-in compatible.
- Do not run Better Auth CLI migrations.
@wpmoo/dbowns every table and Drizzle migrations are the single schema source. - Keep auth route handlers under
/api/auth/*; i18n middleware excludes API routes, webhooks, and callbacks.
The dashboard and RBAC schema are scaffold-only. No product roles, demo admin role, tenant role, or domain permission set should be treated as production-ready.
Before shipping a product, define the real role/permission model, seed it explicitly, and re-guard every sensitive server operation. Client-side navigation or hidden buttons are not security boundaries.
Use Node 20 or newer and pnpm@10.34.1 through Corepack.
corepack enable
pnpm install --frozen-lockfile
cp .env.example .env
docker compose up -d postgres minio minio-init mailpit
pnpm db:push # create/sync the dev database tables (confirm when prompted)
pnpm devLocal dev and db:* scripts read environment from the repo-root .env via
dotenv-cli, so the values you set there are what the app and Drizzle use. The
RBAC seed script (pnpm db:seed:roles) also reads the repo-root .env. (CI and
production inject env directly; build/start are not wrapped.)
To exercise RBAC locally, set WPMOO_ADMIN_EMAILS (or WPMOO_SETUP_TOKEN) in
.env, register that email, verify it via Mailpit, then claim the first admin at
/setup/admin.
Upgrading an existing install after the permission system was added: if you
bootstrapped a first-admin before seedDemoRoles started seeding permissions and
rolePermission rows, your admin has roles but no permissions and will be blocked
by requirePermission guards. Run pnpm --filter @wpmoo/rbac seed:roles once to
backfill the permission rows (idempotent; safe to re-run).
Useful local URLs:
- Playground:
http://localhost:3000 - Mailpit:
http://localhost:8025 - MinIO console:
http://localhost:9001
All Drizzle tables live in packages/db. Better Auth uses that schema; it does not own migrations.
pnpm db:generate
pnpm db:migrate
pnpm db:studioRun migration commands only when you intend to change or apply the Drizzle schema.
pnpm lint
pnpm typecheck
pnpm test
pnpm test:e2e
pnpm buildCI runs install, lint, typecheck, unit tests, Playwright Chromium smoke tests, and build. The e2e smoke runs against a production-backed Next server and covers public rendering, auth-page rendering, localized privacy routing, cookie acknowledgement, and the absence of /datenschutz.
Deployment notes live in docs/deploy:
The web app and worker are separate processes. Both need the same Postgres database because jobs use pg-boss in Postgres. Do not add Redis for the current job queue.
- Replace
/privacy,/terms, and/imprintplaceholder copy. - Set real auth, email, storage, Stripe, and job environment variables.
- Run
@wpmoo/dbmigrations against the production database. - Start the worker as a long-running process.
- Configure the Stripe webhook at
/api/payment/webhook. - Replace scaffold RBAC/demo assumptions with product-specific authorization.