Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/revokslab/shipfree/llms.txt

Use this file to discover all available pages before exploring further.

ShipFree uses environment-based feature flags to enable/disable features and control application behavior. The feature flag system is defined in src/config/feature-flags.ts.

Environment Detection

ShipFree provides utilities to detect the current runtime environment:
import { 
  isProd, 
  isDev, 
  isTest 
} from '@/config/feature-flags'

if (isProd) {
  // Production-only code
  enableAnalytics()
}

if (isDev) {
  // Development-only code
  enableDebugMode()
}

Available Environment Flags

isProd
boolean
Returns true when NODE_ENV === 'production'
export const isProd = env.NODE_ENV === 'production'
isDev
boolean
Returns true when NODE_ENV === 'development'
export const isDev = env.NODE_ENV === 'development'
isTest
boolean
Returns true when NODE_ENV === 'test'
export const isTest = env.NODE_ENV === 'test'

Core Feature Flags

These flags control major application features.

Billing

isBillingEnabled
boolean
default:"false"
Controls whether billing and subscription features are enforced.Set via environment variable:
BILLING_ENABLED=true
Implementation:
export const isBillingEnabled = isTruthy(env.BILLING_ENABLED)
Usage:
import { isBillingEnabled } from '@/config/feature-flags'

export default function DashboardPage() {
  if (isBillingEnabled) {
    // Show subscription status
    // Enforce plan limits
    // Display upgrade prompts
  }
  
  return <Dashboard />
}

Email Verification

isEmailVerificationEnabled
boolean
default:"false"
Controls whether new users must verify their email address.Set via environment variable:
EMAIL_VERIFICATION_ENABLED=true
Implementation:
export const isEmailVerificationEnabled = isTruthy(env.EMAIL_VERIFICATION_ENABLED)
Usage:
import { isEmailVerificationEnabled } from '@/config/feature-flags'

export async function registerUser(email: string, password: string) {
  const user = await createUser(email, password)
  
  if (isEmailVerificationEnabled) {
    await sendVerificationEmail(user)
    return { requiresVerification: true }
  }
  
  return { requiresVerification: false }
}

Payment Provider Detection

These flags detect which payment providers are configured.

Stripe

isStripeConfigured
boolean
Returns true if Stripe credentials are configured.Implementation:
export const isStripeConfigured = Boolean(
  env.STRIPE_SECRET_KEY && env.STRIPE_WEBHOOK_SECRET
)
Required environment variables:
  • STRIPE_SECRET_KEY
  • STRIPE_WEBHOOK_SECRET

Polar

isPolarConfigured
boolean
Returns true if Polar credentials are configured.Implementation:
export const isPolarConfigured = Boolean(env.POLAR_ACCESS_TOKEN)
Required environment variables:
  • POLAR_ACCESS_TOKEN

Any Provider

hasBillingProvider
boolean
Returns true if any payment provider is configured.Implementation:
export const hasBillingProvider = isStripeConfigured || isPolarConfigured
Usage:
import { 
  hasBillingProvider,
  isStripeConfigured,
  isPolarConfigured 
} from '@/config/feature-flags'

export function PaymentSettings() {
  if (!hasBillingProvider) {
    return <EmptyState message="No payment provider configured" />
  }
  
  return (
    <div>
      {isStripeConfigured && <StripeSettings />}
      {isPolarConfigured && <PolarSettings />}
    </div>
  )
}

Helper Functions

ShipFree provides utility functions for working with boolean environment variables.

isTruthy

isTruthy
function
Converts string or boolean values to boolean.Returns true for:
  • "true" (case-insensitive)
  • "1"
  • true
  • 1
Implementation:
export const isTruthy = (value: string | boolean | number | undefined) =>
  typeof value === 'string' 
    ? value.toLowerCase() === 'true' || value === '1' 
    : Boolean(value)
Usage:
import { isTruthy } from '@/config/env'

const debugMode = isTruthy(process.env.DEBUG_MODE)
const featureX = isTruthy(process.env.FEATURE_X_ENABLED)

isFalsy

isFalsy
function
Checks if a value is explicitly false.Returns true for:
  • "false" (case-insensitive)
  • "0"
  • false
Implementation:
export const isFalsy = (value: string | boolean | number | undefined) =>
  typeof value === 'string' 
    ? value.toLowerCase() === 'false' || value === '0' 
    : value === false
Usage:
import { isFalsy } from '@/config/env'

const analytics = process.env.ANALYTICS_ENABLED

if (!isFalsy(analytics)) {
  // Analytics is enabled or not set
  initializeAnalytics()
}

Adding Custom Feature Flags

You can extend the feature flag system with your own flags.

Step 1: Add Environment Variable

Add to .env.example and .env:
# Enable experimental AI features
AI_FEATURES_ENABLED=false

# Enable beta API endpoints
BETA_API_ENABLED=false

Step 2: Validate in env.ts

Add to src/config/env.ts:
export const env = createEnv({
  server: {
    // ... existing variables
    AI_FEATURES_ENABLED: z.boolean().default(false),
    BETA_API_ENABLED: z.boolean().default(false),
  },
  // ...
  runtimeEnv: {
    // ... existing mappings
    AI_FEATURES_ENABLED: process.env.AI_FEATURES_ENABLED,
    BETA_API_ENABLED: process.env.BETA_API_ENABLED,
  },
})

