Skip to content
GitHubTwitterDiscord

Application Configuration

LaunchKit uses a centralized configuration system to manage app settings, branding, integrations, and behavior. This guide covers all configuration options and customization possibilities.

The main configuration file is located at /config.ts and includes:

  • App branding (name, description, domain)
  • Payment settings (currency, Stripe configuration)
  • Third-party integrations (Crisp, AWS, social links)
  • Authentication flows (login/callback URLs)
  • Theme and UI preferences
const config = {
  // REQUIRED: Your app's name (used throughout the UI)
  appName: 'LaunchKit Project',
  
  // REQUIRED: Short description for SEO and metadata
  appDescription: 'The NextJS boilerplate with all you need to build your SaaS, AI tool, or any other web app.',
  
  // REQUIRED: Your domain (no https://, no trailing slash)
  domainName: 'localhost:3000', // Development
  // domainName: 'yourawesomeapp.com', // Production
};

Usage in Components:

import config from '@/config';

// Page title
<title>{config.appName} - Dashboard</title>

// Meta description
<meta name="description" content={config.appDescription} />

// Redirect URLs
const redirectUrl = `https://${config.domainName}/dashboard`;
const config = {
  crisp: {
    // Crisp website ID from your Crisp dashboard
    id: 'your-crisp-website-id',
    
    // Show Crisp only on specific routes
    onlyShowOnRoutes: ['/'],
    
    // If empty array, shows on all routes:
    // onlyShowOnRoutes: [],
  },
  
  resend: {
    // Support email (used when Crisp is not available)
    supportEmail: '[email protected]',
  },
};

How it works:

  • If crisp.id is provided, the chat widget appears on specified routes
  • If no Crisp ID, the support system falls back to email
  • The ButtonSupport component automatically handles the integration
const config = {
  // Currency configuration
  currency: {
    // ISO 4217 currency code
    code: 'USD', // 'EUR', 'GBP', 'CAD', etc.
    
    // Currency symbol
    symbol: '$', // '€', '£', 'C$', etc.
    
    // Symbol position relative to amount
    symbolPosition: 'before' as const, // 'before' or 'after'
  },
  
  stripe: {
    // Products are now managed through the database
    // This section is kept for future Stripe configuration
    // Use /dashboard/admin/products to manage products
  },
};

Usage in Components:

import config from '@/config';

// Format currency
const formatPrice = (amount: number) => {
  const { symbol, symbolPosition } = config.currency;
  const formatted = amount.toFixed(2);
  
  return symbolPosition === 'before' 
    ? `${symbol}${formatted}`
    : `${formatted}${symbol}`;
};

// Example: formatPrice(99) → "$99.00"
const config = {
  resend: {
    // Email 'From' field for automated emails
    fromNoReply: `${config.appName} <[email protected]>`,
    
    // Email 'From' field for personal emails
    fromAdmin: `Your Name at ${config.appName} <[email protected]>`,
    
    // Support email (shown to customers)
    supportEmail: '[email protected]',
  },
};

Email Template Usage:

// Emails automatically use these settings
await EmailService.sendPurchaseConfirmation({
  // ... email data
  // fromAdmin and supportEmail are automatically included
});
const config = {
  auth: {
    // Login page URL
    loginUrl: '/signin',
    
    // Redirect after successful login
    callbackUrl: '/dashboard',
  },
};

Usage in Middleware:

// Redirect unauthenticated users
if (!user && isProtectedRoute) {
  return NextResponse.redirect(new URL(config.auth.loginUrl, request.url));
}

// Redirect after successful auth
return NextResponse.redirect(new URL(config.auth.callbackUrl, request.url));
const config = {
  theme: {
    // Default theme when app loads
    defaultTheme: 'system', // 'light', 'dark', or 'system'
    
    // Enable system theme detection
    enableSystem: true,
    
    // Disable transition animation on theme change
    disableTransitionOnChange: true,
    
    // Available themes (must match your CSS themes)
    themes: ['light', 'dark', 'system'],
  },
};

Theme Provider Usage:

// The theme provider automatically uses these settings
<ThemeProvider
  defaultTheme={config.theme.defaultTheme}
  enableSystem={config.theme.enableSystem}
  disableTransitionOnChange={config.theme.disableTransitionOnChange}
  themes={config.theme.themes}
>
  {children}
</ThemeProvider>
const config = {
  social: {
    // Discord server invite
    discord: 'https://discord.gg/yourserver',
    
    // Twitter/X profile
    twitter: 'https://twitter.com/yourusername',
    
    // GitHub organization/profile
    github: 'https://github.com/yourusername',
    
    // LinkedIn profile/company
    linkedin: 'https://linkedin.com/company/yourcompany',
  },
};

