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

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

Create the server settings configuration

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

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

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

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

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

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>;
}
 

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

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

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

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

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 parseServerSettingConfig function.

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

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

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.

How is this guide?

Last updated on 1/18/2026