Inspiration

Every B2B SaaS needs the same three things before it can charge real customers: team accounts, role-based access, and billing that actually reflects who's on the plan. I've built this plumbing from scratch on multiple projects. It always takes 2–4 weeks and it's never the product — it's the infrastructure before the product.

I built Control Plane to make that layer disappear.

## What it does

Control Plane sits between your identity provider, your payment platform, and your app's business logic. It handles:

  • Organizations — users belong to teams, not just accounts
  • Role-based access — built-in admin/member roles, custom roles, permission guards
  • Billing sync — when a member joins, the seat count updates automatically; when a subscription lapses, new invites are blocked
  • Audit log — every mutation recorded with actor, org, and timestamp
  • Operator console — one dashboard to see every org, every member, every billing event. No more switching between your auth dashboard and your payment dashboard.

Developers integrate via a typed TypeScript SDK (@controlplane/sdk) or drop in embeddable React components (<MemberList />, <InviteMember />, <BillingPanel />). Self-serve onboarding takes under 10 minutes.

## How I built it

Turborepo monorepo with four packages and three apps:

  • apps/api — Hono REST API, deployed to Fly.io
  • apps/console — Next.js operator dashboard, deployed to Vercel
  • apps/web — marketing site + docs, deployed to Vercel
  • packages/db — Drizzle ORM + Neon serverless Postgres
  • packages/sdk — typed HTTP client, zero external dependencies
  • packages/ui — embeddable React components

Auth via JWT verification middleware. Billing via webhook processing with idempotency guards. 111 tests passing across all packages.

## Challenges I ran into

The hardest part was the data model. A developer using Control Plane has their own payment platform account — completely separate from how Control Plane bills that developer. Getting the two "layers" of billing cleanly isolated without leaking abstractions took several design iterations.

The second challenge was making the operator console genuinely useful rather than just a CRUD interface. The value is in seeing org state, member state, and billing state together on one screen — which required carefully designed cross-service queries.

## Accomplishments I'm proud of

The three automatic behaviors that normally require custom code:

  1. Member accepts invite → seat count syncs to payment platform
  2. Subscription canceled → new invites return 403
  3. Payment failed webhook → plan status updates, visible in console immediately

These work correctly under concurrent load and are covered by integration tests against a real database.

## What I learned

Designing APIs for other developers forces a level of clarity you don't need for internal tools. Every ambiguous type, every inconsistent error code, every missing field in a response is a support ticket. The SDK and the API had to be designed together, not sequentially.

## What's next

  • Public npm release of @controlplane/sdk and @controlplane/ui
  • Support for additional identity and payment providers beyond the current defaults
  • Usage-based billing primitives (event counters, metered features)
  • Hosted version with zero-config onboarding

Built With

Share this project:

Updates