npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@enhancers/react-native-whitelabel

v1.1.3

Published

React Native White Label (RNWL) - A production-ready library for white-label React Native applications with feature flags support

Readme

RNWL - React Native White Label Library

npm version

RNWL is a production-ready library for building white-label React Native applications with advanced feature flags support.

🎯 What is RNWL?

RNWL enables you to build a single React Native app that can be branded and configured for multiple clients/brands, with the ability to dynamically enable/disable features per brand.

✨ Features

  • 🎯 Feature Flags Management: Enable/disable features dynamically per brand
  • ⚛️ React Hooks: useRNWLFeatures and useRNWLColors for easy component integration
  • 🚪 Component Gating: RNWLGate for conditional rendering
  • 🎨 Brand Colors: Per-brand color tokens accessible at runtime via useRNWLColors()
  • 🔗 Deep Link Configuration: Custom URL schemes and universal links, configured natively per brand
  • 🏷️ Xcode Scheme Renaming: Scheme files and PRODUCT_NAME in project.pbxproj are updated to match displayName, so the correct brand name appears in Xcode's scheme dropdown immediately after running the CLI
  • 📱 Multi-Platform: iOS, Android, and Web support
  • 💪 TypeScript: Fully typed API
  • 🔧 Zero Config: Works with sensible defaults
  • 🌐 Remote Feature Flags: Load flags from your backend via custom async loader

📦 Installation

npm install @enhancers/react-native-whitelabel
# or
yarn add @enhancers/react-native-whitelabel

🚀 Quick Start

1. Wrap your app with RNWLProvider

import { RNWLProvider } from '@enhancers/react-native-whitelabel';

export default function App() {
  return (
    <RNWLProvider>
      <YourApp />
    </RNWLProvider>
  );
}

The provider initializes the feature flags manager once and makes the state available to the entire component tree.

On iOS and Android, features are loaded synchronously from the bundled rnwl.json via require() at module-evaluation time, so isFeatureEnabled is ready before the first component render — no loading flash, no spurious warnings in Metro.

On web and when using a custom featureLoader, loading is asynchronous. Use the isLoading flag from useRNWLFeatures() to guard against rendering before flags are available.

You can pass initialization options via the options prop:

<RNWLProvider options={{ featureLoader: fetchFeaturesFromBackend }}>
  <YourApp />
</RNWLProvider>

2. Configure Metro

Add the rnwl-config virtual module to your metro.config.js. This tells Metro where to find the rnwl.json generated by the CLI:

const path = require('path');
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');

