feat(pricing): implement A/B experiment for pricing page flexibility/visibility (#44293)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES/NO

## What kind of change does this PR introduce?

Includes 5 new variants to test pricing to solve cost flexibility and
visibility concerns

## What is the current behavior?

Current pricing page

## What is the new behavior?
Variants:
<img width="3840" height="2160" alt="CleanShot 2026-03-30 at 19 32
02@2x"
src="https://github.com/user-attachments/assets/a4f3b048-fa0d-4137-9f24-2eb6d431f3c8"
/>
<img width="3840" height="2160" alt="CleanShot 2026-03-30 at 19 33
34@2x"
src="https://github.com/user-attachments/assets/a9045a33-b8c3-4df1-a47d-00cf58495bb3"
/>
<img width="3840" height="2160" alt="CleanShot 2026-03-30 at 19 32
22@2x"
src="https://github.com/user-attachments/assets/10511fc8-5e88-4e3c-baa1-56798009d86f"
/>
<img width="3840" height="2160" alt="CleanShot 2026-03-30 at 19 33
09@2x"
src="https://github.com/user-attachments/assets/aa01aaf6-0e41-4466-bfc7-77ae912b6043"
/>
<img width="3840" height="2160" alt="CleanShot 2026-03-30 at 19 32
43@2x"
src="https://github.com/user-attachments/assets/21834f7d-43cb-4981-9c29-934f44986714"
/>




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

**New Features**
- Launched A/B testing variants on the pricing page featuring customized
pricing plan layouts, descriptions, and billing options based on your
assigned experiment group.
- Certain plan tiers now display optional flexibility information,
hourly billing rates, and project scaling details to help optimize your
cost estimation and plan selection.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Mert YEREKAPAN authored and GitHub committed 2026-04-01 13:40:20 +02:00
1 parent 83edf14a23
commit f590319f0a
3 files changed
+169 -27

No files matched your search

+60 -1
View File
@@ -4,13 +4,25 @@ import { ArrowDownIcon } from '@heroicons/react/outline'
import DefaultLayout from '~/components/Layouts/Default'
import PricingPlans from '~/components/Pricing/PricingPlans'
import { useOrganizations } from '~/data/organizations'
import { hasConsented, posthogClient } from 'common'
import { ArrowUpRight } from 'lucide-react'
import dynamic from 'next/dynamic'
import { usePathname } from 'next/navigation'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { Button } from 'ui'
import { InfoTooltip } from 'ui-patterns/info-tooltip'
const EXPERIMENT_ID = 'pricingPageExperiment' as const
export type PricingPageExperimentVariant =
| 'control'
// GROWTH-694: flexibility/hourly billing
| 'flexibility' // concrete FAQ example as section between cards and buttons
| 'flexibility_card' // "no lock-in" messaging on Pro card warning
| 'hourly_rate' // actual hourly compute rate on Pro card warning
// GROWTH-697: pro project cost visibility
| 'multi_project' // "first project included, additional from $10/mo" on Pro card
| 'estimate_cta' // "estimate your cost" link on Pro card scrolling to calculator
const PricingComputeSection = dynamic(() => import('~/components/Pricing/PricingComputeSection'))
const PricingAddons = dynamic(() => import('~/components/Pricing/PricingAddons'))
const PricingComparisonTable = dynamic(() => import('~/components/Pricing/PricingComparisonTable'))
@@ -45,6 +57,41 @@ export default function PricingContent() {
const { isLoading, organizations } = useOrganizations()
const hasExistingOrganizations = !isLoading && organizations.length > 0
// Pricing value/flexibility A/B experiment
// Uses client-side PostHog directly — server-side evaluation lacks full person context for www pages.
// DevToolbar overrides (x-ph-flag-overrides cookie) are respected in local dev via posthogClient.getFeatureFlag.
const [flagValue, setFlagValue] = useState<PricingPageExperimentVariant | false | undefined>(
() =>
posthogClient.getFeatureFlag(EXPERIMENT_ID) as
| PricingPageExperimentVariant
| false
| undefined
)
useEffect(() => {
return posthogClient.onFeatureFlags(() => {
const value = posthogClient.getFeatureFlag(EXPERIMENT_ID)
setFlagValue(value as PricingPageExperimentVariant | false | undefined)
})
}, [])
const validVariants: PricingPageExperimentVariant[] = [
'control',
'flexibility',
'flexibility_card',
'hourly_rate',
'multi_project',
'estimate_cta',
]
const isInExperiment = validVariants.includes(flagValue as PricingPageExperimentVariant)
const showFlexibilitySection = flagValue === 'flexibility'
useEffect(() => {
if (!isInExperiment) return
posthogClient.captureExperimentExposure(EXPERIMENT_ID, { variant: flagValue }, hasConsented())
}, [isInExperiment, flagValue])
return (
<DefaultLayout>
<div className="relative z-10 pt-8 pb-4 xl:py-16">
@@ -64,8 +111,20 @@ export default function PricingContent() {
<PricingPlans
organizations={organizations}
hasExistingOrganizations={hasExistingOrganizations}
experimentVariant={isInExperiment ? (flagValue as PricingPageExperimentVariant) : undefined}
/>
{showFlexibilitySection && (
<div className="mx-auto max-w-xl px-8 mt-10 xl:mt-16 text-center">
<p className="text-foreground text-sm">
Need more power for a launch? Scale up instantly.{' '}
<span className="text-foreground-light">
Scale back down after — you only pay for the hours you use.
</span>
</p>
</div>
)}
<div className="text-center mt-10 xl:mt-16 mx-auto max-w-lg flex flex-col gap-8">
<div className="flex justify-center gap-2">
<a href="#compare-plans">
+79 -26
View File
@@ -1,20 +1,26 @@
'use client'
import Link from 'next/link'
import { Check } from 'lucide-react'
import { plans } from 'shared-data/plans'
import { Button, cn } from 'ui'
import type { PricingPageExperimentVariant } from '~/app/pricing/PricingContent'
import { Organization } from '~/data/organizations'
import { useSendTelemetryEvent } from '~/lib/telemetry'
import { Check } from 'lucide-react'
import Link from 'next/link'
import { plans } from 'shared-data/plans'
import { Button, cn } from 'ui'
import UpgradePlan from './UpgradePlan'
interface PricingPlansProps {
organizations?: Organization[]
hasExistingOrganizations?: boolean
experimentVariant?: PricingPageExperimentVariant
}
const PricingPlans = ({ organizations, hasExistingOrganizations }: PricingPlansProps) => {
const PricingPlans = ({
organizations,
hasExistingOrganizations,
experimentVariant,
}: PricingPlansProps) => {
const sendTelemetryEvent = useSendTelemetryEvent()
return (
@@ -66,14 +72,37 @@ const PricingPlans = ({ organizations, hasExistingOrganizations }: PricingPlansP
)}
</div>
</div>
<p
<div
className={cn(
'text-foreground-light mb-4 text-sm 2xl:pr-4',
isProPlan && 'xl:mb-12'
'flex flex-col',
isProPlan &&
(!experimentVariant || experimentVariant === 'control') &&
'xl:mb-12'
)}
>
{plan.description}
</p>
<p
className={cn(
'text-foreground-light text-sm 2xl:pr-4',
isUpgradablePlan &&
(experimentVariant === 'flexibility_card' ||
experimentVariant === 'hourly_rate')
? 'mb-2'
: 'mb-4'
)}
>
{plan.description}
</p>
{isUpgradablePlan && experimentVariant === 'flexibility_card' && (
<p className="text-brand text-xs mb-4">
No lock-in. Scale up or down anytime — you only pay for the hours you use.
</p>
)}
{isUpgradablePlan && experimentVariant === 'hourly_rate' && (
<p className="text-brand text-xs mb-4">
Compute from $0.01/hr. Scale anytime, pay only for what you use.
</p>
)}
</div>
{isUpgradablePlan && hasExistingOrganizations ? (
<UpgradePlan
planId={plan.planId}
@@ -123,24 +152,48 @@ const PricingPlans = ({ organizations, hasExistingOrganizations }: PricingPlansP
</p>
</div>
{plan.warning && (
{isUpgradablePlan && experimentVariant === 'multi_project' ? (
<div className="mt-4 flex flex-col gap-1">
<span
className={cn(
'text-[13px] leading-4 inline-flex gap-1 items-center'
)}
>
{plan.warning}
<span className="text-[13px] leading-4">
First project included. Additional projects from $10/mo.
</span>
{(plan.name === 'Pro' || plan.name === 'Team') && (
<Link
href="#addon-compute"
className="hover:underline text-foreground-lighter text-[13px] m-0 p-0 leading-3"
>
Need more compute?
</Link>
)}
<Link
href="#addon-compute"
className="hover:underline text-foreground-lighter text-[13px] m-0 p-0 leading-3"
>
See how pricing scales
</Link>
</div>
) : isUpgradablePlan && experimentVariant === 'estimate_cta' ? (
<div className="mt-4 flex flex-col gap-1">
<span className="text-[13px] leading-4">{plan.warning}</span>
<Link
href="#addon-compute"
className="hover:underline text-brand text-[13px] m-0 p-0 leading-3 font-medium"
>
Estimate your cost &darr;
</Link>
</div>
) : (
plan.warning && (
<div className="mt-4 flex flex-col gap-1">
<span
className={cn(
'text-[13px] leading-4 inline-flex gap-1 items-center'
)}
>
{plan.warning}
</span>
{(plan.name === 'Pro' || plan.name === 'Team') && (
<Link
href="#addon-compute"
className="hover:underline text-foreground-lighter text-[13px] m-0 p-0 leading-3"
>
Need more compute?
</Link>
)}
</div>
)
)}
</div>
</div>
+30
View File
@@ -2992,6 +2992,35 @@ export interface StorageExplorerNavigateSubmittedEvent {
groups: TelemetryGroups
}
/**
* User was exposed to the pricing value/flexibility experiment on the /pricing page.
*
* @group Events
* @source www
* @page /pricing
*/
export interface PricingPageExperimentExposedEvent {
action: 'pricing_page_experiment_exposed'
properties: {
/**
* Experiment identifier for tracking
*/
experiment_id: 'pricingPageExperiment'
/**
* Experiment variant.
* GROWTH-694: flexibility (section), flexibility_card (on card), hourly_rate (on card)
* GROWTH-697: multi_project (on card), estimate_cta (on card)
*/
variant:
| 'control'
| 'flexibility'
| 'flexibility_card'
| 'hourly_rate'
| 'multi_project'
| 'estimate_cta'
}
}
/**
* @hidden
*/
@@ -3160,3 +3189,4 @@ export type TelemetryEvent =
| ComputeBadgeUpgradeClickedEvent
| FreeMicroUpgradeBannerDismissedEvent
| FreeMicroUpgradeBannerCtaClickedEvent
| PricingPageExperimentExposedEvent