GenerateSaaS

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

SurfaceLocationControls
Central configpackages/config/src/index.ts (config)Feature flags, identity/branding, routes, vendor providers
Pricingpackages/config/src/pricing.ts (pricingConfig)Plans, prices, credits, products - see payments
Localespackages/config/src/i18n.ts (i18nConfig)Supported languages - see i18n
Cache & limitspackages/config/src/cache.ts (cacheConfig)TTLs, rate-limit windows - see caching
Section tabspackages/config/src/section-tabs.ts (sectionTabsConfig)/admin, /settings sub-tabs - see dashboard
Roles & tenancypackages/config/src/{roles,tenancy}.tsRole names, org limits - see organizations
Navbar / Sidebar / User menuapps/web-next/config/{navbar,sidebar,user-menu}.tsThis frontend's menus - see navigation
Bannersapps/web-next/config/banner.ts (bannerConfig)Announcement bars - see banners
Secrets & envroot .envAPI 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.

FlagDefaultHidden when off
storage.enabledtrue (s3)File uploads - see storage
payment.enabledtrue (stripe)Pricing, checkout, billing - see payments
sms.enabledtrue (twilio)Phone 2FA, SMS sends - see sms
newsletter.enabledtrue (listmonk)Newsletter signup - see email
apiKeys.enabledtrueAPI key generation - see api
apiDocstrueScalar API reference at /api/docs - see api
notifications.enabledtrueNotification bell - see notifications
adminNotifications.enabledtrueAdmin business-event alerts - see notifications
captcha.enabledtrue (turnstile)Bot protection on auth forms - see authentication
blog.enabledtrueBlog routes - see blog
contentApi.enabledtrueContent API - see blog
docs.enabledfalse (on with generatesaas init --docs)"Docs" link in marketing nav - see authoring docs
tenancy.multiTenanttrueOrganizations, teams - see organizations
waitlistfalseNormal 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; fullstack projects generate with it false (Next.js logs requests natively, so the mounted API would double-log) and separate-backend projects with it true - 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.

KeyTypeDescription
siteName / fullSiteNamestringShort and full app names
domainstringPrimary domain, no protocol
baseUrlstringprocess.env.BASE_URL ?? "https://generatesaas.com"
logo{ main, square }Logo image paths
indexableboolean (default true)Gates search indexing + sitemap; set false for staging
business{ name, address, registrationNumber }Legal pages, invoices
phone{ number, formatted }tel: links
seoSeoConfigOrganization structured-data fields
socialobjectFooter 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 trustedOrigins with no baseUrl injection.
  • Every origin you serve from - including production baseUrl - must be in TRUSTED_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 change

Rule: 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.

ExportPurpose
pricingConfig, mainCurrencyPlans, credits, products - see payments
i18nConfig, LocaleSupported locales - see i18n
cacheConfigTTLs + rate-limit windows - see caching
getBillingScope(), getOrganizationLimit()Tenancy helpers - see organizations
getFooterColumns(), SOCIAL_PROVIDERS_METAFooter + 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.

On this page