const config = {
  resolver: {
    extraNodeModules: {
      'rnwl-config': path.resolve(__dirname, 'rnwl.json'),
    },
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

This is a one-time setup. rnwl.json is generated by npx rnwl apply-white-label <brand> at the project root.

3. Use Features in Components

import { useRNWLFeatures, useRNWLColors, RNWLGate } from '@enhancers/react-native-whitelabel';

function MyComponent() {
  const { isFeatureEnabled, isLoading, error, brand, displayName, packageName, deeplinkScheme } = useRNWLFeatures();
  const { primary, background } = useRNWLColors();

  if (isLoading) return <LoadingScreen />;
  if (error) return <ErrorScreen />;

  return (
    <View style={{ backgroundColor: background }}>
      {isFeatureEnabled('darkMode') && <DarkModeUI />}

      <RNWLGate feature="analytics">
        <AnalyticsPanel />
      </RNWLGate>

      {deeplinkScheme && (
        <Text>Share link: {deeplinkScheme}://profile/123</Text>
      )}

      <Button color={primary} title="Continue" />
    </View>
  );
}

📚 Documentation

📁 Project Structure

react-native-whitelabel/
├── src/                          # 📦 Main library source
│   ├── config/                   #    Feature flags manager
│   │   └── rnwlFeatureFlagsManager.ts
│   ├── hooks/                    #    React hooks & components
│   │   ├── RNWLGate.tsx
│   │   ├── useRNWLColors.ts
│   │   └── useRNWLFeatures.ts
│   └── index.ts                  #    Main entry point
│
├── bin/
│   └── rnwl.js                   # 🖥️  CLI entry point
│
├── dist/                         # 📤 Compiled output (generated)
│
├── example/                      # 📋 Example React Native app
│   ├── src/
│   │   ├── App.tsx
│   │   └── screens/
│   │       └── HomeScreen.tsx
│   ├── rnwl-configs/             #    Brand configurations
│   │   ├── blue/
│   │   │   ├── config.yml
│   │   │   ├── icons/
│   │   │   ├── ios/
│   │   │   ├── android/
│   │   │   └── assets/
│   │   ├── green/                #    (same structure as blue)
│   │   └── red/                  #    (same structure as blue)
│   └── rnwl.json             #    Generated by apply-white-label.js
│
├── package.json
├── tsconfig.json
└── README.md

🔄 How It Works

  1. Configure Brands: Create YAML files for each brand in rnwl-configs/
  2. Run Build Script: Execute npx rnwl apply-white-label <brand> to apply the brand configuration
  3. Wrap your app: Mount <RNWLProvider> at the root — it initializes the manager and provides state to the tree
  4. Use Features: Use useRNWLFeatures() or RNWLGate in any component

📖 Example Configuration

See example/configs/ for complete examples. Full configuration structure:

# Metadata
brand: my-brand
displayName: "My Brand App"
version: "1.0.0"

# Brand colors and styling
colors:
  primary: "#3366FF"
  secondary: "#FF6B6B"

# Feature flags (enabled/disabled per brand)
features:
  authentication: true
  pushNotifications: true
  darkMode: false
  analytics: true
  videoStreaming: false
  paymentGateway: true

# Asset handling
ignoreAssets: false  # Set to true to skip asset processing (default: false)

# Deep linking — both fields are optional, use one or both
deeplinkScheme: myapp           # custom URL scheme  → myapp://  (shorthand for a single scheme)
# deeplinkSchemes:              # array form — use when multiple schemes are needed (e.g. SDK requirements)
#   - myapp
#   - sdkscheme
universalLinkDomain: myapp.com  # universal/app link → https://myapp.com

# iOS-only entitlements (optional) — any key/value pair written to the .entitlements file
# iosEntitlements:
#   com.apple.developer.networking.HotspotConfiguration: true
#   com.apple.developer.networking.wifi-info: true

🔧 Custom Parameters

Each brand can define arbitrary key/value parameters in config.yml under the params key. Values can be a single scalar or an array.

params:
  apiBaseUrl: "https://api.blue.example.com"
  supportedLocales:
    - "en"
    - "it"
    - "fr"
  maxRetries: 3
  debugMode: false

They are written to rnwl.json and exposed at runtime via useRNWLFeatures():

const { params } = useRNWLFeatures();

const url = params.apiBaseUrl;           // "https://api.blue.example.com"
const locales = params.supportedLocales; // ["en", "it", "fr"]

Or directly from the manager singleton:

import { rnwlFeatureFlagsManager } from '@enhancers/react-native-whitelabel';

const params = rnwlFeatureFlagsManager.getParams();

Supported value types: string, number, boolean, or an array of those.


📖 Asset Handling with ignoreAssets

The ignoreAssets flag controls whether the CLI auto-detects and processes brand assets:

  • ignoreAssets: false (default): CLI detects and reports on:

    • Generic icons in icons/
    • iOS-specific icons in ios/
    • Android density-specific icons in android/
    • Additional assets in assets/
  • ignoreAssets: true: CLI skips all asset detection and processing

    • Use when managing assets separately in your build pipeline
    • Use when assets are handled by native build tools
    • Useful for design teams that generate assets independently

🤖 Android Icons

Place icon files in rnwl-configs/<brand>/android/icons/:

| Source file | Destination | |-------------|-------------| | ic_launcher-{density}.png | android/app/src/main/res/mipmap-{density}/ic_launcher.png | | ic_launcher_round-{density}.png | android/app/src/main/res/mipmap-{density}/ic_launcher_round.png | | ic_launcher_foreground-{density}.png | android/app/src/main/res/mipmap-{density}/ic_launcher_foreground.png |

Supported densities: mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi.

ic_launcher_foreground files are optional and intended for adaptive icons (Android 8.0+, API 26+). If not provided, no foreground file is written. To use adaptive icons correctly:

  1. Provide ic_launcher_foreground-{density}.png files designed with the adaptive icon safe zone (108×108dp canvas, 72×72dp safe zone)
  2. Add android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml manually to your project referencing @mipmap/ic_launcher_foreground and a background layer

Why not auto-generate ic_launcher_foreground from ic_launcher? On Android 8.0+ launchers (Samsung, Pixel, etc.) that find a ic_launcher_foreground in the APK, they apply a circular mask to it. A square icon not designed for the safe zone would appear visually cropped.

🤖 Android Splash Screen

Place source files in rnwl-configs/<brand>/android/splash/. No YAML key is required. The ignoreAssets: true flag also skips splash processing.

| Source file | Destination | |-------------|-------------| | splashscreen-ldpi.png | android/app/src/main/res/mipmap-ldpi/splashscreen.png | | splashscreen-mdpi.png | android/app/src/main/res/mipmap-mdpi/splashscreen.png | | splashscreen-hdpi.png | android/app/src/main/res/mipmap-hdpi/splashscreen.png | | splashscreen-xhdpi.png | android/app/src/main/res/mipmap-xhdpi/splashscreen.png | | splashscreen-xxhdpi.png | android/app/src/main/res/mipmap-xxhdpi/splashscreen.png | | splashscreen-xxxhdpi.png | android/app/src/main/res/mipmap-xxxhdpi/splashscreen.png |

Landscape variants are optional — add splashscreen_land-{density}.png files and they will be copied to mipmap-{density}/splashscreen_land.png.

📄 Android strings.xml

Place a strings.xml file in rnwl-configs/<brand>/android/strings.xml to fully control the Android string resources for that brand. The CLI copies it directly to android/app/src/main/res/values/strings.xml when present.

| Source file | Destination | |-------------|-------------| | rnwl-configs/<brand>/android/strings.xml | android/app/src/main/res/values/strings.xml |

If the file is absent in the brand folder it is silently skipped — no error is thrown.

Example

<resources>
    <string name="app_name">Blue App</string>
    <string name="splash_name">Blue App</string>
</resources>

This replaces the entire destination file, so make sure to include all string keys your app references.

🍎 iOS Splash Screen

All iOS splash screen assets are placed in rnwl-configs/<brand>/ios/splash/. No YAML key is required. The ignoreAssets: true flag also skips splash processing.

Imageset assets

These files are copied into the corresponding .imageset directories in Images.xcassets and Contents.json is regenerated automatically based on which files are present:

| Source file | Destination | |-------------|-------------| | Splashscreen.png | ios/<App>/Images.xcassets/Splashscreen.imageset/ | | [email protected] | ios/<App>/Images.xcassets/Splashscreen.imageset/ | | [email protected] | ios/<App>/Images.xcassets/Splashscreen.imageset/ | | Splashscreen~landscape.png | ios/<App>/Images.xcassets/Splashscreen~landscape.imageset/ | | [email protected] | ios/<App>/Images.xcassets/Splashscreen~landscape.imageset/ | | [email protected] | ios/<App>/Images.xcassets/Splashscreen~landscape.imageset/ |

Landscape variants are optional — if no Splashscreen~landscape[@Nx].png files are found, Splashscreen~landscape.imageset is left untouched.

Note: the CLI copies images into existing imagesets — it does not create Splashscreen.imageset or Splashscreen~landscape.imageset if they don't already exist in your Xcode project.

Launch Storyboard

The CLI copies the launch storyboard and its referenced images from the brand config automatically, using UILaunchStoryboardName from Info.plist to resolve the correct destination.

How it works

  1. The CLI reads UILaunchStoryboardName from ios/<App>/Info.plist and normalises the value by appending .storyboard if absent (e.g. LaunchLaunch.storyboard). Falls back to LaunchScreen.storyboard if the key is missing.
  2. The CLI looks for the source storyboard in rnwl-configs/<brand>/ios/:
    • First tries an exact name match (<UILaunchStoryboardName>)
    • If not found, picks any .storyboard file present in that folder
  3. The destination is resolved in this order:
    • ios/<App>/<UILaunchStoryboardName> — classic location inside the app folder
    • ios/<UILaunchStoryboardName> — root of the ios/ directory
  4. The storyboard is copied to the first path that already exists in the project.
  5. The CLI parses the storyboard XML and extracts all referenced image names (<imageView image="..."> and <image name="...">). For each name it looks for <name>.png, <name>@2x.png, <name>@3x.png in rnwl-configs/<brand>/ios/splash/ and copies any found files to the same directory as the storyboard.

Warnings

The CLI emits a warning (non-fatal) in these cases:

| Situation | Warning | |-----------|---------| | No .storyboard file found in brand config ios/ | ⚠ No .storyboard found in brand config ios/ — launch screen not updated | | Storyboard references an image with no matching file in ios/splash/ | ⚠ Storyboard references image(s) not found in ios/splash: <name> — launch screen may appear broken |

Brand config structure

rnwl-configs/<brand>/ios/
  <any>.storyboard        ← any name — copied to the path declared in UILaunchStoryboardName
  splash/
    icon.png              ← images referenced by the storyboard (all variants optional)
    [email protected]
    [email protected]

Examples

| UILaunchStoryboardName | Brand config file | Destination | |--------------------------|-------------------|-------------| | LaunchScreen | ios/LaunchScreen.storyboard | ios/<App>/LaunchScreen.storyboard | | Launch | ios/LaunchScreen.storyboard | ios/Launch.storyboard (resolved from root) | | Launch | ios/Launch.storyboard | ios/Launch.storyboard (resolved from root) | | MyCustomSplash | ios/anything.storyboard | ios/<App>/MyCustomSplash.storyboard |

🏷️ Xcode Scheme Renaming

After applying a brand, opening the Xcode project shows the correct brand name in the scheme dropdown — no manual rename required.

What gets updated

| File / location | Change | |-----------------|--------| | ios/<App>.xcodeproj/xcshareddata/xcschemes/<ProjectName>[suffix].xcscheme | File renamed to <displayName>[suffix].xcscheme | | Inside each renamed scheme file | BlueprintName attribute updated to displayName | | ios/<App>.xcodeproj/project.pbxproj | Hardcoded PRODUCT_NAME = <ProjectName>; entries replaced with displayName |

The .xcodeproj folder itself and all ios/<AppDir>/ paths are never renamed — they are referenced throughout the project and in Fastlane.

How scheme detection works

Schemes are identified by looking for ReferencedContainer = "container:<ProjectName>.xcodeproj" inside the scheme XML. Because the .xcodeproj folder name never changes, this works regardless of what the scheme file is currently called — so the rename is fully idempotent:

  • Applying connect (displayName Connect) renames hOn.xcschemeConnect.xcscheme
  • Applying hon afterwards (displayName hOn) finds Connect.xcscheme via the container reference and renames it back to hOn.xcscheme
  • Applying the same brand twice is a no-op

Schemes that do not reference the project's .xcodeproj (e.g. CocoaPods schemes) are left untouched.

Suffix preservation

Suffixed variants are handled automatically:

Use the optional schemeName field in config.yml to set an explicit scheme identifier. If omitted, displayName is used as-is — set schemeName whenever displayName contains spaces to avoid issues with xcodebuild and CI scripts. PRODUCT_NAME in project.pbxproj is always set to displayName (spaces included) since that field supports it.

displayName: "Blue Theme"
schemeName: BlueTheme      # recommended when displayName has spaces

| Original file | After schemeName: Connect | After schemeName: BlueTheme | |---------------|-----------------------------|-------------------------------| | hOn.xcscheme | Connect.xcscheme | BlueTheme.xcscheme | | hOn-release.xcscheme | Connect-release.xcscheme | BlueTheme-release.xcscheme | | hOn-tvOS.xcscheme | Connect-tvOS.xcscheme | BlueTheme-tvOS.xcscheme |

The suffix is derived by stripping the current BlueprintName value from the front of the filename — the remainder is kept as-is.

If no matching .xcscheme files are found the step is skipped silently with a warning log.

🔥 Google Services Config

Brand-specific Firebase / Google Services config files are copied automatically when present in the brand folder. No YAML key is required. Processed independently of ignoreAssets.

| Source file | Destination | |-------------|-------------| | rnwl-configs/<brand>/android/google-services.json | android/app/google-services.json | | rnwl-configs/<brand>/ios/GoogleService-Info.plist | ios/<App>/GoogleService-Info.plist |

If either file is absent in the brand folder it is silently skipped — no error is thrown.

🔗 Deep Link Configuration

RNWL supports two deep link strategies, configured independently per brand in config.yml:

| Field | Type | Description | |-------|------|-------------| | deeplinkScheme | string | Custom URL scheme — shorthand for a single scheme (e.g. myapp → handles myapp:// links) | | deeplinkSchemes | string[] | Array of URL schemes — use when multiple schemes are needed (e.g. alongside schemes required by third-party SDKs). deeplinkScheme is equivalent to a one-element array. | | universalLinkDomain | string | Domain for universal links / App Links (e.g. myapp.com → handles https://myapp.com/…) | | webcredentialsDomain | string | Domain for Shared Web Credentials / Password AutoFill on iOS (e.g. myapp.com) |

All fields are optional. universalLinkDomain and webcredentialsDomain can share the same domain or use different ones — they are written as separate entries in the com.apple.developer.associated-domains array.

config.yml example

deeplinkScheme: myapp
universalLinkDomain: myapp.com
webcredentialsDomain: myapp.com  # optional, can match universalLinkDomain or differ

To register multiple iOS URL schemes (e.g. when a third-party SDK requires its own scheme):

deeplinkSchemes:
  - myapp
  - sdkscheme               # additional scheme required by a third-party SDK
universalLinkDomain: myapp.com

What the CLI configures natively

deeplinkScheme / deeplinkSchemes

Both forms are equivalent — deeplinkScheme: myapp is shorthand for deeplinkSchemes: [myapp]. When both are present, deeplinkSchemes takes precedence.

| Platform | File modified | Change | |----------|--------------|--------| | Android | android/app/src/main/AndroidManifest.xml | Adds an <intent-filter> with <data android:scheme="..."/> for the first scheme inside MainActivity | | iOS | ios/<App>/Info.plist | Adds/updates CFBundleURLTypes with all schemes in a single <array> entry |

universalLinkDomain

| Platform | File modified | Change | |----------|--------------|--------| | Android | android/app/src/main/AndroidManifest.xml | Adds an <intent-filter android:autoVerify="true"> with android:scheme="https" and android:host | | iOS | ios/<App>/<App>.entitlements | Creates or updates the file with com.apple.developer.associated-domainsapplinks:<domain> | | iOS | ios/<App>.xcodeproj/project.pbxproj | Sets CODE_SIGN_ENTITLEMENTS to point to the entitlements file |

webcredentialsDomain

| Platform | File modified | Change | |----------|--------------|--------| | iOS | ios/<App>/<App>.entitlements | Adds webcredentials:<domain> to the com.apple.developer.associated-domains array (alongside applinks: if universalLinkDomain is also set) | | iOS | ios/<App>.xcodeproj/project.pbxproj | Sets CODE_SIGN_ENTITLEMENTS if not already pointing to the entitlements file | | Android | android/app/src/main/AndroidManifest.xml | Adds a separate <intent-filter android:autoVerify="true"> for the domain — only if it differs from universalLinkDomain (if they match, the existing App Links filter already covers it) |

Note on universal links (iOS): the entitlements alone are not sufficient — you also need to host an apple-app-site-association (AASA) file at https://<domain>/.well-known/apple-app-site-association. This is a server-side requirement outside the scope of the CLI.

iOS Entitlements (iosEntitlements)

Beyond universal links, you can inject any additional iOS entitlement directly from config.yml using the iosEntitlements key:

iosEntitlements:
  com.apple.developer.networking.HotspotConfiguration: true
  com.apple.developer.networking.wifi-info: true

Supported value types:

| YAML type | Plist output | |-----------|-------------| | true / false | <true/> / <false/> | | "string" | <string>string</string> | | ["a", "b"] | <array><string>a</string>…</array> |

The CLI writes all keys into <AppName>.entitlements, creating the file if it does not exist, and sets CODE_SIGN_ENTITLEMENTS in project.pbxproj automatically. Re-running the command updates keys in-place; keys not present in iosEntitlements are left untouched.

Note: com.apple.developer.associated-domains is managed exclusively by universalLinkDomain — do not set it via iosEntitlements.

The CLI uses HTML comment markers (<!-- RNWL:deeplinkScheme --> / <!-- RNWL:universalLinkDomain -->) around the injected blocks in AndroidManifest.xml so that re-running the command cleanly replaces the previous values instead of appending duplicates. If an equivalent <intent-filter> already exists without markers, the CLI wraps it in-place on the first run.

Accessing brand identity in the app

brand, displayName and packageName are exposed via useRNWLFeatures():

const { brand, displayName, packageName } = useRNWLFeatures();

return <Text>{displayName}</Text>; // "Blue App"
// brand === "blue" (matches the folder name in rnwl-configs/ and the brand: field in config.yml)

Accessing deep link config in the app

Both values are exposed via useRNWLFeatures():

import { useRNWLFeatures } from '@enhancers/react-native-whitelabel';

function ShareButton() {
  const { deeplinkScheme, universalLinkDomain } = useRNWLFeatures();

  const shareUrl = universalLinkDomain
    ? `https://${universalLinkDomain}/profile/123`
    : `${deeplinkScheme}://profile/123`;

  return <Button title="Share" onPress={() => Share.share({ url: shareUrl })} />;
}

🖥️ CLI: apply-white-label

The apply-white-label command applies a brand configuration to your React Native project in one step.

Usage

npx rnwl apply-white-label <brand> [options]

Options:

| Flag | Description | |------|-------------| | --androidVersionCode <code> | Override versionCode in android/app/build.gradle | | --androidVersionName <name> | Override versionName in android/app/build.gradle | | --iosCurrentProjectVersion <ver> | Override CURRENT_PROJECT_VERSION in project.pbxproj | | --iosMarketingVersion <ver> | Override MARKETING_VERSION in project.pbxproj |

Project structure

Create an rnwl-configs/ folder in your project root with one subfolder per brand:

my-app/
├── rnwl-configs/
│   ├── blue/
│   │   ├── config.yml
│   │   ├── android/
│   │   │   ├── icons/
│   │   │   │   ├── ic_launcher-mdpi.png
│   │   │   │   ├── ic_launcher-hdpi.png
│   │   │   │   ├── ...
│   │   │   │   ├── ic_launcher_foreground-mdpi.png  (optional, adaptive icons)
│   │   │   │   ├── ic_launcher_foreground-hdpi.png
│   │   │   │   └── ...
│   │   │   ├── splash/
│   │   │   │   ├── splashscreen-mdpi.png
│   │   │   │   ├── splashscreen-hdpi.png
│   │   │   │   ├── ...
│   │   │   │   ├── splashscreen_land-mdpi.png   (optional landscape)
│   │   │   │   └── ...
│   │   │   ├── strings.xml                      (optional)
│   │   │   └── google-services.json             (optional)
│   │   └── ios/
│   │       ├── icons/
│   │       │   ├── AppIcon.png
│   │       │   ├── AppIcon-120.png
│   │       │   └── ...
│   │       ├── splash/
│   │       │   ├── Splashscreen.png
│   │       │   ├── [email protected]
│   │       │   ├── [email protected]
│   │       │   ├── Splashscreen~landscape.png   (optional landscape)
│   │       │   ├── [email protected]
│   │       │   ├── [email protected]
│   │       │   └── icon.png, [email protected] ...    (optional — storyboard-referenced images)
│   │       ├── <any>.storyboard                     (optional — any name; destination resolved from UILaunchStoryboardName in Info.plist)
│   │       └── GoogleService-Info.plist         (optional)
│   └── red/
│       └── config.yml
├── android/
├── ios/
└── app.json

Example config.yml

brand: blue
displayName: "Blue App"
schemeName: BlueApp         # optional — Xcode scheme identifier (no spaces); defaults to displayName with spaces stripped
bundleId: com.myapp.blue
packageName: com.myapp.blue
version: "1.0.0"

colors:
  primary: "#0066FF"
  secondary: "#3366FF"
  background: "#FFFFFF"

# Deep links (both optional)
deeplinkScheme: blueapp             # registers blueapp:// on Android & iOS
# deeplinkSchemes:                  # alternative array form (iOS supports multiple schemes)
#   - blueapp
#   - sdkscheme
# universalLinkDomain: blue.myapp.com  # registers https://blue.myapp.com App Links

features:
  authentication: true
  pushNotifications: true
  darkMode: false
  analytics: true
  paymentGateway: false

ignoreAssets: false

Examples

# Apply the "blue" brand
npx rnwl apply-white-label blue

# Apply the "red" brand
npx rnwl apply-white-label red

# Override Android version
npx rnwl apply-white-label blue --androidVersionCode 42 --androidVersionName 2.1.0

# Override iOS version
npx rnwl apply-white-label blue --iosCurrentProjectVersion 42 --iosMarketingVersion 2.1.0

# Override both platforms at once
npx rnwl apply-white-label blue --androidVersionCode 42 --androidVersionName 2.1.0 --iosCurrentProjectVersion 42 --iosMarketingVersion 2.1.0

# List available brands (shown when an unknown brand is passed)
npx rnwl apply-white-label unknown-brand

You can also add convenience scripts to your package.json:

{
  "scripts": {
    "brand:blue": "npx rnwl apply-white-label blue",
    "brand:red":  "npx rnwl apply-white-label red"
  }
}

What it does

When executed, the CLI:

  1. Reads rnwl-configs/<brand>/config.yml
  2. Updates app.json with displayName and version
  3. Updates android/app/build.gradle with applicationId, and optionally versionCode / versionName if the flags are provided
  4. Copies android/strings.xml from the brand config folder to android/app/src/main/res/values/strings.xml (if present)
  5. Updates ios/.../Info.plist with CFBundleDisplayName
  6. Copies the launch storyboard from the brand config using UILaunchStoryboardName from Info.plist to resolve the correct filename and destination, and copies storyboard-referenced images from ios/splash/ next to the storyboard (see iOS Splash Screen)
  7. Updates ios/.../project.pbxproj with PRODUCT_BUNDLE_IDENTIFIER and PRODUCT_NAME, and optionally CURRENT_PROJECT_VERSION / MARKETING_VERSION if the flags are provided
  8. Renames .xcscheme files inside ios/<App>.xcodeproj/xcshareddata/xcschemes/ to match displayName and updates the BlueprintName attribute inside each file (see Xcode Scheme Renaming)
  9. Copies Android icons into mipmap-* directories
  10. Copies iOS icons into AppIcon.appiconset and regenerates Contents.json
  11. Copies Android splash images into mipmap-* directories (if android/splash/ exists in the brand folder)
  12. Copies iOS splash images into Splashscreen.imageset and/or Splashscreen~landscape.imageset and regenerates Contents.json (if ios/splash/ exists — see iOS Splash Screen)
  13. Copies google-services.json to android/app/ (if present in the brand folder)
  14. Copies GoogleService-Info.plist to ios/<App>/ (if present in the brand folder)
  15. Generates rnwl.json in the project root with the active feature flags and brand colors
  16. Configures native deep links (if deeplinkScheme or universalLinkDomain are set — see Deep Link Configuration)
  17. Writes additional iOS entitlements (if iosEntitlements is set — see iOS Entitlements)

Full example with version flags

npx rnwl apply-white-label blue \
  --androidVersionCode 42 \
  --androidVersionName 2.1.0 \
  --iosCurrentProjectVersion 42 \
  --iosMarketingVersion 2.1.0

This will apply the blue brand config and additionally set:

| File | Field | Value | |------|-------|-------| | android/app/build.gradle | versionCode | 42 | | android/app/build.gradle | versionName | "2.1.0" | | ios/.../project.pbxproj | CURRENT_PROJECT_VERSION | 42 | | ios/.../project.pbxproj | MARKETING_VERSION | 2.1.0 |

The version flags are independent of the version field in config.yml. Use them when you need to bump versions at build time (e.g. from a CI pipeline) without editing the config file.


🎨 Brand Colors

Each brand can define color tokens in config.yml under the colors key:

colors:
  primary: "#0066FF"
  secondary: "#3366FF"
  background: "#FFFFFF"

Any key name is supported — primary, secondary, background, accent, error, etc. The CLI writes them into rnwl.json and they are available at runtime via the useRNWLColors() hook.

useRNWLColors()

import { useRNWLColors } from '@enhancers/react-native-whitelabel';

function ThemedButton() {
  const { primary } = useRNWLColors();
  return <Button color={primary} title="Continue" />;
}

Returns an RNWLColors object. Any key not defined in the brand config will be undefined.

RNWLColors type

interface RNWLColors {
  primary?: string;
  secondary?: string;
  background?: string;
  [key: string]: string | undefined; // any additional custom color keys
}

Colors are also accessible directly via useRNWLFeatures() through the colors field on the context value, for components that already destructure the full context.


🎯 Feature Flag API

isFeatureEnabled(featureName)

Returns true if the feature is enabled, false otherwise. Never throws. Logs a console.warn in two cases:

  • The feature key does not exist in the loaded configuration (useful for catching typos or stale references at development time)
  • Features have not been loaded yet (only possible on web or when using a custom featureLoader, since native initialization is synchronous)
import { useRNWLFeatures } from '@enhancers/react-native-whitelabel';

function MyComponent() {
  const { isFeatureEnabled } = useRNWLFeatures();

  // ✅ feature exists and is enabled → true
  // ✅ feature exists and is disabled → false
  // ⚠️  feature does not exist → false + console.warn
  // ⚠️  not yet loaded (web / featureLoader) → false + console.warn
  return isFeatureEnabled('darkMode') ? <DarkUI /> : <LightUI />;
}

Or directly via the manager singleton:

import { rnwlFeatureFlagsManager } from '@enhancers/react-native-whitelabel';

const enabled = rnwlFeatureFlagsManager.isFeatureEnabled('analytics');

💡 Use Cases

  • Multi-tenant SaaS apps: Different features for different customers
  • A/B Testing: Enable features for specific user segments
  • Gradual Rollouts: Enable new features for specific brands first
  • White-label Solutions: Build once, brand many times
  • Feature Management: Control feature availability without rebuilding

🌐 Remote Feature Flags

Feature flags can be loaded from a remote backend via RNWLInitializeOptions, passed to <RNWLProvider>. This lets you control feature availability server-side without rebuilding the app.

featureLoader — custom async loader

Replaces the built-in file loading entirely. Return an RNWLFeatures object from any async source.

<RNWLProvider
  options={{
    featureLoader: async () => {
      const res = await fetch('https://api.myapp.com/feature-flags');
      const data = await res.json();
      return data.features; // { darkMode: true, ads: false, ... }
    },
  }}
>
  <YourApp />
</RNWLProvider>

overrides — static partial overrides

Merged on top of whatever was loaded (file or featureLoader). Keys present here always win.

<RNWLProvider options={{ overrides: { ads: false } }}>
  <YourApp />
</RNWLProvider>

You can combine both: featureLoader fetches the base config, overrides applies local adjustments on top.

<RNWLProvider
  options={{
    featureLoader: fetchFeaturesFromBackend,
    overrides: { ads: false }, // always disable ads, regardless of backend response
  }}
>
  <YourApp />
</RNWLProvider>

reinitialize() — refresh at runtime

Call rnwlFeatureFlagsManager.reinitialize() to discard current state and re-run initialization. Useful for refreshing flags after login or when the user changes their plan.

import { rnwlFeatureFlagsManager } from '@enhancers/react-native-whitelabel';

// After user logs in, reload flags from backend
await rnwlFeatureFlagsManager.reinitialize({
  featureLoader: fetchFeaturesFromBackend,
});