Step 3: Create Feature Flag

Add to src/config/feature-flags.ts:
import { env, isTruthy } from './env'

// ... existing flags

/**
 * Enable AI-powered features (experimental)
 */
export const isAiFeaturesEnabled = isTruthy(env.AI_FEATURES_ENABLED)

/**
 * Enable beta API endpoints
 */
export const isBetaApiEnabled = isTruthy(env.BETA_API_ENABLED)

Step 4: Use in Your Code

import { isAiFeaturesEnabled } from '@/config/feature-flags'

export default function DashboardPage() {
  return (
    <div>
      <Dashboard />
      
      {isAiFeaturesEnabled && (
        <AIAssistant />
      )}
    </div>
  )
}

Advanced Patterns

Conditional API Routes

Protect beta API endpoints:
import { isBetaApiEnabled } from '@/config/feature-flags'
import { NextResponse } from 'next/server'

export async function GET(request: Request) {
  if (!isBetaApiEnabled) {
    return NextResponse.json(
      { error: 'Beta API not enabled' },
      { status: 403 }
    )
  }
  
  // Beta API logic
  return NextResponse.json({ data: 'beta feature' })
}

Feature Gating with Multiple Flags

Combine multiple flags for complex gating:
import { 
  isProd, 
  isBillingEnabled,
  isStripeConfigured 
} from '@/config/feature-flags'

export function SubscriptionButton() {
  const canShowSubscriptions = 
    isBillingEnabled && 
    isStripeConfigured && 
    !isProd // Hide in production for now
  
  if (!canShowSubscriptions) {
    return null
  }
  
  return <button>Upgrade Now</button>
}

Environment-Specific Behavior

import { isProd, isDev } from '@/config/feature-flags'

export function initializeApp() {
  if (isDev) {
    // Development: Use mock data
    useMockApi()
    enableDevTools()
  }
  
  if (isProd) {
    // Production: Use real services
    initializeSentry()
    initializeAnalytics()
  }
}

Server vs Client Feature Flags

For client-accessible flags, use NEXT_PUBLIC_ prefix:
# Server-only flag
ADMIN_FEATURES_ENABLED=true

# Client-accessible flag
NEXT_PUBLIC_BETA_UI_ENABLED=true
// src/config/env.ts
export const env = createEnv({
  server: {
    ADMIN_FEATURES_ENABLED: z.boolean().default(false),
  },
  client: {
    NEXT_PUBLIC_BETA_UI_ENABLED: z.boolean().default(false),
  },
  // ...
})
// src/config/feature-flags.ts
export const isAdminFeaturesEnabled = isTruthy(env.ADMIN_FEATURES_ENABLED)
export const isBetaUiEnabled = isTruthy(env.NEXT_PUBLIC_BETA_UI_ENABLED)
'use client'

import { isBetaUiEnabled } from '@/config/feature-flags'

export function Header() {
  return (
    <header>
      <Logo />
      {isBetaUiEnabled && <NewNavigation />}
    </header>
  )
}

Best Practices

1. Explicit Defaults

Always provide explicit default values:
// ✅ Good: Explicit default
NEW_FEATURE_ENABLED: z.boolean().default(false)

// ❌ Bad: No default
NEW_FEATURE_ENABLED: z.boolean()

2. Descriptive Names

Use clear, descriptive flag names:
// ✅ Good: Clear intent
isEmailVerificationEnabled
isStripeConfigured
isBillingEnabled

// ❌ Bad: Vague names
enabled
configured
active

3. Documentation

Document flags in both code and .env.example:
/**
 * Enable AI-powered content suggestions
 * Requires OpenAI API key to be configured
 */
export const isAiFeaturesEnabled = isTruthy(env.AI_FEATURES_ENABLED)
# .env.example
# Enable AI-powered content suggestions (requires OPENAI_API_KEY)
AI_FEATURES_ENABLED=false

4. Gradual Rollout

Use feature flags for gradual rollouts:
# .env.development
NEW_DASHBOARD_ENABLED=true

# .env.production
NEW_DASHBOARD_ENABLED=false

5. Cleanup Old Flags

Remove feature flags after features are stable:
  1. Enable feature in production
  2. Monitor for issues
  3. If stable, remove flag and make feature permanent
  4. Clean up flag from all environments

Testing with Feature Flags

Test features in isolation:
import { isAiFeaturesEnabled } from '@/config/feature-flags'

// Mock the flag for tests
jest.mock('@/config/feature-flags', () => ({
  isAiFeaturesEnabled: true,
}))

test('AI features render when enabled', () => {
  render(<Dashboard />)
  expect(screen.getByText('AI Assistant')).toBeInTheDocument()
})

Migration Guide

When deprecating a feature flag:
  1. Announce deprecation in release notes
  2. Set default to final state (e.g., true for enabled features)
  3. Remove conditional logic and make feature permanent
  4. Remove from env.ts and feature-flags.ts
  5. Update documentation
Example:
// Before: Feature flag
if (isNewDashboardEnabled) {
  return <NewDashboard />
}
return <OldDashboard />

// After: Feature permanent
return <NewDashboard />

Build docs developers (and LLMs) love