Footer Component Usage:

import config from '@/config';

const Footer = () => (
  <footer>
    <div className="social-links">
      {config.social.twitter && (
        <a href={config.social.twitter} target="_blank">
          Twitter
        </a>
      )}
      {config.social.github && (
        <a href={config.social.github} target="_blank">
          GitHub
        </a>
      )}
      {/* More social links */}
    </div>
  </footer>
);
const config = {
  aws: {
    // S3 bucket name
    bucket: 'your-bucket-name',
    
    // S3 bucket URL
    bucketUrl: 'https://your-bucket-name.s3.amazonaws.com/',
    
    // CloudFront CDN URL (optional)
    cdn: 'https://d1234567890.cloudfront.net/',
  },
};

File Upload Usage:

const uploadToS3 = async (file: File) => {
  const uploadUrl = config.aws.cdn || config.aws.bucketUrl;
  // Upload logic using the configured URLs
};
// config.ts
const isDevelopment = process.env.NODE_ENV === 'development';

const config = {
  appName: 'LaunchKit Project',
  
  // Different domains for different environments
  domainName: isDevelopment 
    ? 'localhost:3000' 
    : 'yourapp.com',
    
  // Different support settings
  crisp: {
    id: isDevelopment 
      ? '' // Disable in development
      : 'your-production-crisp-id',
    onlyShowOnRoutes: ['/'],
  },
  
  // Development-specific settings
  ...(isDevelopment && {
    // Enable debugging features
    debug: true,
    // Show development tools
    showDevTools: true,
  }),
};
const config = {
  features: {
    // Enable/disable features
    enableBlog: true,
    enableChat: true,
    enableAnalytics: process.env.NODE_ENV === 'production',
    enableEmailCapture: true,
    
    // Beta features
    betaFeatures: {
      newDashboard: false,
      advancedAnalytics: false,
    },
  },
};

Component Usage:

import config from '@/config';

const DashboardPage = () => {
  return (
    <div>
      {config.features.enableAnalytics && <AnalyticsWidget />}
      {config.features.betaFeatures.newDashboard && <NewDashboard />}
      <StandardDashboard />
    </div>
  );
};
// For SaaS platforms with multiple customers
const config = {
  multiTenant: {
    enabled: true,
    subdomainRouting: true, // customer.yourapp.com
    customDomains: true,    // customer-domain.com
  },
  
  // Tenant-specific overrides
  getTenantConfig: (tenantId: string) => ({
    appName: `${config.appName} - ${tenantId}`,
    branding: {
      primaryColor: getTenantColor(tenantId),
      logo: getTenantLogo(tenantId),
    },
  }),
};
const config = {
  experiments: {
    // Pricing page variants
    pricingPageVariant: {
      enabled: true,
      variants: ['original', 'simplified', 'premium-focused'],
      traffic: [50, 25, 25], // Percentage split
    },
    
    // CTA button tests
    ctaButtonText: {
      enabled: true,
      variants: ['Get Started', 'Start Free Trial', 'Try Now'],
      traffic: [33, 33, 34],
    },
  },
};
const config = {
  integrations: {
    analytics: {
      google: {
        enabled: !!process.env.GOOGLE_ANALYTICS_ID,
        id: process.env.GOOGLE_ANALYTICS_ID,
      },
      plausible: {
        enabled: !!process.env.PLAUSIBLE_DOMAIN,
        domain: process.env.PLAUSIBLE_DOMAIN,
      },
    },
    
    monitoring: {
      sentry: {
        enabled: !!process.env.SENTRY_DSN,
        dsn: process.env.SENTRY_DSN,
      },
      logRocket: {
        enabled: !!process.env.LOGROCKET_APP_ID,
        appId: process.env.LOGROCKET_APP_ID,
      },
    },
  },
};
// types/config.ts
export interface ConfigProps {
  appName: string;
  appDescription: string;
  domainName: string;
  
  crisp: {
    id: string;
    onlyShowOnRoutes: string[];
  };
  
  currency: {
    code: string;
    symbol: string;
    symbolPosition: 'before' | 'after';
  };
  
  theme: {
    defaultTheme: 'light' | 'dark' | 'system';
    enableSystem: boolean;
    disableTransitionOnChange: boolean;
    themes: string[];
  };
  
  // ... other config types
}

// config.ts
import { ConfigProps } from './types/config';

const config: ConfigProps = {
  // Configuration with full type checking
};
// utils/validateConfig.ts
import { z } from 'zod';

