Source:
ocean/docs/adr/0018-plan-model-and-upgrade-paths.md| ✏️ Edit on GitHub
ADR-0018: Plan Model, Upgrade Paths, and Seat Management
Date: 2025-08-13
Status
Accepted
Context
We need a clear plan model designed for fast signup and smooth in-product upsell:
- Default all organizations to Free plan at signup; create a Stripe customer up front
- Enable simple upgrade paths to Pro (single seat) and Team (multi-seat) from the billing page
- Enterprise remains manual (contact sales)
- Use PostHog feature flags to gate features by plan
Decision
- Default Plan
- New organizations are set to
freeduring provisioning if unset.
- Upgrade Paths
- Pro: single-seat subscription (quantity=1) with
STRIPE_PRO_PRICE_ID. - Team: multi-seat subscription using
quantityas seat count withSTRIPE_TEAM_PRICE_ID. - Enterprise: contact sales; no automatic subscription creation.
- Seat Management
- Team seats map to the subscription item
quantity. - Admin/Owner can update seats via
updateSubscription({ priceId, quantity }). - UI exposes a numeric input for seat count (min 1) when Team is active.
- Feature Flags (PostHog)
- Gate features by plan with flags, e.g.,
team-invites-enabled,api-access-enabled,advanced-analytics, etc. - Client uses
usePostHogFeature; server may evaluate flags or enforce plan checks in resolvers as needed.
- Stripe/GraphQL Integration
- GraphQL billing resolvers implement create/update/cancel/resume subscription.
- Plan is updated on
organizations.planwhen priceId matches env-configured IDs. - Idempotent behavior and permission checks enforced server-side.
Consequences
Positive
- Frictionless Free plan signup with immediate upsell capability
- Simple, maintainable seat management via Stripe quantity
- Clean plan-to-feature mapping with flags
Negative
- Requires environment configuration for price IDs
- Seat changes require Stripe update; audit and UI feedback needed
Implementation Details
- Stripe price IDs via env:
STRIPE_PRO_PRICE_ID,STRIPE_TEAM_PRICE_ID - Billing resolvers:
createSubscription,updateSubscription,cancelSubscription,resumeSubscription - Org field
planset on provisioning (free) and updated on plan change - Team seat management flows through
updateSubscription.quantity
Alternatives Considered
- Multiple items per subscription for seats
- Rejected: more complex than necessary; single item quantity is sufficient
- Separate seat table detached from Stripe quantity
- Rejected: risks desync and increases complexity
References
- ADR-0012: GraphQL-Based Billing Architecture
- ADR-0016: GraphQL Billing Resolver Alignment and Billing UI Standards
- ADR-017: Synchronous User Provisioning During Signup