diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats-overview/team-seats-overview.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats-overview/team-seats-overview.tsx
index 9ad2e4528..2c077ea04 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats-overview/team-seats-overview.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats-overview/team-seats-overview.tsx
@@ -2,7 +2,9 @@ import { Building2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Progress } from '@/components/ui/progress'
import { Skeleton } from '@/components/ui/skeleton'
-import { checkEnterprisePlan, getTeamTierLimitPerSeat } from '@/lib/billing/subscriptions/utils'
+import { DEFAULT_TEAM_TIER_COST_LIMIT } from '@/lib/billing/constants'
+import { checkEnterprisePlan } from '@/lib/billing/subscriptions/utils'
+import { env } from '@/lib/env'
type Subscription = {
id: string
@@ -100,7 +102,7 @@ export function TeamSeatsOverview({
Seats
{!checkEnterprisePlan(subscriptionData) ? (
- (${getTeamTierLimitPerSeat()}/month each)
+ (${env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT}/month each)
) : null}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats/team-seats.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats/team-seats.tsx
index 23bc3c6df..96cef39c4 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats/team-seats.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-seats/team-seats.tsx
@@ -17,7 +17,8 @@ import {
SelectValue,
} from '@/components/ui/select'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
-import { getTeamTierLimitPerSeat } from '@/lib/billing/subscriptions/utils'
+import { DEFAULT_TEAM_TIER_COST_LIMIT } from '@/lib/billing/constants'
+import { env } from '@/lib/env'
interface TeamSeatsProps {
open: boolean
@@ -54,7 +55,7 @@ export function TeamSeats({
}
}, [open, initialSeats])
- const costPerSeat = getTeamTierLimitPerSeat()
+ const costPerSeat = env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT
const totalMonthlyCost = selectedSeats * costPerSeat
const costChange = currentSeats ? (selectedSeats - currentSeats) * costPerSeat : 0
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx
index b9a6be82c..0e8793f83 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx
@@ -1,7 +1,9 @@
import { useCallback, useEffect, useState } from 'react'
import { Alert, AlertDescription, AlertTitle, Skeleton } from '@/components/ui'
import { useSession } from '@/lib/auth-client'
-import { checkEnterprisePlan, getTeamTierLimitPerSeat } from '@/lib/billing/subscriptions/utils'
+import { DEFAULT_TEAM_TIER_COST_LIMIT } from '@/lib/billing/constants'
+import { checkEnterprisePlan } from '@/lib/billing/subscriptions/utils'
+import { env } from '@/lib/env'
import { createLogger } from '@/lib/logs/console/logger'
import {
MemberInvitationCard,
@@ -293,7 +295,8 @@ export function TeamManagement() {
-
Your team is billed a minimum of $
- {(subscriptionData?.seats || 0) * getTeamTierLimitPerSeat()}
+ {(subscriptionData?.seats || 0) *
+ (env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT)}
/month for {subscriptionData?.seats || 0} licensed seats
- All team member usage is pooled together from a shared limit
@@ -411,7 +414,7 @@ export function TeamManagement() {
open={isAddSeatDialogOpen}
onOpenChange={setIsAddSeatDialogOpen}
title='Add Team Seats'
- description={`Each seat costs $${getTeamTierLimitPerSeat()}/month and provides $${getTeamTierLimitPerSeat()} in monthly inference credits. Adjust the number of licensed seats for your team.`}
+ description={`Each seat costs $${env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT}/month and provides $${env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT} in monthly inference credits. Adjust the number of licensed seats for your team.`}
currentSeats={subscriptionData?.seats || 1}
initialSeats={newSeatCount}
isLoading={isUpdatingSeats}
diff --git a/apps/sim/hooks/use-subscription-state.ts b/apps/sim/hooks/use-subscription-state.ts
index fc37ac9db..6ef937d14 100644
--- a/apps/sim/hooks/use-subscription-state.ts
+++ b/apps/sim/hooks/use-subscription-state.ts
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from 'react'
-import { getFreeTierLimit } from '@/lib/billing/subscriptions/utils'
+import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('useSubscriptionState')
@@ -82,7 +82,7 @@ export function useSubscriptionState() {
usage: {
current: data?.usage?.current ?? 0,
- limit: data?.usage?.limit ?? getFreeTierLimit(),
+ limit: data?.usage?.limit ?? DEFAULT_FREE_CREDITS,
percentUsed: data?.usage?.percentUsed ?? 0,
isWarning: data?.usage?.isWarning ?? false,
isExceeded: data?.usage?.isExceeded ?? false,
@@ -203,9 +203,9 @@ export function useUsageLimit() {
}
return {
- currentLimit: data?.currentLimit ?? getFreeTierLimit(),
+ currentLimit: data?.currentLimit ?? DEFAULT_FREE_CREDITS,
canEdit: data?.canEdit ?? false,
- minimumLimit: data?.minimumLimit ?? getFreeTierLimit(),
+ minimumLimit: data?.minimumLimit ?? DEFAULT_FREE_CREDITS,
plan: data?.plan ?? 'free',
setBy: data?.setBy,
updatedAt: data?.updatedAt ? new Date(data.updatedAt) : null,
diff --git a/apps/sim/lib/billing/constants.ts b/apps/sim/lib/billing/constants.ts
index 050cacfaf..31eb2622b 100644
--- a/apps/sim/lib/billing/constants.ts
+++ b/apps/sim/lib/billing/constants.ts
@@ -1,5 +1,27 @@
+/**
+ * Billing and cost constants shared between client and server code
+ */
+
+/**
+ * Fallback free credits (in dollars) when env var is not set
+ */
+export const DEFAULT_FREE_CREDITS = 10
+
+/**
+ * Default per-user minimum limits (in dollars) for paid plans when env vars are absent
+ */
+export const DEFAULT_PRO_TIER_COST_LIMIT = 20
+export const DEFAULT_TEAM_TIER_COST_LIMIT = 40
+export const DEFAULT_ENTERPRISE_TIER_COST_LIMIT = 200
+
/**
* Base charge applied to every workflow execution
* This charge is applied regardless of whether the workflow uses AI models
*/
export const BASE_EXECUTION_CHARGE = 0.001
+
+/**
+ * Default threshold (in dollars) for incremental overage billing
+ * When unbilled overage reaches this amount, an invoice item is created
+ */
+export const DEFAULT_OVERAGE_THRESHOLD = 50
diff --git a/apps/sim/lib/billing/storage/limits.ts b/apps/sim/lib/billing/storage/limits.ts
index 5afb6ca55..fd7a23455 100644
--- a/apps/sim/lib/billing/storage/limits.ts
+++ b/apps/sim/lib/billing/storage/limits.ts
@@ -4,9 +4,15 @@
*/
import { db } from '@sim/db'
+import {
+ DEFAULT_ENTERPRISE_STORAGE_LIMIT_GB,
+ DEFAULT_FREE_STORAGE_LIMIT_GB,
+ DEFAULT_PRO_STORAGE_LIMIT_GB,
+ DEFAULT_TEAM_STORAGE_LIMIT_GB,
+} from '@sim/db/consts'
import { organization, subscription, userStats } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
-import { env } from '@/lib/env'
+import { getEnv } from '@/lib/env'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('StorageLimits')
@@ -19,16 +25,25 @@ function gbToBytes(gb: number): number {
}
/**
- * Get storage limits from environment variables
+ * Get storage limits from environment variables with fallback to constants
* Returns limits in bytes
- * Defaults are defined in env.ts and will be applied automatically
*/
export function getStorageLimits() {
return {
- free: gbToBytes(env.FREE_STORAGE_LIMIT_GB),
- pro: gbToBytes(env.PRO_STORAGE_LIMIT_GB),
- team: gbToBytes(env.TEAM_STORAGE_LIMIT_GB),
- enterpriseDefault: gbToBytes(env.ENTERPRISE_STORAGE_LIMIT_GB),
+ free: gbToBytes(
+ Number.parseInt(getEnv('FREE_STORAGE_LIMIT_GB') || String(DEFAULT_FREE_STORAGE_LIMIT_GB))
+ ),
+ pro: gbToBytes(
+ Number.parseInt(getEnv('PRO_STORAGE_LIMIT_GB') || String(DEFAULT_PRO_STORAGE_LIMIT_GB))
+ ),
+ team: gbToBytes(
+ Number.parseInt(getEnv('TEAM_STORAGE_LIMIT_GB') || String(DEFAULT_TEAM_STORAGE_LIMIT_GB))
+ ),
+ enterpriseDefault: gbToBytes(
+ Number.parseInt(
+ getEnv('ENTERPRISE_STORAGE_LIMIT_GB') || String(DEFAULT_ENTERPRISE_STORAGE_LIMIT_GB)
+ )
+ ),
}
}
@@ -63,6 +78,7 @@ export function getStorageLimitForPlan(plan: string, metadata?: any): number {
*/
export async function getUserStorageLimit(userId: string): Promise {
try {
+ // Check if user is in a team/enterprise org
const { getHighestPrioritySubscription } = await import('@/lib/billing/core/subscription')
const sub = await getHighestPrioritySubscription(userId)
@@ -76,7 +92,9 @@ export async function getUserStorageLimit(userId: string): Promise {
return limits.pro
}
+ // Team/Enterprise: Use organization limit
if (sub.plan === 'team' || sub.plan === 'enterprise') {
+ // Get organization storage limit
const orgRecord = await db
.select({ metadata: subscription.metadata })
.from(subscription)
@@ -90,6 +108,7 @@ export async function getUserStorageLimit(userId: string): Promise {
}
}
+ // Default for team/enterprise
return sub.plan === 'enterprise' ? limits.enterpriseDefault : limits.team
}
@@ -106,10 +125,12 @@ export async function getUserStorageLimit(userId: string): Promise {
*/
export async function getUserStorageUsage(userId: string): Promise {
try {
+ // Check if user is in a team/enterprise org
const { getHighestPrioritySubscription } = await import('@/lib/billing/core/subscription')
const sub = await getHighestPrioritySubscription(userId)
if (sub && (sub.plan === 'team' || sub.plan === 'enterprise')) {
+ // Use organization storage
const orgRecord = await db
.select({ storageUsedBytes: organization.storageUsedBytes })
.from(organization)
@@ -119,6 +140,7 @@ export async function getUserStorageUsage(userId: string): Promise {
return orgRecord.length > 0 ? orgRecord[0].storageUsedBytes || 0 : 0
}
+ // Free/Pro: Use user stats
const stats = await db
.select({ storageUsedBytes: userStats.storageUsedBytes })
.from(userStats)
diff --git a/apps/sim/lib/billing/subscriptions/utils.ts b/apps/sim/lib/billing/subscriptions/utils.ts
index 64f7aac6a..baabb5e42 100644
--- a/apps/sim/lib/billing/subscriptions/utils.ts
+++ b/apps/sim/lib/billing/subscriptions/utils.ts
@@ -1,31 +1,37 @@
+import {
+ DEFAULT_ENTERPRISE_TIER_COST_LIMIT,
+ DEFAULT_FREE_CREDITS,
+ DEFAULT_PRO_TIER_COST_LIMIT,
+ DEFAULT_TEAM_TIER_COST_LIMIT,
+} from '@/lib/billing/constants'
import { env } from '@/lib/env'
/**
- * Get the free tier limit
+ * Get the free tier limit from env or fallback to default
*/
export function getFreeTierLimit(): number {
- return env.FREE_TIER_COST_LIMIT
+ return env.FREE_TIER_COST_LIMIT || DEFAULT_FREE_CREDITS
}
/**
- * Get the pro tier limit
+ * Get the pro tier limit from env or fallback to default
*/
export function getProTierLimit(): number {
- return env.PRO_TIER_COST_LIMIT
+ return env.PRO_TIER_COST_LIMIT || DEFAULT_PRO_TIER_COST_LIMIT
}
/**
- * Get the team tier limit per seat
+ * Get the team tier limit per seat from env or fallback to default
*/
export function getTeamTierLimitPerSeat(): number {
- return env.TEAM_TIER_COST_LIMIT
+ return env.TEAM_TIER_COST_LIMIT || DEFAULT_TEAM_TIER_COST_LIMIT
}
/**
- * Get the enterprise tier limit per seat
+ * Get the enterprise tier limit per seat from env or fallback to default
*/
export function getEnterpriseTierLimitPerSeat(): number {
- return env.ENTERPRISE_TIER_COST_LIMIT
+ return env.ENTERPRISE_TIER_COST_LIMIT || DEFAULT_ENTERPRISE_TIER_COST_LIMIT
}
export function checkEnterprisePlan(subscription: any): boolean {
diff --git a/apps/sim/lib/billing/threshold-billing.ts b/apps/sim/lib/billing/threshold-billing.ts
index cd4d9a581..28ca37055 100644
--- a/apps/sim/lib/billing/threshold-billing.ts
+++ b/apps/sim/lib/billing/threshold-billing.ts
@@ -2,6 +2,7 @@ import { db } from '@sim/db'
import { member, subscription, userStats } from '@sim/db/schema'
import { and, eq, inArray, sql } from 'drizzle-orm'
import type Stripe from 'stripe'
+import { DEFAULT_OVERAGE_THRESHOLD } from '@/lib/billing/constants'
import { calculateSubscriptionOverage, getPlanPricing } from '@/lib/billing/core/billing'
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
import { requireStripeClient } from '@/lib/billing/stripe-client'
@@ -10,7 +11,7 @@ import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('ThresholdBilling')
-const OVERAGE_THRESHOLD = env.OVERAGE_THRESHOLD_DOLLARS
+const OVERAGE_THRESHOLD = env.OVERAGE_THRESHOLD_DOLLARS || DEFAULT_OVERAGE_THRESHOLD
function parseDecimal(value: string | number | null | undefined): number {
if (value === null || value === undefined) return 0
diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts
index f227acb78..6820a5832 100644
--- a/apps/sim/lib/env.ts
+++ b/apps/sim/lib/env.ts
@@ -41,16 +41,16 @@ export const env = createEnv({
STRIPE_SECRET_KEY: z.string().min(1).optional(), // Stripe secret key for payment processing
STRIPE_WEBHOOK_SECRET: z.string().min(1).optional(), // General Stripe webhook secret
STRIPE_FREE_PRICE_ID: z.string().min(1).optional(), // Stripe price ID for free tier
- FREE_TIER_COST_LIMIT: z.number().optional().default(10), // Cost limit for free tier users (in dollars)
+ FREE_TIER_COST_LIMIT: z.number().optional(), // Cost limit for free tier users
FREE_STORAGE_LIMIT_GB: z.number().optional().default(5), // Storage limit in GB for free tier users
STRIPE_PRO_PRICE_ID: z.string().min(1).optional(), // Stripe price ID for pro tier
- PRO_TIER_COST_LIMIT: z.number().optional().default(20), // Cost limit for pro tier users (in dollars)
+ PRO_TIER_COST_LIMIT: z.number().optional(), // Cost limit for pro tier users
PRO_STORAGE_LIMIT_GB: z.number().optional().default(50), // Storage limit in GB for pro tier users
STRIPE_TEAM_PRICE_ID: z.string().min(1).optional(), // Stripe price ID for team tier
- TEAM_TIER_COST_LIMIT: z.number().optional().default(40), // Cost limit per seat for team tier (in dollars)
+ TEAM_TIER_COST_LIMIT: z.number().optional(), // Cost limit for team tier users
TEAM_STORAGE_LIMIT_GB: z.number().optional().default(500), // Storage limit in GB for team tier organizations (pooled)
STRIPE_ENTERPRISE_PRICE_ID: z.string().min(1).optional(), // Stripe price ID for enterprise tier
- ENTERPRISE_TIER_COST_LIMIT: z.number().optional().default(200), // Cost limit per seat for enterprise tier (in dollars)
+ ENTERPRISE_TIER_COST_LIMIT: z.number().optional(), // Cost limit for enterprise tier users
ENTERPRISE_STORAGE_LIMIT_GB: z.number().optional().default(500), // Default storage limit in GB for enterprise tier (can be overridden per org)
BILLING_ENABLED: z.boolean().optional(), // Enable billing enforcement and usage tracking
OVERAGE_THRESHOLD_DOLLARS: z.number().optional().default(50), // Dollar threshold for incremental overage billing (default: $50)
diff --git a/apps/sim/stores/subscription/store.ts b/apps/sim/stores/subscription/store.ts
index 36c505684..40ef9ac90 100644
--- a/apps/sim/stores/subscription/store.ts
+++ b/apps/sim/stores/subscription/store.ts
@@ -1,6 +1,6 @@
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
-import { getFreeTierLimit } from '@/lib/billing/subscriptions/utils'
+import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
import { createLogger } from '@/lib/logs/console/logger'
import type {
BillingStatus,
@@ -16,7 +16,7 @@ const CACHE_DURATION = 30 * 1000
const defaultUsage: UsageData = {
current: 0,
- limit: getFreeTierLimit(),
+ limit: DEFAULT_FREE_CREDITS,
percentUsed: 0,
isWarning: false,
isExceeded: false,
diff --git a/packages/db/consts.ts b/packages/db/consts.ts
index 81c64a630..39bd76a57 100644
--- a/packages/db/consts.ts
+++ b/packages/db/consts.ts
@@ -8,6 +8,15 @@
*/
export const DEFAULT_FREE_CREDITS = 10
+/**
+ * Storage limit constants (in GB)
+ * Can be overridden via environment variables
+ */
+export const DEFAULT_FREE_STORAGE_LIMIT_GB = 5
+export const DEFAULT_PRO_STORAGE_LIMIT_GB = 50
+export const DEFAULT_TEAM_STORAGE_LIMIT_GB = 500
+export const DEFAULT_ENTERPRISE_STORAGE_LIMIT_GB = 500
+
/**
* Tag slots available for knowledge base documents and embeddings
*/