GenerateSaaS

Manifest

The .generatesaas/manifest.json record of every option your project was built with, which update reads to re-shape each new boilerplate version into your exact configuration.

.generatesaas/manifest.json is the record of how your project was scaffolded. It stores the full set of options you chose at init (minus secrets), so update can re-shape each new boilerplate version into your configuration before diffing.

You normally never edit this file by hand - init writes it and update keeps it current. Editing an option here changes how the next update shapes the template, which is exactly how a feature gets adopted.

How update keeps it current

The boilerplate gains new options over time; a project that predates one simply lacks that field. On every real update, before staging:

Default-fill every missing option at the value that preserves your current behavior. The manifest becomes structurally complete and nothing about your project changes.
Surface newly adoptable options to your AI assistant, which presents each with a recommendation. Adopting one writes the flag and re-stages, so the feature comes in cleanly.

The new-options review always runs, even though the update is otherwise unattended - surfacing a new feature is never a decision the update should assume.

KindBehavior
Init-locked (architecture, providers, identity)Recorded but never re-offered. Switching one after the fact is a migration, not an update.
AdoptableFeatures added since you built. They default to off, so behavior never changes until you choose.

Field reference

Type is the JSON value. Default is what an absent field is filled with. Adoptable marks options the update flow offers when newly available.

Infrastructure

FieldTypeDefaultAdoptableNotes
architecture"fullstack" | "separate""fullstack"NoFrontend-hosted API or standalone Hono backend.
deploymentTarget"node" | "vercel""node"NoLong-running runtime or Vercel serverless.
databaseProvider"postgres" | "neon" | "supabase""postgres"NoDatabase driver and connection style. On deploymentTarget: "vercel" only the managed providers (neon, supabase) are valid - serverless cannot pool to a self-hosted postgres, and update refuses the pair if you write it by hand.
cacheProvider"redis" | "upstash""redis"NoCache/session backend. On deploymentTarget: "vercel" only upstash is valid - serverless cannot hold persistent Redis connections, and update refuses the pair if you write it by hand.

Features

FieldTypeDefaultAdoptableNotes
paymentProvider"stripe" | "polar" | "none""none"NoBilling provider.
defaultCurrencycurrency code"USD"NoBilling and pricing currency.
emailProvider"smtp" | "ses" | "resend""smtp"NoTransactional email provider.
observability("sentry" | "posthog")[][]YesWhich services receive your app's errors, and on PostHog its LLM analytics. Pick any combination. Only a selected provider's adapter is generated under packages/observability and only its SDK installed, so an unpicked one costs no bytes and no dependency; [] ships no reporting SDK at all. The mobile app follows the same choice: it receives only the picked provider's React Native SDK, its startup half of apps/mobile/lib/observability.ts and its EXPO_PUBLIC_* key, and the Sentry Expo config plugin only when Sentry is picked. Change it by editing this array and running update - pasting a credential into config.observability never works for an adapter that was never generated. The Electron half follows desktop; PostHog has no Electron SDK, so a PostHog-only desktop build reports nothing and says so at startup. Projects carrying the older errorTracking string are migrated automatically.
multiTenancybooleanfalseYesOrganizations - teams, members, shared resources.
projectsbooleanfalseYesProjects - a switcher that scopes a user's work inside their account, or inside the active organization when multiTenancy is on. Independently settable from multiTenancy but not independent in effect: enabling multiTenancy alone already scopes the desktop app's local data by an invisible per-organization project, so this option adds the UI rather than the scoping. In a single-tenant app, enabling projects moves desktop automations to the reachable-allowlist mode, where an explicit sign-out (or an expired session) stops all unattended automations on that device. See Projects.
docsbooleanfalseYesThe self-hosted Fumadocs docs app (apps/docs).
desktopbooleanfalseYesThe cross-platform desktop app (apps/desktop, built with Electron).
mobilebooleanfalseYesThe cross-platform mobile app (apps/mobile, built with Expo). Ships the iOS and Android app, the .well-known universal-link routes on your site, and the Expo push channel. Offered on the Pro plan and up.
mobilePurchases"revenuecat" | "none""none"YesIn-app purchases in the mobile app, through RevenueCat. Store purchases are additive to your web payment provider, never a replacement - the same account, billed by Apple or Google instead of by you. Only meaningful with mobile: true and a real paymentProvider; on any other shape the field is absent, and update refuses a hand-written "revenuecat" there. "none" ships the app on web billing, where its plan card says the subscription is managed on your website.
aibooleanfalseYesThe app's AI features (config.ai.enabled): chat, models, automations, integrations. Master gate for the AI sub-options - when off they are forced off and the AI nav self-hides. A desktop project always ships the app's local AI stack; this option sets config.desktop.agents.enabled, the runtime gate on those screens. The AI packages themselves ship either way, because packages/translate depends on @repo/ai for the i18n pipeline every project uses.
webAibooleantrueYesWhether the web app shows the AI surfaces - chat, automations, model and integration settings, and their nav entries. false is the desktop-first shape: your desktop or mobile app is the AI product and the website keeps account, login, billing and licensing only. The AI service is untouched either way - config.ai.enabled, the /api/ai/* routes and the desktop agents all stay exactly as ai set them, so the desktop app keeps talking to the same backend. Only meaningful with ai: true and an app to host the AI in - desktop: true or mobile: true; update refuses that combination if you write one by hand.
creditsbooleanfalseYesMetered usage credits on top of subscription plans. Requires a payment provider.
revenueSharingbooleanfalseYesOpt-in MRR leaderboard with dofollow backlinks.

System fields

Not options. Written by init and maintained by the CLI - do not edit them.

FieldNotes
versionThe boilerplate version the project is on. update bumps it when an apply completes.
initialVersionThe version the project was first scaffolded from.
appNameDisplay name of the app.
projectNameSlug used for the project directory and package names.
frontendWhich frontend generated the project. Always "nextjs"; a project recording "nuxt" predates the single-frontend move and update refuses it (see update).
baseUrlPublic base URL baked into config and .env, when set.
licenseTokenSigned license token; refreshed by update for the target version.
licenseKeyHashHash of the license key, for attribution.
installIdPer-install identifier used by the heartbeat.

A hand-edited value outside an option's allowed set is caught before staging: update names the field, the value it holds, and what it accepts. Retired keys are left alone - see update for how each one is reconciled.

On this page