GenerateSaaS

Configuration

Control branding, routes, and every flag-gated feature from the one shared @repo/config object, plus the per-app files that own navigation and banners.

Most configuration lives in @repo/config (packages/config/src/index.ts) - one typed config constant the backend and the frontend both import, so a value changed once propagates everywhere. It is pure static data: no process.env, so a packaged desktop app and a sandboxed renderer can import the same module.

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 and /settings sub-tabs - see dashboard
Roles & tenancypackages/config/src/{roles,tenancy}.tsRole names, org limits - see organizations
Navbar / Sidebar / User menuapps/web/config/{navbar,sidebar,user-menu}.tsThis frontend's menus - see navigation
Bannersapps/web/config/banner.ts (bannerConfig)Announcement bars - see banners
Secrets & envroot .envAPI keys, connection strings - see environment variables

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

FlagDefaultHidden when off
storage.enabledtrue (s3)File uploads - see storage
payment.enabledyour init answerPricing, checkout, billing - see payments
sms.enabledfalsePhone 2FA, SMS sends - see sms
newsletter.enabledfalseNewsletter 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.enabledfalse (turnstile when on)Bot protection on auth forms - see authentication
content.sectionsblog, alternatives, compareContent routes - see content
contentApi.enabledfalseContent API - see authoring content
docs.enabledyour --docs answer"Docs" nav link - see authoring docs
desktop.enabledyour --desktop answerDesktop app + device sign-in - see desktop app
ai.enabledyour init answerChat, automated runs, capabilities - see AI
runner.enabledyour init answer (needs ai)Daemon pairing + the Runners settings tab - see runner
mcpServer.enabledfalse (no ai needed)Outward MCP server URL + the MCP settings tab - see external agent (MCP)
revenueSharing.enabledyour init answerNothing. It only decides whether the daily license heartbeat self-reports aggregate revenue - see licensing & heartbeat
observability.sentry.dsn / observability.posthog.publicKey""Error reporting everywhere. The credential is the switch: empty loads no SDK at all - see error reporting
tenancy.multiTenantyour init answerOrganizations, teams - see organizations
projects.enabledyour init answerProject switcher + the Projects settings tab - see projects
waitlistfalseWaitlist mode; off means signups create real accounts - see waitlist
cookieBanner"auto"Cookie consent banner; false removes all consent gating - see cookie consent
consentPolicy"gdpr-and-unknown"Who must consent before gated analytics run. Also "gdpr-only", "everyone", "never" - see cookie consent

Shapes that break the { enabled } pattern:

  • config.apiDocs and config.waitlist are bare booleans - read them directly, with no .enabled.
  • config.performanceMonitor.enabled toggles the Hono request logger. Fullstack projects generate it false (Next.js logs requests natively, so the mounted API would double-log); separate-backend projects generate it true - see api.
  • config.observability has no enabled flag and no provider selector: a provider runs when its own credential is non-empty, and both may run at once - see error reporting.
  • config.analytics, config.support, config.affiliate are presence-gated - each provider activates when you add its sub-config - see integrations.
  • config.email has no off switch - you only pick its provider - see email.

The desktop app's AI orchestration gates on config.desktop.agents.enabled, which a desktop project derives from your AI option rather than a separate switch. Every desktop project ships the agent runtime the app forks, so flipping the flag on turns those screens back on with no regeneration. See desktop AI.

Identity, branding, and SEO

KeyTypeDescription
siteName / fullSiteNamestringShort and full app names
domainstringPrimary domain, no protocol
baseUrlstringThe project's own origin. Server code reads the BASE_URL env when a deployment sets one, else this literal
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
builtWith{ founderName? }?Footer attribution line. Set founderName for "Built by {name} using GenerateSaaS"; leave it empty for "Built with GenerateSaaS"; delete the block to remove the line

Rebranding walks these fields one by one in customization.

CORS origins

