Codapult's behavior is controlled through a small set of TypeScript configuration files. These are the files you'll edit most when making the product your own.
| File | Purpose |
|---|---|
src/config/app.ts | Brand, feature toggles, auth, AI, payments, company links |
src/config/navigation.ts | Dashboard and admin sidebar items |
src/config/marketing.ts | Landing page pricing, testimonials, stats, competitor comparison |
src/lib/config.ts | Typed env var access (hand-maintained Zod schema) |
App identity
Edit src/config/app.ts to set your product identity:
const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000';
export const appConfig: AppConfig = {
appUrl,
serverUrl: process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : appUrl,
appName: process.env.NEXT_PUBLIC_APP_NAME ?? 'my-app',
brand: {
name: 'My App',
description: 'The best project management tool for teams',
logo: '/logo.svg',
icons: {
favicon: '/favicon.ico',
svg: '/icon.svg',
apple: '/apple-icon.png',
pwa192: '/icon-192.png',
pwa512: '/icon-512.png',
},
},
// ...
};
| Property | Source | Description |
|---|---|---|
appUrl | NEXT_PUBLIC_APP_URL env var | Canonical public URL (auth callbacks, emails, SEO). Defaults to http://localhost:3000 for dev. |
serverUrl | VERCEL_URL → appUrl | SSR self-reference URL for server-side fetches (tRPC). On Vercel, uses the deployment URL; otherwise falls back to appUrl. |
appName | NEXT_PUBLIC_APP_NAME env var | Technical project name (lowercase). Used for OTEL service name and machine identifiers. |
brand.name | Hardcoded in source | Display name (proper case). Shown in sidebar, emails, meta tags, PWA manifest. Edit directly in app.ts. |
brand.description | Hardcoded in source | Short product description for SEO meta tags. |
brand.logo | Hardcoded in source | Path to logo image (relative to /public). |
brand.icons | Hardcoded in source | Brand icon paths: favicon (.ico), svg, apple (180×180), pwa192, pwa512. Wired into <head> metadata and PWA manifest. Drop your files at these paths in public/ (favicon.ico lives in src/app/) or change the paths here. |
The brand.name display name propagates to manifest.ts, layout.tsx metadata, and email templates automatically. The appUrl is used everywhere: robots.ts, sitemap.ts, auth callbacks, Stripe redirects, invite links, and email links. The serverUrl is used by tRPC for SSR data fetching — on Vercel preview deployments it points to the correct deployment URL instead of the production domain.
Feature toggles
All feature toggles live in environment variables (prefix ENABLE_*). They are surfaced server-side via env.features in src/lib/config.ts, and both the navigation and src/proxy.ts use the same source to hide UI and block routes.
All toggles default to true. Set the corresponding env var to false to disable the feature — its routes return 404 and its sidebar/footer entries disappear.
| Env var | Feature | Default |
|---|---|---|
ENABLE_AI_CHAT | AI chat assistant (Vercel AI SDK, streaming) | true |
ENABLE_TEAMS | Organizations, multi-tenancy, team invitations | true |
ENABLE_BLOG | MDX blog with i18n, tags, and authors | true |
ENABLE_HELP_CENTER | Help center / documentation (MDX) | true |
ENABLE_WAITLIST | Waitlist with email confirmation | true |
ENABLE_REFERRALS | Referral / affiliate program | true |
ENABLE_ANALYTICS | Self-serve analytics dashboard | true |
ENABLE_WORKFLOWS | Event-triggered workflow automation | true |
ENABLE_WEBHOOKS | Outgoing webhooks (HMAC signed) | true |
ENABLE_AUDIT_LOG | User-facing activity / audit log | true |
ENABLE_REPORTS | Scheduled email reports | true |
ENABLE_FEATURE_REQUESTS | Public feature request voting board | true |
ENABLE_CHANGELOG | In-app "What's new" changelog widget | true |
ENABLE_ONBOARDING | Interactive onboarding tours | true |
ENABLE_BRANDING | Per-org white-labeling | true |
ENABLE_SSO | Enterprise SSO (SAML via BoxyHQ) | true |
ENABLE_EXPERIMENTS | A/B testing framework | true |
ENABLE_DRIP_CAMPAIGNS | Email drip campaigns | true |
ENABLE_TWO_FACTOR | TOTP-based two-factor authentication | true |
ENABLE_PLUGINS | Premium plugin marketplace (/plugins, /dashboard/plugins) | true |
ENABLE_API_DOCS | Interactive OpenAPI reference page (/docs/api) | true |
Each ENABLE_* flag is independent — disabling auth (AUTH_PROVIDER=none) does not cascade to other features. The only exception is twoFactor, which is meaningless without an auth provider and is therefore force-disabled when AUTH_PROVIDER=none. Dashboard-only modules (aiChat, teams, referrals, etc.) keep their flags but their pages live under /dashboard/*, which is itself 404 when auth is off; their public-facing parts (e.g. /feature-requests, /changelog) remain accessible until you flip the matching ENABLE_* flag (the CLI setup --preset marketing does this for you).
Auth configuration
Control which sign-in methods appear on the auth pages:
auth: {
magicLink: true,
passkeys: true,
},
| Property | Type | Description |
|---|---|---|
magicLink | boolean | Passwordless email sign-in via magic link |
passkeys | boolean | WebAuthn / passkey authentication |
OAuth providers appear automatically when both <PROVIDER>_CLIENT_ID and <PROVIDER>_CLIENT_SECRET env vars are set (supported: google, github, apple, discord, twitter, microsoft).
Two-factor authentication (TOTP) is controlled by ENABLE_TWO_FACTOR — see Feature toggles.
AI configuration
Customize the AI chat assistant behavior:
ai: {
defaultModel: 'gpt-4o-mini',
systemPrompt: 'You are a helpful AI assistant. Be concise, accurate, and helpful.',
ragEnabled: true,
ragMaxChunks: 3,
ragMinScore: 0.4,
allowedModels: [],
},
| Property | Type | Description |
|---|---|---|
defaultModel | string | Default model ID. Must match an entry in src/lib/ai/models.ts |
systemPrompt | string | System prompt prepended to every conversation |
ragEnabled | boolean | Enable retrieval-augmented generation in chat |
ragMaxChunks | number | Maximum context chunks injected from RAG |
ragMinScore | number | Minimum cosine similarity score for RAG results (0–1) |
allowedModels | string[] | Models shown in the chat model selector. Empty array = all models from models.ts |
Payments configuration
Control Stripe Connect marketplace settings:
payments: {
stripeConnectFeePercent: Number(process.env.NEXT_PUBLIC_STRIPE_CONNECT_FEE_PERCENT ?? 10),
},
| Property | Type | Description |
|---|---|---|
stripeConnectFeePercent | number | Platform fee percentage shown in the Connect dashboard UI |
Both the server-side fee (in src/lib/payments/connect.ts via env.stripeConnectFeePercent) and the client-side UI display read from appConfig.payments.stripeConnectFeePercent — a single source of truth. Override it per environment with NEXT_PUBLIC_STRIPE_CONNECT_FEE_PERCENT.
Company links
Set company contact details used in the Privacy Policy, Terms of Service, and footer:
company: {
contactEmail: 'support@myapp.com',
githubUrl: 'https://github.com/myorg/myapp',
},
| Property | Type | Description |
|---|---|---|
contactEmail | string | Contact email used in legal pages (Privacy Policy, Terms) |
githubUrl | string | GitHub URL shown in the footer. Hidden if empty |
Navigation
Edit src/config/navigation.ts to customize sidebar items for the dashboard and admin panel.
Each item has a href, label, icon (from Lucide), and an optional featureKey:
import type { env } from '@/lib/config';
export interface NavItem {
href: string;
label: string;
icon: LucideIcon;
featureKey?: keyof typeof env.features;
}
When featureKey is set, the item is automatically hidden if that feature is disabled in env.features (see the Feature toggles table).
Dashboard sidebar
export const dashboardNavItems: NavItem[] = [
{ href: '/dashboard', label: 'Dashboard', icon: LayoutDashboard },
{ href: '/dashboard/ai-chat', label: 'AI Chat', icon: MessageSquare, featureKey: 'aiChat' },
{ href: '/dashboard/analytics', label: 'Analytics', icon: BarChart3, featureKey: 'analytics' },
{ href: '/dashboard/billing', label: 'Billing', icon: CreditCard },
// ...add your own items here
];
Admin sidebar
export const adminNavItems: NavItem[] = [
{ href: '/admin', label: 'Overview', icon: LayoutDashboard },
{ href: '/admin/users', label: 'Users', icon: Users },
{ href: '/admin/subscriptions', label: 'Subscriptions', icon: CreditCard },
{ href: '/admin/feature-flags', label: 'Feature Flags', icon: Flag },
// ...
];
To add a custom page, create the route in src/app/(dashboard)/dashboard/your-page/page.tsx and add a corresponding entry to dashboardNavItems.
Marketing configuration
Edit src/config/marketing.ts to customize the landing page content.
Pricing tiers
export const pricingTiers: PricingTier[] = [
{
key: 'starter',
price: '$49',
href: process.env.CHECKOUT_URL_STARTER || '/sign-up',
featureKeys: [
'tierFeatureFullCode',
'tierFeatureAllModules',
'tierFeatureProject1',
'tierFeatureUpdates6',
'tierFeatureCommunity',
],
},
// ... pro ($99, featured), enterprise ($149)
];
Feature labels are stored in messages/en.json under the "ProductPricing" namespace, so they support i18n. The key maps to a translation key for the tier name.
Set featured: true on a tier to visually highlight it. The href property links to an external checkout (configured via CHECKOUT_URL_* env vars) or falls back to /sign-up.
Premium plugins
Premium plugins are configured in the premiumPlugins array and pluginBundle object. Each plugin has a href that links to an external checkout URL (configured via CHECKOUT_URL_PLUGIN_* env vars):
CHECKOUT_URL_PLUGIN_AI_KIT— AI Kit pluginCHECKOUT_URL_PLUGIN_CRM— CRM pluginCHECKOUT_URL_PLUGIN_HELPDESK— Helpdesk pluginCHECKOUT_URL_PLUGIN_EMAIL_MARKETING— Email Marketing pluginCHECKOUT_URL_PLUGIN_BUNDLE— All plugins bundle (discounted)
When set, plugin cards on the pricing page and the /plugins page show "Buy" buttons linking to external checkout. Without these env vars, buttons link to /#plugins.
Testimonials
export const testimonials: Testimonial[] = [
{
name: 'Alex Chen',
role: 'Founder, StartupXYZ',
content: 'Codapult saved me weeks of setup time.',
initials: 'AC',
},
// ...add real customer quotes
];
Stats and competitor comparison
The stats array powers the "by the numbers" section on the landing page. The comparisonFeatures array generates the competitor comparison table — edit it to reflect your product's feature matrix.
Typed env var access
src/lib/config.ts provides a typed env object for reading environment variables in server code:
import { env } from '@/lib/config';
// Type-safe access with defaults
env.authProvider; // 'better-auth' | 'kinde' | 'none'
env.paymentProvider; // 'stripe' | 'lemonsqueezy'
env.storageProvider; // 'local' | 's3' | 'r2'
env.notificationTransport; // 'poll' | 'sse' | 'ws'
env.vectorStoreProvider; // 'sqlite' | 'memory'
env.sso.product; // string — SSO product identifier
env.defaultMonthlyCredits; // number — monthly AI credits
env.stripeConnectFeePercent; // number — Stripe Connect fee
env.turso.url; // string
env.stripe.secretKey; // string
Important: Server-side code should use env.* for adapter/provider settings and appConfig.* for app identity (appUrl, serverUrl, appName, brand). Never read process.env directly for values available through these objects.
This file is hand-maintained. When adding a new environment variable, add a Zod field to the schema and a corresponding property to the env object in the same file.
For the full list of environment variables and their descriptions, see the Environment Variables reference.