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 problem
When you want to implement a settings section in your application, you need to:
- Define the page endpoints
- Fetch the settings from the database
- Render a settings form with
react-hook-form
to handle type validation - Handle the form submission to the database
- Request the setting to the database to reuse it in the appropriate place
As you have to do this for each settings page, it leads to a lot of duplicated code.
For those reasons, manual implementation is painful, error-prone, hard to maintain and you have to handle the UI by yourself.
The solution
To solve this, we chose an architecture close to a MVC (Model-View-Controller) architecture.

Settings architecture schema
That way, you just have to define your settings in a type-safe object and you are good to go. The settings UI, storage management and settings fetching is handled by the package.
Features
- Centralized Configuration: Define all settings in a single object
- Type Safety: Full typescript support with autocompletion for setting keys
- Database Connection: Automatically fetch and save settings to the appropriate database tables
- Schema Validation: Uses Zod schemas for validation before database update
- Form Generation: Automatically generate forms
- Navigation: Built-in navigation UI
- Customization: Easily add custom UI and storage providers
Key Benefits
For developers, it makes it easier to maintain and add new settings in no time.
For applications, it provides a consistent settings UI across the application.
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 Settings Configuration
import { createSettingModel } from '@kit/settings'; import { getDBClient } from '~/lib/get-db-client'; import { z } from 'zod'; // other imports... export const settingsModel = createSettingModel( 'your-settings-id', getDBClient, { // 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... }, [ { 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, SettingsPages } from '@kit/settings/ui'; import { settingsModel } from '~/config/settings.config'; 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"> <SettingsPages model={settingsModel} params={awaitedParams} /> </div> </div> ); } export default SettingsPage;
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 { settingsModel } from '~/config/settings.config';
export default async function SomewhereInYourCodebase() {
const { 'user_name': userName } = await settingsModel.getSettings(['user_name']);
return <div>{userName}</div>;
}
settingsModel
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.
Add the setting to the settings model
We gonna use the user_settings
storage provider. See the Storage section for more information.
import { settingsModel } from '~/config/settings.config'; import { z } from 'zod'; export const settingsModel = createSettingModel( 'your-settings-id', getDBClient, { // Your existing settings... use_full_width: { schema: z.boolean().default(false), storage: 'user_settings', }, // Your existing settings... }, // Your existing settings pages... [ { 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 { settingsModel } from '~/config/settings.config';
export default async function SomewhereInYourCodebase({children}: React.PropsWithChildren) {
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 StorageProvider
s.
StorageProvider
Prop | Type | Default |
---|---|---|
fetchFromStorage* | function | |
saveToStorage* | function |
By default, two storage providers are present:
user_settings
: Stores settings in theuser_setting
table as row entries.user_attributes
: Stores settings in theuser
table as column entries.
Add new storage providers
You can pass new storage providers to the createSettingModel
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 '~/lib/get-db-client'; import { z } from 'zod'; // other imports... const yourNewStorageProvider: StorageProvider = { fetchFromStorage: async (keys: string[]) => { const db = await getDBClient(); // do your things to fetch the settings record return settingsRecord; // {'key1': value1, 'key2': value2, ...} }, saveToStorage: async (values: Record<string, any>) => { const db = await getDBClient(); // save the settings where you want }, }; export const settingsModel = createSettingModel( 'your-settings-id', getDBClient, {}, // schemas [], // ui config { your_new_storage_provider: yourNewStorageProvider, } );
Now you can use your new storage provider in your settings model.
import { settingsModel } from '~/config/settings.config'; // ... export const settingsModel = createSettingModel( 'your-settings-id', getDBClient, { // ... your_new_setting: { schema: z.string().default(''), storage: 'your_new_storage_provider', }, // ... }, {}, // ui config { your_new_storage_provider: yourNewStorageProvider, } );
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.
Prop | Type | Default |
---|---|---|
user_media* | function | |
organization_media* | function | |
text* | function | |
textarea* | function | |
phone* | function | |
select* | function | |
boolean* | FC<BooleanInputProps> | |
number* | function | |
color* | function | |
time* | function | |
radio* | function | |
theme* | FC<BaseInputProps> |
Implement your own documentation website with Fumadocs.
A type-safe keybindings system for React applications.
How is this guide?
Last updated on 10/17/2025