GenerateSaaS

Desktop app

The cross-platform Electron desktop app gated by config.desktop - what it ships, how to enable it, and how AI and billing behave inside it.

A cross-platform Electron app for desktop-first products, gated by config.desktop and shipped off by default. The native app is your primary surface; the web frontend keeps marketing, purchase, authentication, and billing.

Locationapps/desktop
StackElectron + React 19 + Vite (electron-vite) + shadcn/ui on Base UI
Themeshared @repo/styles
Main processapps/desktop/src/main - native window, deep links, updates, secure storage
Rendererapps/desktop/src/renderer - the UI, reaching main only through the validated window.api preload bridge

The desktop app requires a Pro (or Agency) license. Solo licenses cannot scaffold apps/desktop: the CLI hides the option and the license server rejects it.

Enabling it

Scaffold with the --desktop flag on the init command. It defaults off; --no-desktop is the explicit off.

generatesaas init --desktop
Piece--desktop on--desktop off
apps/desktop (Electron main + React renderer)keptstripped
bearer plugin in @repo/authkeptstripped
/auth/device verification page + deviceAuthorizationClient wiringkeptstripped
deviceAuthorization plugin in @repo/authkeptkept (inert)
config.desktop block in @repo/configenabled: truekept, enabled: false

The off state keeps the inert pieces - the deviceAuthorization plugin, the deviceCode table, the device.* i18n keys, the config.desktop block - because they are additive and harmless without the app. See Configuration for the generated block.

What the app includes

The AI init answer decides what the app is:

BuildWhat you get
Desktop + AIThe local AI stack (Chat, Automations, the AI settings screens), the Terminal that runs the user's own coding CLI, and the agent runtime the app forks from its own binary. A run goes to your hosted default or to the user's own CLI, whichever their default resolves to.
Desktop without AIA client shell - login, onboarding, home, account, settings, organization, and notifications (bell, list and detail) - wired to your backend over device sign-in. The AI surface still ships, held off by config.desktop.agents.enabled: false, so you can turn it on later without re-scaffolding.

The app follows the same default model as the web app: a fresh chat runs on your hosted default and meters credits, exactly as the website does, unless the user has picked their own. Set config.desktop.agents.defaultModel to a CLI for the local-first posture - then every default run happens on that user's own machine, under their own subscription, and costs you nothing. Either way a user can pick a connected CLI for themselves at any time, and integrations stay in your backend so one connection serves every surface.

The default is a pointer, not a copy: a user who never chose, or who used the Models screen's reset, follows whatever default your next build ships.

On macOS the app draws its own title bar: the top 38px drag the window and hold the side-panel toggle, and they drop in full screen. A control you place in that band needs the app-no-drag class, or clicks on it move the window instead. Windows and Linux keep the native frame.

Entitlements and billing

The app reads the session plan and credits and can gate features behind them. The credit surfaces (sidebar balance pill, Account credits row) show when this app can spend credits: credits configured and the desktop AI surface on.

  • Source: the pure-data @repo/config/pricing export plus apps/desktop/src/renderer/lib/entitlements.ts.
  • Checkout stays on the web: the in-app Billing screen shows the plan, credits and who funds the workspace; changing plan, buying credits and the billing portal open /settings/billing in the browser.
  • Transactions and Developers are in-app: the Transactions screen (with config.payment.enabled) lists the billing history, and the Developers screen (with config.apiKeys.enabled) creates and revokes API keys without leaving the app.
  • The admin panel is in-app too: all eight tabs - the finance dashboard, users, organizations, audit logs, billing logs, API keys, announcements and email analytics - render the same shared bodies the web does, behind the same platform admin role. A non-admin gets no route, no sidebar entry, no palette command and no tab. Starting an impersonation stays a web action; the warning banner mounts here regardless, and draws itself only if a session ever carries one.
  • Profile does not: the same screen edits photo, name, country, phone, and marketing consent over authClient. The photo is set by URL - the renderer's bridge serializes request bodies to text, so it cannot upload a file.
  • Neither does security: the Security screen beside it changes the password, manages TOTP two-factor, and revokes sessions. See Account security settings.

Credits survive the app being local-only. The model turn is paid for by the user's own CLI subscription, but you can meter anything else your product does on their behalf - an external API you pay for, say - against the same balance. Ship credits off in config.pricing to remove the surface entirely.

Next steps

On this page