Configuration
Control branding, routes, and every flag-gated feature from the one shared @repo/config object.
Most configuration lives in @repo/config (packages/config/src/index.ts) - one typed config constant the backend and every frontend import, so a value changed once propagates everywhere. UI menus are the exception: they're per-app files so each frontend owns its own navigation and banners.
Where configuration lives
| Surface | Location | Controls |
|---|---|---|
| Central config | packages/config/src/index.ts (config) | Feature flags, identity/branding, routes, vendor providers |
| Pricing | packages/config/src/pricing.ts (pricingConfig) | Plans, prices, credits, products - see payments |
| Locales | packages/config/src/i18n.ts (i18nConfig) | Supported languages - see i18n |
| Cache & limits | packages/config/src/cache.ts (cacheConfig) | TTLs, rate-limit windows - see caching |
| Section tabs | packages/config/src/section-tabs.ts (sectionTabsConfig) | /admin, /settings sub-tabs - see dashboard |
| Roles & tenancy | packages/config/src/{roles,tenancy}.ts | Role names, org limits - see organizations |
| Navbar / Sidebar / User menu | apps/web-nuxt/app/config/{navbar,sidebar,user-menu}.ts | This frontend's menus - see navigation |
| Banners | apps/web-nuxt/app/config/banner.ts (bannerConfig) | Announcement bars - see banners |
| Secrets & env | root .env | API keys, connection strings - see environment variables |
The rest of this page covers the central config object; follow the links above for each surface's detail.
How flags gate features
Toggleable features are a discriminated union - { enabled: false } | { enabled: true; ... } - so reading config.X.enabled narrows the type and exposes provider fields only when on.
import { config } from "@repo/config";
if (config.payment.enabled) {
// config.payment.provider is now available ("stripe" | "polar")
}A disabled feature must never render or execute. Always check config.X.enabled before rendering UI or running logic - guard backend handlers, hide nav items, and skip client effects when it is false.
Feature flag reference
Each feature page documents the same three things you should reason about: the flag, what hides when off, and the shipped default.
| Flag | Default | Hidden when off |
|---|---|---|
storage.enabled | true (s3) | File uploads - see storage |
payment.enabled | true (stripe) | Pricing, checkout, billing - see payments |
sms.enabled | true (twilio) | Phone 2FA, SMS sends - see sms |
newsletter.enabled | true (listmonk) | Newsletter signup - see email |
apiKeys.enabled | true | API key generation - see api |
apiDocs | true | Scalar API reference at /api/docs - see api |
notifications.enabled | true | Notification bell - see notifications |
adminNotifications.enabled | true | Admin business-event alerts - see notifications |
captcha.enabled | true (turnstile) | Bot protection on auth forms - see authentication |
blog.enabled | true | Blog routes - see blog |
contentApi.enabled | true | Content API - see blog |
docs.enabled | false (on with generatesaas init --docs) | "Docs" link in marketing nav - see authoring docs |
tenancy.multiTenant | true | Organizations, teams - see organizations |
waitlist | false | Normal signup (waitlist mode off) - see waitlist |
cookieBanner | "auto" | Cookie consent banner; false removes all consent gating - see cookie consent |
consentPolicy | "gdpr-and-unknown" | Which visitors must consent before consent-required analytics run ("gdpr-only", "everyone", "never") - see cookie consent |
Two flags are bare booleans, not the { enabled } union - read config.apiDocs and config.waitlist directly (no .enabled). config.performanceMonitor.enabled toggles the Hono request logger; it is true by default - see api.
Some capabilities are presence-gated, not boolean: config.analytics, config.support, and config.affiliate activate per provider key you add - see integrations.
Identity, branding, and SEO
Always-present fields drive titles, structured data, and the logo.
| Key | Type | Description |
|---|---|---|
siteName / fullSiteName | string | Short and full app names |
domain | string | Primary domain, no protocol |
baseUrl | string | process.env.BASE_URL ?? "https://generatesaas.com" |
logo | { main, square } | Logo image paths |
indexable | boolean (default true) | Gates search indexing + sitemap; set false for staging |
business | { name, address, registrationNumber } | Legal pages, invoices |
phone | { number, formatted } | tel: links |
seo | SeoConfig | Organization structured-data fields |
social | object | Footer profile links - absent keys render nothing |
CORS origins
config.origins lists the origins allowed to call the backend.
- It reads
process.env.TRUSTED_ORIGINS(comma-separated, trimmed), falling back to local dev ports. - It is passed verbatim to both Hono CORS and Better Auth
trustedOriginswith nobaseUrlinjection. - Every origin you serve from - including production
baseUrl- must be inTRUSTED_ORIGINS. See environment variables.
Routes - never hardcode paths
config.routes.* centralizes shared in-app paths (home, auth, loginRedirect, dashboard, pricing, onboarding, settings, notifications, admin) so a path change is one edit.
import { config } from "@repo/config";
redirect(config.routes.loginRedirect); // "/dashboard" - survives a path changeRule: use config.routes.* for any redirect or shared link - never hardcode "/dashboard". Framework-idiomatic route construction (locale prefixing, navigation helpers) lives in data fetching.
Barrel exports
@repo/config also re-exports named values that are NOT keys on config - import them directly.
| Export | Purpose |
|---|---|
pricingConfig, mainCurrency | Plans, credits, products - see payments |
i18nConfig, Locale | Supported locales - see i18n |
cacheConfig | TTLs + rate-limit windows - see caching |
getBillingScope(), getOrganizationLimit() | Tenancy helpers - see organizations |
getFooterColumns(), SOCIAL_PROVIDERS_META | Footer + social provider metadata |
Frequently asked questions
How do I turn a feature off?
Set its flag to { enabled: false } (or false for waitlist) in packages/config/src/index.ts. Every consumer reads the flag, so UI, routes, and jobs stand down automatically.
Where do the defaults live?
In config itself - the shipped values in packages/config/src/index.ts are the defaults. Edit them in place; there is no hidden layer.
Why are provider fields sometimes missing in TypeScript?
Toggleable configs are a discriminated union. Provider fields only exist after you narrow with config.X.enabled === true.
Where do secrets and API keys go?
Not here. config holds non-secret behavior; keys and credentials live in environment variables.