For the complete documentation index, see llms.txt. Prefer markdown by appending.mdto documentation URLs or sendingAccept: 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
- Decide soft vs hard disable (keep the Better Auth plugin gated, or remove it).
- Block create on the server with
allowUserToCreateOrganization: false(or removeorganization()entirely). - Strip org UI: account switcher create/list, organization picker, invitations, org dashboard routes, join page.
- Force billing to
BillingReference.USERand drop org / per-seat checkout options. - Mirror the same cuts on mobile and the extension if you ship them.
- Leave org tables in the database unless you need a clean schema. Do this before launch if you can.
Use-cases
| Product shape | Recommendation |
|---|---|
| B2C SaaS (one user, one account) | Disable organizations |
| B2B with teams / workspaces | Keep organizations |
| B2B, no personal workspace | Disable personal accounts |
| Single tenant, invite-only workspace | Keep orgs, disable self-serve create (see Soft path) |
| Subdomain per customer | Keep 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
| Approach | What you do | Reversible | Best when |
|---|---|---|---|
| Soft | Keep organization() plugin, block create, hide UI, force user billing | Easy | You might re-enable teams later |
| Hard | Remove plugin + clients, unmount org API/UI, simplify billing and hooks | More work | You 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:
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.tsif 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
AccountSwitcherwith 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:
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:
| Surface | Path |
|---|---|
| Org dashboard | apps/web/src/app/[locale]/dashboard/[organization]/ |
| Join invitation | apps/web/src/app/[locale]/auth/join/ |
| Admin organizations | apps/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:
.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.
- In
apps/web/src/modules/billing/pricing/controls/reference-selector.tsx(andcontrols/index.tsx), remove org options or deleteReferenceSelectorso checkout always usesBillingReference.USERandreferenceId = user.id. - Keep user billing pages under
/dashboard/settings/billing(they already passBillingReference.USER). - Remove or ignore
/dashboard/[organization]/settings/billingonce org routes are gone. - Optionally drop
BillingType.PER_SEATvariants frompackages/billing/sharedconfig. Per-seat plans are filtered to organizations ingetFilteredPlans. 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
/dashboardwith no create-org prompt - Account switcher (or header) shows only the user, no "Create organization"
-
authClient.organization.createfails (soft) or does not exist (hard) -
/dashboard/some-slugand/auth/joinredirect or 404 - Checkout and billing portal use
user.idonly - 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.
| Symptom | What to check |
|---|---|
| Users can still create orgs via API / client | allowUserToCreateOrganization on the server, or plugin still registered |
| Create button still visible | account-switcher.tsx, organization-picker.tsx, CreateOrganizationModal |
| Pricing still offers "on behalf of" an org | ReferenceSelector / pricing controls |
Session still has activeOrganizationId | Soft: clear on personal routes; hard: plugin removed, redirect old org URLs |
Type errors on organizationClient | Remove plugin from all auth clients and fix imports |
| Per-seat plans still show for personal checkout | getFilteredPlans / remove PER_SEAT variants from billing config |
How is this guide?
Last updated on