Skip to main content
The config.json file handles basic feature toggles. For deeper customizations, modify the Zustand store, theme configuration, or component props directly.

Understanding the Customization Architecture

The React Native UI Kit Builder uses these main files for customization:
FilePurposeWhen to Modify
config.jsonFeature flags and configuration constantsFunctional changes (enable/disable features)
store.tsZustand store for state managementRuntime configuration updates
Theme objectColors, typography, and stylingUI/visual changes
The config.json file is the source of truth for your Builder configuration. You can update it manually or by scanning a QR code with new settings.

Using the Configuration Store

The Zustand store manages your Builder configuration at runtime. Access it anywhere in your app to read or update settings.

Reading Configuration

import { useConfig } from './src/config/store';

const MyComponent = () => {
  // Access entire settings object
  const settings = useConfig((state) => state.settings);
  
  // Access specific feature category
  const chatFeatures = useConfig((state) => state.settings.chatFeatures);
  
  // Access style configuration
  const styleConfig = useConfig((state) => state.settings.style);
  
  return (/* ... */);
};

Updating Configuration at Runtime

import { useConfigStore } from './src/config/store';

// Update a specific setting
const updateFeature = () => {
  const store = useConfigStore.getState();
  store.updateSettings({
    ...store.settings,
    chatFeatures: {
      ...store.settings.chatFeatures,
      coreMessagingExperience: {
        ...store.settings.chatFeatures.coreMessagingExperience,
        photosSharing: false,
      },
    },
  });
};
Runtime changes to the store are not persisted by default. Use AsyncStorage to save and restore configurations.

Theme Customization

Applying Builder Theme to UI Kit

The Builder configuration includes style settings that should be applied to the CometChat UI Kit theme:
import React from 'react';
import { CometChatThemeProvider } from '@cometchat/chat-uikit-react-native';
import { useConfig } from './src/config/store';

const App = () => {
  const styleConfig = useConfig((state) => state.settings.style);

  const theme = {
    light: {
      color: {
        primary: styleConfig.color.brandColor,
        textPrimary: styleConfig.color.primaryTextLight,
        textSecondary: styleConfig.color.secondaryTextLight,
        background: '#FFFFFF',
        border: '#E8E8E8',
      },
      typography: {
        fontFamily: getFontFamily(styleConfig.typography.font),
      },
    },
    dark: {
      color: {
        primary: styleConfig.color.brandColor,
        textPrimary: styleConfig.color.primaryTextDark,
        textSecondary: styleConfig.color.secondaryTextDark,
        background: '#141414',
        border: '#3D3D3D',
      },
      typography: {
        fontFamily: getFontFamily(styleConfig.typography.font),
      },
    },
  };

  return (
    <CometChatThemeProvider theme={theme}>
      {/* Your app components */}
    </CometChatThemeProvider>
  );
};

Custom Color Palette

Override the default colors by modifying the theme object:
const customTheme = {
  light: {
    color: {
      primary: '#FF6B6B',        // Custom brand color
      textPrimary: '#2D3436',    // Custom primary text
      textSecondary: '#636E72',  // Custom secondary text
      success: '#00B894',        // Success state
      error: '#D63031',          // Error state
      warning: '#FDCB6E',        // Warning state
    },
  },
  dark: {
    color: {
      primary: '#FF6B6B',
      textPrimary: '#DFE6E9',
      textSecondary: '#B2BEC3',
      success: '#00B894',
      error: '#FF7675',
      warning: '#FFEAA7',
    },
  },
};

Custom Font Integration

Step 1: Add Font Files

Add your custom font files to the appropriate platform directories:
  • iOS: ios/<App>/Resources/Fonts/
  • Android: android/app/src/main/assets/fonts/
For React Native CLI projects, create or update react-native.config.js:
module.exports = {
  assets: ['./assets/fonts'],
};
Then run:
npx react-native-asset

Step 3: Map Font Family

Create a font mapping utility:
import { Platform } from 'react-native';

const FONT_MAP = {
  'roboto': {
    regular: Platform.OS === 'ios' ? 'Roboto-Regular' : 'roboto_regular',
    medium: Platform.OS === 'ios' ? 'Roboto-Medium' : 'roboto_medium',
    bold: Platform.OS === 'ios' ? 'Roboto-Bold' : 'roboto_bold',
  },
  'inter': {
    regular: Platform.OS === 'ios' ? 'Inter-Regular' : 'inter_regular',
    medium: Platform.OS === 'ios' ? 'Inter-Medium' : 'inter_medium',
    bold: Platform.OS === 'ios' ? 'Inter-Bold' : 'inter_bold',
  },
  'your-custom-font': {
    regular: Platform.OS === 'ios' ? 'YourFont-Regular' : 'your_font_regular',
    medium: Platform.OS === 'ios' ? 'YourFont-Medium' : 'your_font_medium',
    bold: Platform.OS === 'ios' ? 'YourFont-Bold' : 'your_font_bold',
  },
};