config.origins lists the origins allowed to call the backend, as both the Hono CORS allowlist and Better Auth trustedOrigins.

  • It is static data, so it holds your development origins and any origin you want committed.
  • The backend unions it with TRUSTED_ORIGINS (comma-separated env), which is where a per-deployment production URL belongs.
  • baseUrl is never auto-trusted. Every origin you serve from must appear in one of the two - see environment variables.

Shipping the desktop app? It needs no entry in TRUSTED_ORIGINS: its main process makes every backend call - dev and packaged alike - with no Origin header, so the backend treats it as a native client and never CORS-checks it. TRUSTED_ORIGINS governs your web frontends.

Routes - never hardcode paths

config.routes.* centralizes shared in-app paths (home, auth, loginRedirect, dashboard, pricing, onboarding, settings, chat, automations, settingsModels, settingsIntegrations, settingsRunners, settingsMcp, settingsBilling, notifications, admin, adminOrganizations, adminBillingLogs) so a path change is one edit.

import { redirect } from "next/navigation";
import { config } from "@repo/config";

redirect(config.routes.loginRedirect); // "/dashboard" - survives a path change

Use config.routes.* for any redirect or shared link. Framework-idiomatic route construction (locale prefixing, navigation helpers) lives in data fetching.

AI product - config.ai

config.ai sets who pays for inference and which models are pickable. The AI page covers what it ships.

FieldTypeDescription
enabledbooleanMaster switch. When false the /ai/* endpoints are inert (400) and no AI UI renders.
builtinbooleanYour app offers models under its OWN name, billed from user credits. Runs through OpenRouter at live per-token pricing times your markup; the only setup is OPENROUTER_API_KEY.
builtinModelsstring[]?Limit which models built-in AI offers, as plain OpenRouter model ids. Omit for the full catalog.
byokbooleanLet users connect their own provider API keys; MODEL calls on their key never use credits. Web-tool calls still do — they spend your vendor keys, not theirs.
defaults{ model?, effort?, fallbackModel?, fallbackEffort? }What every user starts on, so built-in AI needs zero user setup. Their own choice always wins.
titles{ builtin?, byok?, cli? }The cheap model that names each conversation, per lane - your decision, never a user setting. See AI.
terminals{ cli? }The model a desktop terminal session STARTS on, per coding CLI ({ model }). Empty by default: each CLI picks its own default, on the user's own subscription.
userAutomationsboolean (default true)Whether end-users may author automated runs. false hides the create form, 403s the create route, and withholds the create_automation capability; existing automations still list, run, edit, and delete.
webSearchboolean (default on)Master switch over BOTH search lanes. Users opt in with the composer toggle — off by default, remembered per browser. false hides the toggle and refuses search on every lane, BYOK included (searching sends prompts to a third-party provider, so the switch is about data egress, not cost). See web search and extract.
webTools{ searchEngine?, extractEngine?, searchCostUsd?, extractCostUsd? }Tunes the app's OWN web tools: which engine serves each capability when several are configured ("firecrawl" by default, "parallel" for the cheaper vendor, "tinyfish" for the free one - chosen per capability, so the two may differ), and what one call costs you (billed to credits with your markup, same price whichever engine ran). Leave the costs unset and each engine charges its own vendor rate - 0.011 Firecrawl search, 0.005 Parallel search, 0 TinyFish search, 0.0032 Firecrawl page, 0.001 Parallel page, 0 TinyFish page. The tools themselves are switched on by FIRECRAWL_API_KEY / PARALLEL_API_KEY / TINYFISH_API_KEY, not here.
chats{ cap }Stored chat threads per user; the server evicts the oldest beyond the cap.
runLimits{ perUser, perPayer }Concurrent metered runs per user, and the per-balance fallback for plans that set no maxConcurrentRuns of their own. BYOK and runner runs never count. See concurrency caps.

Two AI sidebar flags are derived, not config keys: aiByok (AI on and byok on) and aiModels (the Models page). Both are computed in sidebar-flags.ts and follow config.ai automatically - see navigation.

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
getOrganizationLimit()Tenancy helper - see organizations
getFooterColumns(), SOCIAL_PROVIDERS_METAFooter + social provider metadata

On this page