Features
PreviousNext

Settings

Manage your application settings with ease.

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.

The solution

To solve this, we chose an architecture close to a MVC (Model-View-Controller) architecture.

Settings architecture schema

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

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

pnpm add '@kit/settings@workspace:*'

Create Settings Configuration

config/settings.config.tsx
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

app/settings/[[...settings]]/page.tsx
'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.

app/dashboard/[slug]/layout.tsx
'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.

components/settings-sidebar.tsx
'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

somewhere-in-your-codebase.tsx
import { settingsModel } from '~/config/settings.config';
 
export default async function SomewhereInYourCodebase() {
    const { 'user_name': userName } = await settingsModel.getSettings(['user_name']);
    return <div>{userName}</div>;
}
 

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.

config/settings.config.tsx
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

somewhere-in-your-codebase.tsx
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 StorageProviders.

StorageProvider

PropTypeDefault
fetchFromStorage*
function
saveToStorage*
function

By default, two storage providers are present:

  • user_settings: Stores settings in the user_setting table as row entries.
  • user_attributes: Stores settings in the user table as column entries.

Add new storage providers

You can pass new storage providers to the createSettingModel function.

config/settings.config.tsx
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.

config/settings.config.tsx
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

By default, we add the following inputs to the QuickForm component :

  • user_media: UserSettingMedia to manage user media
  • organization_media: OrganizationSettingMedia to manage organization media

Check the Media Manager page to learn more about the user_media and organization_media inputs.

PropTypeDefault
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>

How is this guide?

Last updated on 10/17/2025