Settings Navigation Configuration in the Tanstack Start Supabase SaaS Kit

Configure the dedicated settings-section sidebar, which adapts to whether a personal or team account is active.

The settings section has its own sidebar, separate from the main application navigation. Its menu is built from the active account at apps/web/src/components/settings/settings-navigation.tsx: a personal account shows a "Your Account" group, while a team shows a group labelled with the team's name.

How it works

getSettingsNavigationConfig(account) inspects account.is_personal_account and returns the appropriate grouped menu. It is consumed by both the desktop SettingsSidebar and the mobile navigation so they stay in sync.

import { CreditCard, Settings, User, Users } from 'lucide-react';
import * as z from 'zod';
import { NavigationConfigSchema } from '@kit/ui/navigation-schema';
import featureFlagsConfig from '#/config/feature-flags.config.ts';
import pathsConfig from '#/config/paths.config.ts';
const iconClasses = 'w-4';
export function getSettingsNavigationConfig(account: {
name: string | null;
is_personal_account: boolean | null;
}) {
const routes = account.is_personal_account
? [getPersonalAccountGroup()]
: [getTeamAccountGroup(account.name)];
return NavigationConfigSchema.parse({
routes,
style: import.meta.env.VITE_USER_NAVIGATION_STYLE,
sidebarCollapsed: import.meta.env.VITE_HOME_SIDEBAR_COLLAPSED,
sidebarCollapsedStyle: import.meta.env.VITE_SIDEBAR_COLLAPSIBLE_STYLE,
});
}

Personal account group

function getPersonalAccountGroup() {
const children = [
{
label: 'common.routes.profile',
path: pathsConfig.app.settings,
Icon: <User className={iconClasses} />,
highlightMatch: `${pathsConfig.app.settings}$`,
},
featureFlagsConfig.enablePersonalAccountBilling
? {
label: 'common.routes.billing',
path: pathsConfig.app.settingsBilling,
Icon: <CreditCard className={iconClasses} />,
}
: undefined,
].filter((route) => !!route);
return { label: 'common.routes.yourAccount', children };
}

Team account group

The group label is the active team's name; billing respects the team billing feature flag.

function getTeamAccountGroup(accountName: string | null) {
const children = [
{
label: 'common.routes.settings',
path: pathsConfig.app.settings,
Icon: <Settings className={iconClasses} />,
highlightMatch: `${pathsConfig.app.settings}$`,
},
{
label: 'common.routes.members',
path: pathsConfig.app.settingsMembers,
Icon: <Users className={iconClasses} />,
},
featureFlagsConfig.enableTeamAccountBilling
? {
label: 'common.routes.billing',
path: pathsConfig.app.settingsBilling,
Icon: <CreditCard className={iconClasses} />,
}
: undefined,
].filter((route) => !!route);
return { label: accountName ?? 'common.routes.settings', children };
}

Adding a settings route

Add an item to the relevant group. New settings pages are static and live under /settings:

function getTeamAccountGroup(accountName: string | null) {
const children = [
// ... existing items
{
label: 'Integrations',
path: '/settings/integrations',
Icon: <Plug className={iconClasses} />,
},
].filter((route) => !!route);
return { label: accountName ?? 'common.routes.settings', children };
}

Team-only settings pages should redirect a personal active account away, e.g. in the route's beforeLoad:

beforeLoad: ({ context }) => {
if (context.workspace.account.is_personal_account) {
throw redirect({ to: pathsConfig.app.settings });
}
},

Route Properties

PropertyTypeRequiredDescription
labelstringYesDisplay text (i18n key or, for a team, the team name)
pathstringYesStatic settings path
IconReactNodeNoLucide icon component
highlightMatchstringNoRegex for active highlighting (use on the index item)

Best Practices

  1. Gate by account type: use account.is_personal_account to decide which group/items to show, not the URL.
  2. Use pathsConfig: reference app.settings* paths instead of string literals.
  3. Respect feature flags: gate billing items on enablePersonalAccountBilling / enableTeamAccountBilling.
  4. Guard team-only pages: redirect a personal active account to /settings.