const configSchema = z.object({
  appName: z.string().min(1, 'App name is required'),
  domainName: z.string().min(1, 'Domain name is required'),
  currency: z.object({
    code: z.string().length(3, 'Currency code must be 3 characters'),
    symbol: z.string().min(1, 'Currency symbol is required'),
    symbolPosition: z.enum(['before', 'after']),
  }),
});

export const validateConfig = (config: any) => {
  try {
    configSchema.parse(config);
    return { valid: true, errors: [] };
  } catch (error) {
    return { 
      valid: false, 
      errors: error.errors.map(e => e.message) 
    };
  }
};

// Use in your app
const validation = validateConfig(config);
if (!validation.valid) {
  console.error('Configuration errors:', validation.errors);
}
// For settings that can be changed at runtime
interface AppSettings {
  maintenance_mode: boolean;
  allow_registrations: boolean;
  max_file_size: number;
  supported_currencies: string[];
}

const getAppSettings = async (): Promise<AppSettings> => {
  const { data } = await supabase
    .from('app_settings')
    .select('*')
    .single();
    
  return data || defaultSettings;
};

// Usage in components
const useAppSettings = () => {
  const [settings, setSettings] = useState<AppSettings | null>(null);
  
  useEffect(() => {
    getAppSettings().then(setSettings);
  }, []);
  
  return settings;
};
interface UserPreferences {
  theme: 'light' | 'dark' | 'system';
  notifications: boolean;
  language: string;
  currency: string;
}

const getUserPreferences = async (userId: string): Promise<UserPreferences> => {
  const { data } = await supabase
    .from('user_preferences')
    .select('*')
    .eq('user_id', userId)
    .single();
    
  return data?.preferences || defaultPreferences;
};

Keep sensitive data in environment variables:

// config.ts
const config = {
  // Public settings
  appName: 'LaunchKit Project',
  
  // Private settings (use environment variables)
  stripe: {
    publishableKey: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
    // Never put secret keys in config.ts
  },
  
  // Conditional settings
  analytics: {
    enabled: process.env.NODE_ENV === 'production',
    trackingId: process.env.GOOGLE_ANALYTICS_ID,
  },
};

// .env.local
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX

Document your configuration options:

/**
 * Application Configuration
 * 
 * This file contains all the configuration options for LaunchKit.
 * 
 * REQUIRED SETTINGS:
 * - appName: Your application name
 * - domainName: Your domain (without https://)
 * - resend.supportEmail: Support email address
 * 
 * OPTIONAL SETTINGS:
 * - crisp.id: Crisp chat widget ID
 * - aws: AWS/S3 configuration for file uploads
 * - social: Social media links for footer
 */
const config = {
  // ... configuration
};
// __tests__/config.test.ts
import config from '@/config';

describe('Configuration', () => {
  test('has required fields', () => {
    expect(config.appName).toBeDefined();
    expect(config.domainName).toBeDefined();
    expect(config.resend.supportEmail).toBeDefined();
  });
  
  test('currency format is valid', () => {
    expect(['before', 'after']).toContain(config.currency.symbolPosition);
    expect(config.currency.code).toHaveLength(3);
  });
  
  test('URLs are valid format', () => {
    // Test social URLs
    Object.values(config.social).forEach(url => {
      if (url) {
        expect(url).toMatch(/^https?:\/\//);
      }
    });
  });
});

Configuration Not Loading:

// Debug configuration loading
console.log('Config loaded:', {
  appName: config.appName,
  domainName: config.domainName,
  hasStripeKey: !!process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
});

Environment Variables Not Working:

# Check Next.js environment variable rules
# - NEXT_PUBLIC_ variables are available in browser
# - Regular variables only available on server

# .env.local
NEXT_PUBLIC_APP_NAME=MyApp  # Available everywhere
STRIPE_SECRET_KEY=sk_...    # Server-only

Type Errors:

// Ensure config matches ConfigProps interface
import { ConfigProps } from './types/config';

const config: ConfigProps = {
  // TypeScript will catch missing or incorrect types
};

Once your configuration is set up:

  1. Set up Development Workflow - Local development and hot reloading
  2. Test Your Configuration - Verify all settings work correctly
  3. Deploy to Production - Production configuration and environment variables
  4. Monitor Your App - Set up analytics and error tracking

Before going to production, verify:

  • App name and description are correct
  • Domain name is set to production domain
  • All required environment variables are set
  • Currency and pricing are correct
  • Email settings are configured and tested
  • Support system (Crisp or email) is working
  • Social media links are correct
  • Theme settings match your brand
  • Authentication URLs are correct
  • All integrations are properly configured