Skip to main content

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

  1. Default Plan
  • New organizations are set to free during provisioning if unset.
  1. Upgrade Paths
  • Pro: single-seat subscription (quantity=1) with STRIPE_PRO_PRICE_ID.
  • Team: multi-seat subscription using quantity as seat count with STRIPE_TEAM_PRICE_ID.
  • Enterprise: contact sales; no automatic subscription creation.
  1. 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.
  1. 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.
  1. Stripe/GraphQL Integration
  • GraphQL billing resolvers implement create/update/cancel/resume subscription.
  • Plan is updated on organizations.plan when 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 plan set on provisioning (free) and updated on plan change
  • Team seat management flows through updateSubscription.quantity

Alternatives Considered

  1. Multiple items per subscription for seats
  • Rejected: more complex than necessary; single item quantity is sufficient
  1. 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