refactor(pricing): derive pricing display strings from the shared-data catalog

This commit is contained in:
Pamela Chia committed 2026-08-26 17:24:33 +08:00
1 parent 58ff906e4b
commit ec8ed5f573
6 files changed
+203 -92

No files matched your search

@@ -5,6 +5,7 @@ import { useTheme } from 'next-themes'
import Image from 'next/image'
import Link from 'next/link'
import { FC } from 'react'
import { ADDONS, usd } from 'shared-data/pricing-catalog'
import { Button, cn } from 'ui'
import CostControlAnimation from './CostControlAnimation'
@@ -26,7 +27,7 @@ const addons = [
name: 'Custom Domain',
heroImg: 'custom-domain-on',
icon: 'custom-domain-upgrade',
price: 'Flat fee $10/month',
price: `Flat fee ${usd(ADDONS.customDomain.priceMonthlyPerDomain)}/month`,
description:
'Use your own domain for your Supabase project to present a branded experience to your users.',
ctaText: 'Documentation',
@@ -38,7 +39,7 @@ const addons = [
name: 'Point in Time Recovery',
heroImg: 'pitr-on',
icon: 'pitr-upgrade',
price: 'Starts from $100/month',
price: `Starts from ${usd(ADDONS.pitr.priceMonthlyPer7DaysRetention)}/month`,
description: 'Roll back to any specific point in time, down to the second.',
ctaText: 'Documentation',
ctaLink: 'https://supabase.com/docs/guides/platform/backups',
@@ -5,6 +5,7 @@ import Link from 'next/link'
import React, { useEffect, useRef, useState } from 'react'
import { useWindowSize } from 'react-use'
import { plans as allPlans } from 'shared-data/plans'
import { PLAN_BILLING, usd } from 'shared-data/pricing-catalog'
import { Button, cn } from 'ui'
import { ToggleGroup, ToggleGroupItem } from 'ui/src/components/shadcn/ui/toggle-group'
@@ -98,7 +99,7 @@ const PricingComputeSection = () => {
</>
)}
<p className="text-foreground-lighter text-xs mt-2">
Paid plans include $10/mo in compute credits, enough to cover one Micro instance.
{`Paid plans include ${usd(PLAN_BILLING.pro.computeCreditsMonthly)}/mo in compute credits, enough to cover one Micro instance.`}
</p>
</div>
</div>
@@ -1,28 +1,26 @@
import { Button } from 'ui'
import Panel from '../Panel'
import Link from 'next/link'
import { ArrowUpRight } from 'lucide-react'
import Link from 'next/link'
import { DISK_PRICING, qty, usd } from 'shared-data/pricing-catalog'
import { Button } from 'ui'
const diskTypes = [
{
name: 'General Purpose',
tagline: 'Balance between price and performance',
maxSize: '16 TB',
size: '8 GB included\nthen $0.125 per GB',
iops: '3,000 IOPS included\nthen $0.024 per IOPS',
throughput: '125 MB/s included\nthen $0.095 per MB/s',
durability: '99.9%',
},
{
name: 'High Performance',
tagline: 'For mission critical applications',
maxSize: '60 TB',
size: '$0.195 per GB',
iops: '$0.119 per IOPS',
throughput: 'Scales automatically with IOPS',
durability: '99.999%',
},
]
import Panel from '../Panel'
const diskTypes = Object.values(DISK_PRICING).map((disk) => ({
name: disk.displayName,
tagline: disk.tagline,
maxSize: `${disk.maxSizeTb} TB`,
size: disk.includedPerProject
? `${disk.includedPerProject.sizeGb} GB included\nthen ${usd(disk.perUnitMonth.sizeGb)} per GB`
: `${usd(disk.perUnitMonth.sizeGb)} per GB`,
iops: disk.includedPerProject
? `${qty(disk.includedPerProject.iops, 'comma')} IOPS included\nthen ${usd(disk.perUnitMonth.iops)} per IOPS`
: `${usd(disk.perUnitMonth.iops)} per IOPS`,
throughput:
disk.includedPerProject && disk.perUnitMonth.throughputMBps !== null
? `${disk.includedPerProject.throughputMBps} MB/s included\nthen ${usd(disk.perUnitMonth.throughputMBps)} per MB/s`
: (disk.throughputNote ?? ''),
durability: `${disk.durabilityPercent}%`,
}))
const PricingDiskSection = () => (
<div>
+37 -14
View File
@@ -1,3 +1,5 @@
import { ADDONS, gbDisplay, METERS, PLAN_BILLING, qty, usd, withUnit } from './pricing-catalog'
export type PlanId = 'free' | 'pro' | 'team' | 'enterprise'
export interface PricingInformation {
@@ -27,16 +29,19 @@ export const plans: PricingInformation[] = [
costUnit: '/ month',
href: 'https://supabase.com/dashboard/new?plan=free',
priceLabel: '',
priceMonthly: 0,
priceMonthly: PLAN_BILLING.free.priceMonthly,
description: 'Perfect for passion projects & simple websites.',
preface: 'Get started with:',
features: [
'Unlimited API requests',
'50,000 monthly active users',
['500 MB database size', 'Shared CPU • 500 MB RAM'],
['5 GB egress'],
['5 GB cached egress'],
'1 GB file storage',
`${qty(METERS.maus.includedByPlan.free, 'comma')} monthly active users`,
[
`${gbDisplay(METERS.databaseDiskSize.includedByPlan.free)} database size`,
'Shared CPU • 500 MB RAM',
],
[`${qty(METERS.egress.includedByPlan.free)} GB egress`],
[`${qty(METERS.cachedEgress.includedByPlan.free)} GB cached egress`],
`${qty(METERS.storageSize.includedByPlan.free)} GB file storage`,
'Community support',
],
footer: 'Free projects are paused after 1 week of inactivity. Limit of 2 active projects.',
@@ -51,18 +56,36 @@ export const plans: PricingInformation[] = [
href: 'https://supabase.com/dashboard/new?plan=pro',
priceLabel: 'From',
warning: 'Includes one project running on Micro compute.',
priceMonthly: 25,
priceMonthly: PLAN_BILLING.pro.priceMonthly,
description: 'For production applications with the power to scale.',
features: [
['100,000 monthly active users', 'then $0.00325 per MAU'],
['8 GB disk size per project', 'then $0.125 per GB'],
['250 GB egress', 'then $0.09 per GB'],
['250 GB cached egress', 'then $0.03 per GB'],
['100 GB file storage', 'then $0.0213 per GB'],
[
`${qty(METERS.maus.includedByPlan.pro, 'comma')} monthly active users`,
`then ${usd(METERS.maus.price)} per MAU`,
],
[
`${gbDisplay(METERS.databaseDiskSize.includedByPlan.pro)} disk size per project`,
`then ${usd(METERS.databaseDiskSize.price)} per GB`,
],
[
`${withUnit(METERS.egress.includedByPlan.pro, 'GB')} egress`,
`then ${usd(METERS.egress.price)} per GB`,
],
[
`${withUnit(METERS.cachedEgress.includedByPlan.pro, 'GB')} cached egress`,
`then ${usd(METERS.cachedEgress.price)} per GB`,
],
[
`${withUnit(METERS.storageSize.includedByPlan.pro, 'GB')} file storage`,
`then ${usd(METERS.storageSize.price)} per GB`,
],
'Email support',
'Daily backups stored for 7 days',
'7-day log retention',
['Add Log Drains', 'additional $60 per drain, per project'],
[
'Add Log Drains',
`additional ${usd(ADDONS.logDrain.priceMonthlyPerDrain)} per drain, per project`,
],
],
preface: 'Everything in the Free Plan, plus:',
cta: 'Get Started',
@@ -76,7 +99,7 @@ export const plans: PricingInformation[] = [
href: 'https://supabase.com/dashboard/new?plan=team',
priceLabel: 'From',
warning: 'Includes one project running on Micro compute.',
priceMonthly: 599,
priceMonthly: PLAN_BILLING.team.priceMonthly,
description: 'Add features such as SSO, control over backups, and industry certifications.',
features: [
'SOC2 & ISO 27001',
+9 -3
View File
@@ -53,18 +53,24 @@ export function qty(n: number, style?: 'comma' | 'millionWord'): string {
return String(n)
}
const THIN_SPACE = ' '
export function withUnit(n: number, unit: string, style?: 'comma' | 'millionWord'): string {
return `${qty(n, style)}${THIN_SPACE}${unit}`
}
export function gbDisplay(sizeGb: number): string {
return sizeGb < 1 ? `${sizeGb * 1000} MB` : `${sizeGb} GB`
return sizeGb < 1 ? withUnit(sizeGb * 1000, 'MB') : withUnit(sizeGb, 'GB')
}
const GP3_PER_GB_MONTH = 0.125
export const PLAN_BILLING: Record<PlanKey, PlanBillingEntry> = {
export const PLAN_BILLING = {
free: { priceMonthly: 0, computeCreditsMonthly: null, spendCapAvailable: false },
pro: { priceMonthly: 25, computeCreditsMonthly: 10, spendCapAvailable: true },
team: { priceMonthly: 599, computeCreditsMonthly: 10, spendCapAvailable: false },
enterprise: { priceMonthly: null, computeCreditsMonthly: null, spendCapAvailable: false },
}
} as const satisfies Record<PlanKey, PlanBillingEntry>
export const DISK_PRICING: Record<'gp3' | 'io2', DiskTypeEntry> = {
gp3: {
+131 -49
View File
@@ -1,3 +1,5 @@
import { ADDONS, gbDisplay, METERS, qty, usd, withUnit } from './pricing-catalog'
type Pricing = {
database: PricingCategory
auth: PricingCategory
@@ -122,9 +124,15 @@ export const pricing: Pricing = {
key: 'database.size',
title: 'Database size',
plans: {
free: '500 MB database size per project included',
pro: ['8 GB disk size per project included', 'then $0.125 per GB'],
team: ['8 GB disk size per project included', 'then $0.125 per GB'],
free: `${gbDisplay(METERS.databaseDiskSize.includedByPlan.free)} database size per project included`,
pro: [
`${gbDisplay(METERS.databaseDiskSize.includedByPlan.pro)} disk size per project included`,
`then ${usd(METERS.databaseDiskSize.price)} per GB`,
],
team: [
`${gbDisplay(METERS.databaseDiskSize.includedByPlan.team)} disk size per project included`,
`then ${usd(METERS.databaseDiskSize.price)} per GB`,
],
enterprise: 'Custom',
},
usage_based: true,
@@ -156,9 +164,9 @@ export const pricing: Pricing = {
title: 'Point in time recovery',
plans: {
free: false,
pro: '$100 per month per 7 days retention',
team: '$100 per month per 7 days retention',
enterprise: '$100 per month per 7 days retention, >28 days retention available',
pro: `${usd(ADDONS.pitr.priceMonthlyPer7DaysRetention)} per month per 7 days retention`,
team: `${usd(ADDONS.pitr.priceMonthlyPer7DaysRetention)} per month per 7 days retention`,
enterprise: `${usd(ADDONS.pitr.priceMonthlyPer7DaysRetention)} per month per 7 days retention, >28 days retention available`,
},
usage_based: false,
},
@@ -178,8 +186,8 @@ export const pricing: Pricing = {
title: 'Branching',
plans: {
free: false,
pro: '$0.01344 per branch, per hour',
team: '$0.01344 per branch, per hour',
pro: `${usd(METERS.branching.price)} per branch, per hour`,
team: `${usd(METERS.branching.price)} per branch, per hour`,
enterprise: 'Custom',
},
usage_based: true,
@@ -188,9 +196,15 @@ export const pricing: Pricing = {
key: 'database.egress',
title: 'Egress',
plans: {
free: '5 GB included',
pro: ['250 GB included', 'then $0.09 per GB'],
team: ['250 GB included', 'then $0.09 per GB'],
free: `${withUnit(METERS.egress.includedByPlan.free, 'GB')} included`,
pro: [
`${withUnit(METERS.egress.includedByPlan.pro, 'GB')} included`,
`then ${usd(METERS.egress.price)} per GB`,
],
team: [
`${withUnit(METERS.egress.includedByPlan.team, 'GB')} included`,
`then ${usd(METERS.egress.price)} per GB`,
],
enterprise: 'Custom',
},
usage_based: true,
@@ -201,14 +215,14 @@ export const pricing: Pricing = {
plans: {
free: false,
pro: [
'$0.053 per pipeline per hour',
'$3.00 per GB processed during ongoing replication',
'$0.60 per GB processed during initial sync',
`${usd(METERS.replicationPipelineHours.price)} per pipeline per hour`,
`${usd(METERS.replicationOngoingGb.price, 2)} per GB processed during ongoing replication`,
`${usd(METERS.replicationInitialSyncGb.price, 2)} per GB processed during initial sync`,
],
team: [
'$0.053 per pipeline per hour',
'$3.00 per GB processed during ongoing replication',
'$0.60 per GB processed during initial sync',
`${usd(METERS.replicationPipelineHours.price)} per pipeline per hour`,
`${usd(METERS.replicationOngoingGb.price, 2)} per GB processed during ongoing replication`,
`${usd(METERS.replicationInitialSyncGb.price, 2)} per GB processed during initial sync`,
],
enterprise: 'Custom',
},
@@ -235,9 +249,15 @@ export const pricing: Pricing = {
key: 'auth.maus',
title: 'MAUs',
plans: {
free: '50,000 included',
pro: ['100,000 included', 'then $0.00325 per MAU'],
team: ['100,000 included', 'then $0.00325 per MAU'],
free: `${qty(METERS.maus.includedByPlan.free, 'comma')} included`,
pro: [
`${qty(METERS.maus.includedByPlan.pro, 'comma')} included`,
`then ${usd(METERS.maus.price)} per MAU`,
],
team: [
`${qty(METERS.maus.includedByPlan.team, 'comma')} included`,
`then ${usd(METERS.maus.price)} per MAU`,
],
enterprise: 'Custom',
},
usage_based: true,
@@ -325,8 +345,14 @@ export const pricing: Pricing = {
title: 'Advanced Multi-Factor Auth - Phone',
plans: {
free: false,
pro: ['$75 per month for first project', 'then $10 per month per additional projects'],
team: ['$75 per month for first project', 'then $10 per month per additional projects'],
pro: [
`${usd(ADDONS.advancedMfaPhone.priceMonthlyFirstProject)} per month for first project`,
`then ${usd(ADDONS.advancedMfaPhone.priceMonthlyAdditionalProject)} per month per additional projects`,
],
team: [
`${usd(ADDONS.advancedMfaPhone.priceMonthlyFirstProject)} per month for first project`,
`then ${usd(ADDONS.advancedMfaPhone.priceMonthlyAdditionalProject)} per month per additional projects`,
],
enterprise: 'Custom',
},
usage_based: false,
@@ -335,9 +361,15 @@ export const pricing: Pricing = {
key: 'auth.thirdPartyMAUs',
title: 'Third-Party MAUs',
plans: {
free: '50,000 included',
pro: ['100,000 included', 'then $0.00325 per MAU'],
team: ['100,000 included', 'then $0.00325 per MAU'],
free: `${qty(METERS.thirdPartyMaus.includedByPlan.free, 'comma')} included`,
pro: [
`${qty(METERS.thirdPartyMaus.includedByPlan.pro, 'comma')} included`,
`then ${usd(METERS.thirdPartyMaus.price)} per MAU`,
],
team: [
`${qty(METERS.thirdPartyMaus.includedByPlan.team, 'comma')} included`,
`then ${usd(METERS.thirdPartyMaus.price)} per MAU`,
],
enterprise: 'Custom',
},
usage_based: true,
@@ -347,8 +379,14 @@ export const pricing: Pricing = {
title: 'Single Sign-On (SAML 2.0)',
plans: {
free: false,
pro: ['50 included', 'then $0.015 per MAU'],
team: ['50 included', 'then $0.015 per MAU'],
pro: [
`${qty(METERS.ssoMaus.includedByPlan.pro)} included`,
`then ${usd(METERS.ssoMaus.price)} per MAU`,
],
team: [
`${qty(METERS.ssoMaus.includedByPlan.team)} included`,
`then ${usd(METERS.ssoMaus.price)} per MAU`,
],
enterprise: 'Contact Us',
},
@@ -420,9 +458,15 @@ export const pricing: Pricing = {
key: 'storage.size',
title: 'Storage',
plans: {
free: '1 GB included',
pro: ['100 GB included', 'then $0.0213 per GB'],
team: ['100 GB included', 'then $0.0213 per GB'],
free: `${withUnit(METERS.storageSize.includedByPlan.free, 'GB')} included`,
pro: [
`${withUnit(METERS.storageSize.includedByPlan.pro, 'GB')} included`,
`then ${usd(METERS.storageSize.price)} per GB`,
],
team: [
`${withUnit(METERS.storageSize.includedByPlan.team, 'GB')} included`,
`then ${usd(METERS.storageSize.price)} per GB`,
],
enterprise: 'Custom',
},
usage_based: true,
@@ -431,9 +475,15 @@ export const pricing: Pricing = {
key: 'storage.cachedEgress',
title: 'Cached Egress',
plans: {
free: '5 GB included',
pro: ['250 GB included', 'then $0.03 per GB'],
team: ['250 GB included', 'then $0.03 per GB'],
free: `${withUnit(METERS.cachedEgress.includedByPlan.free, 'GB')} included`,
pro: [
`${withUnit(METERS.cachedEgress.includedByPlan.pro, 'GB')} included`,
`then ${usd(METERS.cachedEgress.price)} per GB`,
],
team: [
`${withUnit(METERS.cachedEgress.includedByPlan.team, 'GB')} included`,
`then ${usd(METERS.cachedEgress.price)} per GB`,
],
enterprise: 'Custom',
},
usage_based: true,
@@ -476,8 +526,14 @@ export const pricing: Pricing = {
title: 'Image Transformations',
plans: {
free: false,
pro: ['100 origin images included', 'then $5 per 1000 origin images'],
team: ['100 origin images included', 'then $5 per 1000 origin images'],
pro: [
`${qty(METERS.imageTransformations.includedByPlan.pro)} origin images included`,
`then ${usd(METERS.imageTransformations.price)} per ${qty(METERS.imageTransformations.per)} origin images`,
],
team: [
`${qty(METERS.imageTransformations.includedByPlan.team)} origin images included`,
`then ${usd(METERS.imageTransformations.price)} per ${qty(METERS.imageTransformations.per)} origin images`,
],
enterprise: 'Custom',
},
usage_based: true,
@@ -492,9 +548,15 @@ export const pricing: Pricing = {
key: 'functions.invocations',
title: 'Invocations',
plans: {
free: '500,000 included',
pro: ['2 Million included', 'then $2 per 1 Million'],
team: ['2 Million included', 'then $2 per 1 Million'],
free: `${qty(METERS.functionsInvocations.includedByPlan.free, 'comma')} included`,
pro: [
`${qty(METERS.functionsInvocations.includedByPlan.pro, 'millionWord')} included`,
`then ${usd(METERS.functionsInvocations.price)} per ${qty(METERS.functionsInvocations.per, 'millionWord')}`,
],
team: [
`${qty(METERS.functionsInvocations.includedByPlan.team, 'millionWord')} included`,
`then ${usd(METERS.functionsInvocations.price)} per ${qty(METERS.functionsInvocations.per, 'millionWord')}`,
],
enterprise: 'Custom',
},
usage_based: true,
@@ -520,9 +582,15 @@ export const pricing: Pricing = {
key: 'realtime.concurrentConnections',
title: 'Concurrent Peak Connections',
plans: {
free: '200 included',
pro: ['500 included', 'then $10 per 1000'],
team: ['500 included', 'then $10 per 1000'],
free: `${qty(METERS.realtimeConnections.includedByPlan.free)} included`,
pro: [
`${qty(METERS.realtimeConnections.includedByPlan.pro)} included`,
`then ${usd(METERS.realtimeConnections.price)} per ${qty(METERS.realtimeConnections.per)}`,
],
team: [
`${qty(METERS.realtimeConnections.includedByPlan.team)} included`,
`then ${usd(METERS.realtimeConnections.price)} per ${qty(METERS.realtimeConnections.per)}`,
],
enterprise: 'Custom concurrent connections and volume discount',
},
usage_based: true,
@@ -531,9 +599,15 @@ export const pricing: Pricing = {
key: 'realtime.messagesPerMonth',
title: 'Messages Per Month',
plans: {
free: '2 Million included',
pro: ['5 Million included', 'then $2.50 per Million'],
team: ['5 Million included', 'then $2.50 per Million'],
free: `${qty(METERS.realtimeMessages.includedByPlan.free, 'millionWord')} included`,
pro: [
`${qty(METERS.realtimeMessages.includedByPlan.pro, 'millionWord')} included`,
`then ${usd(METERS.realtimeMessages.price, 2)} per Million`,
],
team: [
`${qty(METERS.realtimeMessages.includedByPlan.team, 'millionWord')} included`,
`then ${usd(METERS.realtimeMessages.price, 2)} per Million`,
],
enterprise: 'Volume discounts on messages',
},
usage_based: true,
@@ -599,8 +673,16 @@ export const pricing: Pricing = {
title: 'Log Drain',
plans: {
free: false,
pro: ['$60 per drain per month', '+ $0.20 per million events', '+ $0.09 per GB egress'],
team: ['$60 per drain per month', '+ $0.20 per million events', '+ $0.09 per GB egress'],
pro: [
`${usd(ADDONS.logDrain.priceMonthlyPerDrain)} per drain per month`,
`+ ${usd(ADDONS.logDrain.perMillionEvents, 2)} per million events`,
`+ ${usd(ADDONS.logDrain.perGbEgress)} per GB egress`,
],
team: [
`${usd(ADDONS.logDrain.priceMonthlyPerDrain)} per drain per month`,
`+ ${usd(ADDONS.logDrain.perMillionEvents, 2)} per million events`,
`+ ${usd(ADDONS.logDrain.perGbEgress)} per GB egress`,
],
enterprise: 'Custom',
},
usage_based: true,
@@ -720,9 +802,9 @@ export const pricing: Pricing = {
title: 'Custom Domains',
plans: {
free: false,
pro: '$10 per domain per month per project add on',
team: '$10 per domain per month per project add on',
enterprise: '1, additional $10/domain/month',
pro: `${usd(ADDONS.customDomain.priceMonthlyPerDomain)} per domain per month per project add on`,
team: `${usd(ADDONS.customDomain.priceMonthlyPerDomain)} per domain per month per project add on`,
enterprise: `1, additional ${usd(ADDONS.customDomain.priceMonthlyPerDomain)}/domain/month`,
},
usage_based: false,
},