export const getFontFamily = (fontName: string) => {
  return FONT_MAP[fontName] || FONT_MAP['roboto'];
};

Component-Level Customizations

Conditional Rendering Based on Features

Use the configuration store to conditionally render UI elements:
import { useConfig } from './src/config/store';

const MessageComposer = () => {
  const chatFeatures = useConfig((state) => state.settings.chatFeatures);
  const { coreMessagingExperience, deeperUserEngagement } = chatFeatures;

  return (
    <View>
      {/* Always show text input */}
      <TextInput placeholder="Type a message..." />
      
      {/* Conditionally show attachment options */}
      {coreMessagingExperience.photosSharing && (
        <PhotoAttachmentButton />
      )}
      
      {coreMessagingExperience.fileSharing && (
        <FileAttachmentButton />
      )}
      
      {deeperUserEngagement.voiceNotes && (
        <VoiceNoteButton />
      )}
      
      {deeperUserEngagement.stickers && (
        <StickerButton />
      )}
    </View>
  );
};

Customizing Message Options

Control which message options appear based on configuration:
const MessageOptions = ({ message }) => {
  const chatFeatures = useConfig((state) => state.settings.chatFeatures);
  const { coreMessagingExperience, deeperUserEngagement, moderatorControls } = chatFeatures;

  const options = [];

  if (coreMessagingExperience.quotedReplies) {
    options.push({ label: 'Reply', action: 'reply' });
  }

  if (coreMessagingExperience.threadConversationAndReplies) {
    options.push({ label: 'Reply in Thread', action: 'thread' });
  }

  if (deeperUserEngagement.reactions) {
    options.push({ label: 'React', action: 'react' });
  }

  if (coreMessagingExperience.editMessage && message.sender.uid === currentUser.uid) {
    options.push({ label: 'Edit', action: 'edit' });
  }

  if (coreMessagingExperience.deleteMessage && message.sender.uid === currentUser.uid) {
    options.push({ label: 'Delete', action: 'delete' });
  }

  if (moderatorControls.reportMessage) {
    options.push({ label: 'Report', action: 'report' });
  }

  return <OptionsMenu options={options} />;
};

Persisting Configuration

Save Configuration to AsyncStorage

import AsyncStorage from '@react-native-async-storage/async-storage';
import { useConfigStore } from './src/config/store';

const CONFIG_KEY = '@cometchat_builder_config';

export const saveConfig = async () => {
  try {
    const config = useConfigStore.getState();
    await AsyncStorage.setItem(CONFIG_KEY, JSON.stringify(config));
  } catch (error) {
    console.error('Failed to save config:', error);
  }
};

export const loadConfig = async () => {
  try {
    const savedConfig = await AsyncStorage.getItem(CONFIG_KEY);
    if (savedConfig) {
      const parsed = JSON.parse(savedConfig);
      useConfigStore.getState().updateConfig(parsed);
    }
  } catch (error) {
    console.error('Failed to load config:', error);
  }
};

Auto-save on Configuration Changes

import { useEffect } from 'react';
import { useConfigStore } from './src/config/store';

const ConfigPersistence = () => {
  const config = useConfigStore((state) => state);

  useEffect(() => {
    // Debounce saves to avoid excessive writes
    const timeoutId = setTimeout(() => {
      saveConfig();
    }, 1000);

    return () => clearTimeout(timeoutId);
  }, [config]);

  return null;
};

Layout Customization

Dynamic Tab Configuration

import { useConfig } from './src/config/store';

const TabNavigator = () => {
  const layout = useConfig((state) => state.settings.layout);
  const { tabs, withSideBar } = layout;

  if (!withSideBar) {
    return <SingleChatView />;
  }

  return (
    <Tab.Navigator>
      {tabs.includes('chats') && (
        <Tab.Screen name="Chats" component={ChatsScreen} />
      )}
      {tabs.includes('calls') && (
        <Tab.Screen name="Calls" component={CallsScreen} />
      )}
      {tabs.includes('users') && (
        <Tab.Screen name="Users" component={UsersScreen} />
      )}
      {tabs.includes('groups') && (
        <Tab.Screen name="Groups" component={GroupsScreen} />
      )}
    </Tab.Navigator>
  );
};