For the complete documentation index, see llms.txt. Prefer markdown by appending .md to documentation URLs or sending Accept: text/markdown.

Disable organizations

Turn off organizations in TurboStarter and ship a B2C product where every user stays on a personal account with user-scoped billing.

TurboStarter ships organizations for multi-tenant B2B SaaS, but personal accounts already work as the default path. Signup lands on /dashboard, billing can attach to the user (referenceId = user.id), and organizations are opt-in via the account switcher and create flow.

This recipe turns that default into a hard product rule: users never create, join, or switch organizations. Everything stays on the personal account.

TL;DR

  1. Decide soft vs hard disable (keep the Better Auth plugin gated, or remove it).
  2. Block create on the server with allowUserToCreateOrganization: false (or remove organization() entirely).
  3. Strip org UI: account switcher create/list, organization picker, invitations, org dashboard routes, join page.
  4. Force billing to BillingReference.USER and drop org / per-seat checkout options.
  5. Mirror the same cuts on mobile and the extension if you ship them.
  6. Leave org tables in the database unless you need a clean schema. Do this before launch if you can.

Use-cases

Product shapeRecommendation
B2C SaaS (one user, one account)Disable organizations
B2B with teams / workspacesKeep organizations
B2B, no personal workspaceDisable personal accounts
Single tenant, invite-only workspaceKeep orgs, disable self-serve create (see Soft path)
Subdomain per customerKeep orgs, see Subdomain multi-tenancy

TurboStarter does not ship a single env flag for this (unlike some starters). You own the code: gate Better Auth, then remove the surfaces that assume tenants exist.

Personal account vs organization

A personal account is the authenticated user. Dashboard routes live under pathsConfig.dashboard.user (/dashboard, /dashboard/settings, …). Session activeOrganizationId is null.

An organization is a separate tenant at /dashboard/{slug} with members, invitations, RBAC, and optional org-scoped billing. Signup does not create an organization.

Soft vs hard disable

ApproachWhat you doReversibleBest when
SoftKeep organization() plugin, block create, hide UI, force user billingEasyYou might re-enable teams later
HardRemove plugin + clients, unmount org API/UI, simplify billing and hooksMore workYou are sure the product is personal-only

Both approaches should enforce rules on the server, not only in React. Closing the UI while leaving Better Auth endpoints open is not enough.

Block organization creation on the server

Open packages/auth/src/server.ts where the Better Auth organization plugin is registered.

Soft disable

Keep the plugin, but stop self-serve creation:

packages/auth/src/server.ts
organization({
  allowUserToCreateOrganization: false, 
  sendInvitationEmail: async ({ invitation, inviter, organization }, request) => {
    // ...
  },
  ac,
  roles,
  organizationHooks: hooks.organization,
}),

allowUserToCreateOrganization accepts a boolean or an async function (for example, only allow admins). Default is true.

If you also want invite-only workspaces later, keep invitations and the join page, and only hide create. For true personal-only, also remove or dead-end invitations (next steps).

Hard disable

Remove the organization({ ... }) plugin from the server config, and remove organizationClient from every auth client:

  • apps/web/src/lib/auth/client.ts
  • Mobile and extension auth clients that call organizationClient()
  • Re-exports in packages/auth/src/client/web.ts / mobile.ts if nothing else needs them

After hard removal, Better Auth org endpoints and authClient.organization.* / useListOrganizations are gone. Update any imports that still reference them.

Remove organization UI entry points (web)

These surfaces are how users discover orgs today. Strip or simplify them so the product only shows a personal account.

Account switcher

apps/web/src/modules/organization/account-switcher.tsx is rendered from the dashboard sidebar (modules/common/layout/dashboard/sidebar). It lists organizations, switches context, and opens CreateOrganizationModal.

For personal-only:

  • Replace AccountSwitcher with a simple user header (avatar + name), or
  • Keep the component but remove the org list, create CTA, and CreateOrganizationModal

Personal dashboard home

apps/web/src/app/[locale]/dashboard/(user)/page.tsx currently renders invitations and the org picker:

apps/web/src/app/[locale]/dashboard/(user)/page.tsx
import { UserOrganizationInvitationsBanner } from "~/modules/organization/invitations/user/user-organization-invitations";
import { OrganizationPicker } from "~/modules/organization/organization-picker";

