The @kit/settings package is a type-safe settings management system designed to simplify the development of settings pages. It provides a declarative approach to define, store, and render settings with built-in support for multiple storage providers, form validation, and UI rendering.
The settings configuration is made of 3 objects from :
- a UI config object that control the ui (in the
apps/dashboard/config/settings.ui.config.ts=> app scope) - a schema config object that define zod schemas used for type checking (in the
kit/shared/src/config/settings.schema.config.ts=> repo scope) - a server config object that contains the database controllers (in the
kit/shared/src/config/settings.server.config.ts=> repo scope)
Setup
If you are using the apps/dashboard application, you can skip this section and go to the Add a new settings page section.
Installation
Create the server settings configuration
import {
organizationAttributesStorage,
organizationSettingsStorage,
} from '@kit/organization/shared/organization-setting-provider';
import { parseServerSettingConfig } from '@kit/settings/server-config';
export const settingsServer = parseServerSettingConfig({
providers: {
organization_attributes: organizationAttributesStorage,
organization_settings: organizationSettingsStorage,
},
});
Create the schema settings configuration
import { parseSchemaSettingConfig } from '@kit/settings/schema-config'; import { z } from 'zod'; export const settingsSchemas = parseSchemaSettingConfig({ schema: { // User Profile Settings user_profile_url: { schema: z.string().nullable().default(null), storage: 'user_attributes', }, user_name: { schema: z.string().default(''), storage: 'user_attributes', }, user_phone: { schema: z.string().nullable().default(null), storage: 'user_attributes', }, user_bio: { schema: z.string().max(500).default(''), storage: 'user_settings', }, // Appearance Settings // other settings... }, });
Create the schema settings configuration
import type { EXTRA_INPUTS } from '~/app/dashboard/[slug]/settings/[[...settings]]/page-client'; import type { REGISTERED_SETTINGS_INPUTS } from '@kit/settings/www/ui'; import { parseUISettingConfig } from '@kit/settings/ui-config'; export const settingsUI = parseUISettingConfig< typeof settingsSchemas.schema, typeof REGISTERED_SETTINGS_INPUTS & typeof EXTRA_INPUTS >({ ui: [ { group: 'index', label: 'About you', settingsPages: [ { // match : "/" endpoint slug: 'index', icon: <Icon.user className="h-4 w-4" />, title: 'Profile', description: 'User profile settings', settings: [ { type: 'form', id: 'profile-form', header: ( <> <div className="text-2xl font-bold">Profile</div> <Muted>Edit your profile information here.</Muted> </> ), submitButton: { text: 'Save Profile', }, settings: [ { type: 'user_media', slug: 'user_profile_url', label: 'Profile URL', triggerClassName: 'h-48 w-48 rounded-full', imageClassName: 'w-full h-full object-cover', placeholder: <AvatarPlaceholder className="size-full text-6xl" />, }, { type: 'text', slug: 'user_name', label: 'Name', description: 'May appear when you are mentioned or when you add content to the platform', }, { type: 'textarea', slug: 'user_bio', label: 'Bio', description: 'Tell us more about yourself', }, { type: 'ui', render: <Separator />, // to add custom ui }, { type: 'phone', slug: 'user_phone', label: 'Phone', description: 'Used for authentication purposes only', }, ], }, ], }, // set you other pages here {...}, ], }, // other page groups here {...}, ], });
Add the settings pages
'use server'; import React from 'react'; import { SettingSaveButtonAnchor } from '@kit/settings/ui'; import { ClientSettingsPage } from './page-client'; async function SettingsPage({ params, }: { params: { settings: string[]; }; }): Promise<React.JSX.Element> { const awaitedParams = await params; return ( <div className="flex flex-col"> <div className="flex h-24">{/* your top bar component */} <SettingSaveButtonAnchor />{/* display a save button outside of the settings pages */} </div> <div className="flex-1"> <ClientSettingsPage awaitedParams={awaitedParams} /> </div> </div> ); } export default SettingsPage;
'use client'; import { OrganizationSettingMedia } from '@kit/organization/web/ui'; import { SettingsPages } from '@kit/settings/www/ui'; import { settingsSchemas } from '@kit/shared/config/settings.schema.config'; import { cn } from '@kit/utils'; import { SettingWrapperComponent } from '@kit/utils/quick-form'; import { QuestionSelectInput } from '~/components/settings/question-select-input'; import { settingsUI } from '~/config/settings.ui.config'; import { useCtxTrpc } from '~/trpc/trpc-client-provider'; export const EXTRA_INPUTS = { organization_media: OrganizationSettingMedia, question_select: QuestionSelectInput }; const Wrapper: SettingWrapperComponent = ({ className, header, children }) => { return ( <div className={cn('space-y-4 px-4 py-8 sm:px-8', className)}> {header && <div className="space-y-2">{header}</div>} {children} </div> ); }; export function ClientSettingsPage({ awaitedParams }: { awaitedParams: any }) { const { clientTrpc } = useCtxTrpc(); return ( <SettingsPages inputs={EXTRA_INPUTS} clientTrpc={clientTrpc} settingsUI={settingsUI} settingsSchemas={settingsSchemas} Wrapper={Wrapper} params={awaitedParams} /> ); }
Use the settings sidebar
First you need to get the ui config from the server.
'use server'; import React from 'react'; import { settingsModel } from '~/config/settings.config'; import { SettingsSidebar } from '~/components/settings-sidebar'; export default async function OrganizationLayout({ children, }: React.PropsWithChildren): Promise<React.JSX.Element> { // we must serialize the ui config to use it client side const uiConfig = settingsModel.getSerializedUIConfig(); return ( <div className="flex h-screen flex-row overflow-hidden"> <SettingsSidebar uiConfig={uiConfig} /> <div className="flex-1">{children}</div> </div> ); }
Then you can use the settings sidebar component in your layout.
'use client'; import React from 'react'; import { SettingsNavigation, type UIConfig } from '@kit/settings'; import { ScrollArea } from '@kit/ui/scroll-area'; export default function SettingsSidebar({ uiConfig }: { uiConfig: UIConfig<any>[] }): React.JSX.Element { return ( <ScrollArea className="h-full"> <SettingsNavigation uiConfig={uiConfig} basePath={"/settings"} /> </ScrollArea> ); }
Use the settings in your codebase
import { settingsSchemas } from '../config/settings.schema.config';
import { settingsServer } from '../config/settings.server.config';
import { getDBClient } from '@kit/shared/server/get-db-client';
import { SettingServerModel } from '@kit/settings/shared/server/setting-server-model';
export default async function SomewhereInYourCodebase() {
const settingsModel = new SettingServerModel(getDBClient, serverConfig, schemaConfig);
const { 'user_name': userName } = await settingsModel.getSettings(['user_name']);
return <div>{userName}</div>;
}
SettingServerModel works only on the server side.
Add new settings
Now that you have your settings model, you can add new settings to it.
Let's try to add a use_full_width setting to the settings model.
We gonna use the user_settings storage provider. See the Storage section for more information.
Update the schema settings configuration
import { parseSchemaSettingConfig } from '@kit/settings/schema-config';
import { z } from 'zod';
export const settingsSchemas = parseSchemaSettingConfig({
schema: {
// Your existing settings...
use_full_width: {
schema: z.boolean().default(false),
storage: 'user_settings',
},
// Your existing settings...
},
});
Update the schema settings configuration
import type { EXTRA_INPUTS } from '~/app/dashboard/[slug]/settings/[[...settings]]/page-client'; import type { REGISTERED_SETTINGS_INPUTS } from '@kit/settings/www/ui'; import { parseUISettingConfig } from '@kit/settings/ui-config'; export const settingsUI = parseUISettingConfig< typeof settingsSchemas.schema, typeof REGISTERED_SETTINGS_INPUTS & typeof EXTRA_INPUTS >({ ui: [ { group: 'index', label: 'About you', settingsPages: [ { // match : "/" endpoint slug: 'index', icon: <Icon.user className="h-4 w-4" />, title: 'Profile', description: 'User profile settings', settings: [ // Your existing settings... { type: 'form', id: 'profile-form', header: ( <> <div className="text-2xl font-bold">Profile</div> <Muted>Edit your profile information here.</Muted> </> ), submitButton: { text: 'Save Profile', }, settings: [ // Your existing settings... { type: 'phone', slug: 'user_phone', label: 'Phone', description: 'Used for authentication purposes only', }, { type: 'checkbox', slug: 'use_full_width', label: 'Use full width', description: 'Always display the application in full width on mobile and desktop', }, // Your existing settings... ], }, ], }, // set you other pages here {...}, ], } // other page groups here {...}, ], });
The settings UI and database storage is already handled, just use your new use_full_width setting
import { settingsSchemas } from '../config/settings.schema.config';
import { settingsServer } from '../config/settings.server.config';
import { getDBClient } from '@kit/shared/server/get-db-client';
import { SettingServerModel } from '@kit/settings/shared/server/setting-server-model';
export default async function SomewhereInYourCodebase({children}: React.PropsWithChildren) {
const settingsModel = new SettingServerModel(getDBClient, serverConfig, schemaConfig);
const { checkbox } = await settingsModel.getSettings(['checkbox']);
return <div className={cn(checkbox ? 'w-full' : 'w-1/2')}>
{children}
</div>;
}
Storage
The database storage is handled by the StorageProviders.
StorageProvider
| Prop | Type | Default |
|---|---|---|
fetchFromStorage* | function | |
saveToStorage* | function |
By default, two storage providers are present:
user_settings: Stores settings in theuser_settingtable as row entries.user_attributes: Stores settings in theusertable as column entries.
Add new storage providers
You can pass new storage providers to the parseServerSettingConfig function.
Must of the time you gonna save you settings to the database. But you are free to do whatever you want (localStorage, sessionStorage, use another api service, ...).
import { createSettingModel, type StorageProvider } from '@kit/settings'; import { getDBClient } from '@kit/shared/server/get-db-client'; import { z } from 'zod'; // other imports... const yourNewStorageProvider: StorageProvider = { fetchFromStorage: async (rawKeys: string[], getDB) => { const db = await getDB(); // do your things to fetch the settings record return settingsRecord; // {'key1': value1, 'key2': value2, ...} }, saveToStorage: async (rawValues: Record<string, any>, getDB) => { const db = await getDB(); // save the settings where you want }, }; export const settingsServer = parseServerSettingConfig({ providers: { your_new_storage_provider: yourNewStorageProvider, }, });
Now you can use your new storage provider in your settings model.
import { parseSchemaSettingConfig } from '@kit/settings/schema-config';
import { z } from 'zod';
export const settingsSchemas = parseSchemaSettingConfig({
schema: {
// ...
your_new_setting: {
schema: z.string().default(''),
storage: 'your_new_storage_provider',
},
// ...
},
});
Inputs
Under the hood, we are using the Quick Form component to generate the settings form.
By default, we add the following inputs to the QuickForm component :
user_media: UserSettingMedia to manage user mediaorganization_media: OrganizationSettingMedia to manage organization media
Check the Media Manager page to learn more about the user_media and organization_media inputs.
Implement your own documentation website with Fumadocs.
Why the settings package is so powerful.
How is this guide?
Last updated on 1/18/2026