export default function UserPage() {
  return (
    <>
      <UserOrganizationInvitationsBanner />
      <OrganizationPicker />
    </>
  );
}

Replace this with your real personal home (product feed, empty state, onboarding CTA). See Onboarding flow if you need a post-signup wizard without an organization step.

Org dashboard, join, and admin

Redirect or delete:

SurfacePath
Org dashboardapps/web/src/app/[locale]/dashboard/[organization]/
Join invitationapps/web/src/app/[locale]/auth/join/
Admin organizationsapps/web/src/app/[locale]/admin/organizations/ and related modules

Add redirects from /dashboard/[organization] and /auth/join to /dashboard so old links and bookmarks fail closed.

For a hard disable, also stop mounting the Hono org router:

packages/api/src/index.ts
.route("/organizations", organizationRouter) 

And remove or stop using packages/api/src/modules/organization/** plus admin org modules.

Force personal billing

Billing already supports a user as the customer. Org checkout is optional via the pricing "on behalf of" control.

  1. In apps/web/src/modules/billing/pricing/controls/reference-selector.tsx (and controls/index.tsx), remove org options or delete ReferenceSelector so checkout always uses BillingReference.USER and referenceId = user.id.
  2. Keep user billing pages under /dashboard/settings/billing (they already pass BillingReference.USER).
  3. Remove or ignore /dashboard/[organization]/settings/billing once org routes are gone.
  4. Optionally drop BillingType.PER_SEAT variants from packages/billing/shared config. Per-seat plans are filtered to organizations in getFilteredPlans. Without orgs they only add noise.

Seat sync hooks under packages/auth/src/hooks/organization/ only matter while the org plugin and member mutations exist. Soft disable can leave them; hard disable should remove those hooks and related helpers such as syncSubscriptionSeats.

See Billing overview (B2C vs B2B) and Per-seat if you currently sell team seats.

Optional: leave the schema, skip the seed org

You do not need a migration to ship personal-only. Tables organization, member, invitation, and session.active_organization_id can stay unused (same idea as keeping unused team tables in other starters).

If you want a cleaner local DB:

  • Stop creating the demo org in packages/auth/src/scripts/seed.ts
  • Drop org tables later only if you are sure you will not re-enable multi-tenancy

Prefer deciding this before production data. Switching from org-scoped rows (organizationId) to user-scoped rows (userId) after launch means a real data migration.

Your own product tables should reference userId for personal-only apps, not organizationId. See Data model for how tenant scoping works when orgs stay on.

Mobile and extension

If you keep apps/mobile or apps/extension, apply the same product rule there. Shared auth still exposes organizationClient until you remove it.

Mobile

  • Org modules under apps/mobile/src/modules/organization/
  • Org dashboard routes under apps/mobile/src/app/dashboard/organization/
  • Root redirect that branches on activeOrganizationId (apps/mobile/src/app/index.tsx)

Extension

  • User navigation that shows personal vs active org (apps/extension/src/modules/user/user-navigation.tsx)
  • Any deep links into web /dashboard/{slug}

Point everything at the personal dashboard paths. Docs: Organizations on mobile, Organizations in the extension.

Checklist

After the change, verify:

  • New signup reaches /dashboard with no create-org prompt
  • Account switcher (or header) shows only the user, no "Create organization"
  • authClient.organization.create fails (soft) or does not exist (hard)
  • /dashboard/some-slug and /auth/join redirect or 404
  • Checkout and billing portal use user.id only
  • Mobile / extension (if shipped) never set or require activeOrganizationId
  • Admin UI no longer lists organizations (or the section is removed)

Troubleshooting

See the troubleshooting section below for common issues and how to fix them.

SymptomWhat to check
Users can still create orgs via API / clientallowUserToCreateOrganization on the server, or plugin still registered
Create button still visibleaccount-switcher.tsx, organization-picker.tsx, CreateOrganizationModal
Pricing still offers "on behalf of" an orgReferenceSelector / pricing controls
Session still has activeOrganizationIdSoft: clear on personal routes; hard: plugin removed, redirect old org URLs
Type errors on organizationClientRemove plugin from all auth clients and fix imports
Per-seat plans still show for personal checkoutgetFilteredPlans / remove PER_SEAT variants from billing config

How is this guide?

Last updated on

On this page

Ship your startup everywhere. In minutes.Try TurboStarter