white_label_kit

pub package Dart License: MIT

The modern, automated flavor & multi-tenant white-label toolkit for Flutter.

Easily manage multiple branded apps, flavors, and client tenants from a single Flutter codebase. Define all your tenants in white_label.yaml, and let white_label_kit automate Android Gradle flavors, iOS Xcode build schemes, IDE configurations, and compile-time asset isolation.


๐Ÿ’ก Why white_label_kit?

Managing multiple flavors or white-label client apps in Flutter usually means:

  • Hand-editing complex android/app/build.gradle.kts product flavors.
  • Manually creating and wiring iOS Xcode build configurations, schemes, and bundle identifiers.
  • Risking asset leakage where one tenant's logos or credentials accidentally get bundled into another tenant's app.
  • Manually configuring IDE debug and build tasks for every new flavor.

white_label_kit automates all of this:

  • ๐Ÿ“„ Single Source of Truth: Declare all tenants, bundle IDs, colors, API endpoints, environments, and feature flags in one white_label.yaml.
  • ๐Ÿค– Native File Automation: Patches Android Gradle and iOS Xcode schemes automatically with dart run white_label_kit:configure.
  • ๐ŸŽจ Icon & Splash Automation: Opt-in per-tenant launcher icon and native splash generation โ€” including the iOS Xcode wiring step most guides forget (see ยง5).
  • ๐ŸŒ Per-Environment Runtime Config: The same tenant/brand can target staging/production/anything else via --env, without a second tenant (see ยง6).
  • ๐Ÿ›ก๏ธ Asset Isolation: Guarantees only the active tenant's assets and configs are compiled into the binary โ€” never every tenant's data baked into every build.
  • โšก Interactive CLI Runner: Launch dart run white_label_kit to easily run or build APK, AAB, and iOS apps without memorizing long commands.
  • ๐Ÿ’ป 1-Click IDE Configurations: Generates ready-to-use Run/Build configurations for Android Studio, IntelliJ, and VS Code.
  • ๐Ÿ”’ Type-Safe Runtime API: Access tenant metadata cleanly in your Flutter widgets and services using WhiteLabelRuntime.
  • ๐Ÿฉบ doctor: One command that flags missing assets, stale generated files, and missing peer dependencies (flutter_native_splash) before a build fails on them.

๐Ÿš€ Getting Started

1. Add Dependency

Add white_label_kit to your Flutter project's dependencies โ€” not dev_dependencies:

flutter pub add white_label_kit

Or manually in pubspec.yaml:

dependencies:
  white_label_kit: ^0.0.5

Why a regular dependency, not dev-only: lib/white_label.g.dart (the generated file โ€” see step 3) imports WhiteLabelRuntime/WhiteLabelTheme from this package and is compiled directly into your app, read by your own runtime code (whiteLabelRuntime.environment.apiBaseUrl, theme colors, feature flags, etc.). It is not purely a build-time codegen tool the way build_runner is โ€” putting it under dev_dependencies would still happen to compile for a leaf app, but is the wrong semantic declaration for a package your shipped binary actually reads from at runtime, and would break if this package's code ever needed to reach another package that's only resolved via dependencies.

2. Initialize Configuration

Generate a starter white_label.yaml in your project root:

dart run white_label_kit:init

3. Add Your Tenants / Brands

Add a new brand with a single command:

dart run white_label_kit:add-tenant acme "Acme App" com.example.acme --logo path/to/acme_logo.png

This automatically creates the configuration entry in white_label.yaml and the asset folder tenants/acme/, copying your real logo in if --logo was given. Omit --logo and it writes a placeholder tenants/acme/logo.png instead โ€” replace that file with the real logo before shipping (this is the one step white_label_kit genuinely can't automate for you: it doesn't know what your brand's logo looks like).

4. Configure Android & iOS Native Files

Sync all native Gradle flavors, Xcode schemes, and IDE run configurations:

dart run white_label_kit:configure

Run dart run white_label_kit:doctor any time to sanity-check the current setup โ€” missing assets, a stale lib/white_label.g.dart, a missing flutter_native_splash dependency if splash_generate is on, etc.

5. Launcher Icons & Native Splash (per tenant)

Launcher/notification icons and the native splash screen are generated by two well-established, purpose-built packages โ€” icons_launcher and flutter_native_splash โ€” not re-modeled by white_label_kit itself.

icons_launcher is a real dependency of this package, so dart run icons_launcher:create resolves for your app with nothing added to your own pubspec.yaml. flutter_native_splash can't be a dependency of this package the same way (it needs the Flutter SDK to resolve, which this package deliberately doesn't โ€” see maybeGenerateNativeSplash's doc comment for the full reasoning). Add flutter_native_splash to your own app's pubspec.yaml (flutter pub add flutter_native_splash) if you want the splash generation below โ€” icon generation needs no such step.

Opt-in auto-generation (recommended default): declare features: { icon_generate: true } / { splash_generate: true } for a tenant in white_label.yaml, and configure/build create icons_launcher-<id>.yaml / flutter_native_splash-<id>.yaml for you โ€” derived from that tenant's assets.icon/assets.logo (icon) or assets.splash/assets.icon/assets.logo + theme.primary_color (splash) โ€” only if the file doesn't already exist. Nothing to hand-author for the common case, and a file you've already customized is never touched or overwritten:

tenants:
  acme:
    features:
      icon_generate: true
      splash_generate: true

Both flags are off by default โ€” a tenant that declares neither sees no change in behavior at all.

The auto-created icons_launcher-<id>.yaml includes an adaptive icon (Android 8.0+/API 26) by default โ€” adaptive_foreground_image reuses the tenant's icon/logo, adaptive_background_color uses theme.primary_color (white if unset). A reasonable automatic default, not a substitute for a properly-padded, transparent foreground asset โ€” hand-author the file with a dedicated foreground image for a polished result.

iOS storyboard registration is automatic โ€” for the opt-in flag only. flutter_native_splash:create only writes ios/Runner/Base.lproj/LaunchScreen<Tenant>.storyboard to disk โ€” Xcode never bundles a resource it doesn't know about, so a stock run of that command alone silently produces a splash screen that never ships. When splash_generate: true triggers a successful flutter_native_splash:create run, white_label_kit registers that storyboard into Runner.xcodeproj's Resources build phase for you right after (idempotent โ€” safe to re-run, best-effort โ€” a missing ruby/xcodeproj gem is reported as a warning in the output, never a crash).

Manual (full control) โ€” skip the flags: hand-author icons_launcher-acme.yaml / flutter_native_splash-acme.yaml yourself using either package's full config reference (adaptive icon background/foreground, dark-mode variants, fullscreen, per-platform overrides, and everything else either supports), then run:

dart run icons_launcher:create --flavor acme
dart run flutter_native_splash:create --flavor acme

Going this route puts you outside the automatic registration above โ€” you must still register the storyboard into Xcode yourself (ios/Runner.xcodeproj's Resources build phase) before it will actually appear in the built app.

6. Staging / Production (--env)

environments: + --env switches runtime config (API URL and whatever else you put in custom:) for the SAME tenant/brand โ€” it is not a second tenant. Icon, theme, bundle id, and app name all stay whatever the tenant already declares; only environment changes. See Configuration File below for the YAML shape.

staging/production below are just this README's example names โ€” the key under environments: is an arbitrary string you choose, not a fixed/reserved keyword. Name it whatever matches your own release process (qa, uat, demo, beta, ...) and pass that exact name to --env.

dart run white_label_kit:generate  --tenant acme --env staging
dart run white_label_kit:configure --tenant acme --env staging
dart run white_label_kit:build     --tenant acme --env staging --platform android
dart run white_label_kit:run       --tenant acme --env staging

Omit --env anywhere above and the tenant's default environment: block is used โ€” fully backward compatible, a white_label.yaml that never declares environments: needs no change. Passing an --env name the tenant never declared is a hard error (never a silent fallback to the default) โ€” you can't accidentally ship "staging" with production's API URL baked in.

build/run/configure all (re)generate lib/white_label.g.dart for whichever tenant/environment they actually resolved to, every time they run โ€” there's no separate "don't forget to regenerate" step to remember.


๐Ÿ–ฅ๏ธ Running & Building Your App

Launch the interactive runner:

dart run white_label_kit
โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘              โœจ WHITE_LABEL_KIT RUNNER & BUILDER                 โ•‘
โ•‘          Automated Multi-Tenant Flutter CLI & Launcher           โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

๐Ÿ“Œ SELECT TENANT:
   [0] Acme App [acme] (Default)

Enter tenant number (default: acme): 0

โšก SELECT ACTION:
   [1] โ–ถ๏ธ  Run in Debug Mode (Simulator / Connected Device)
   [2] โšก  Run in Release Mode (Device)
   [3] ๐Ÿš€  Build Release APK (Android)
   [4] ๐Ÿ“ฆ  Build Release AppBundle / AAB (Google Play Store)
   [5] ๐ŸŽ  Build Release iOS (Simulator / Archive)
   [6] ๐Ÿ”ง  Configure All Tenants (white_label_kit:configure)
   [7] โž•  Add New Tenant (white_label_kit:add-tenant)
   [8] โŒ  Remove Tenant (white_label_kit:remove-tenant)
   [9] ๐Ÿ”  Analyze & Health Check (Flutter Analyze + Tests)
   [0] ๐Ÿšช  Exit

Option B: Flutter CLI Commands

You can also run or build directly with standard Flutter commands โ€” this bypasses white_label_kit's own build/run (so it does not regenerate lib/white_label.g.dart for you; run generate/configure first if you switched tenant or --env):

# Run tenant in debug mode
flutter run --flavor acme --dart-define=TENANT_ID=acme

# Build Android Release APK
flutter build apk --release --flavor acme --dart-define=TENANT_ID=acme

# Build Android Release AppBundle (Google Play)
flutter build appbundle --release --flavor acme --dart-define=TENANT_ID=acme

# Build iOS Release App
flutter build ios --release --flavor acme --dart-define=TENANT_ID=acme

Group tenant-specific logos and platform credentials under the root tenants/ folder:

my_flutter_app/
โ”œโ”€โ”€ white_label.yaml                # ๐ŸŒŸ Central configuration for all tenants
โ”œโ”€โ”€ tenants/                        # ๐Ÿ“‚ Assets grouped per tenant
โ”‚   โ”œโ”€โ”€ acme/
โ”‚   โ”‚   โ”œโ”€โ”€ logo.png                # ๐ŸŽจ App logo / icon asset
โ”‚   โ”‚   โ””โ”€โ”€ firebase/               # ๐Ÿ”’ Firebase credentials (optional)
โ”‚   โ”‚       โ”œโ”€โ”€ google-services.json
โ”‚   โ”‚       โ””โ”€โ”€ GoogleService-Info.plist
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ beta/
โ”‚       โ”œโ”€โ”€ logo.png
โ”‚       โ””โ”€โ”€ firebase/
โ”‚           โ”œโ”€โ”€ google-services.json
โ”‚           โ””โ”€โ”€ GoogleService-Info.plist
โ”‚
โ”œโ”€โ”€ lib/
โ”‚   โ”œโ”€โ”€ main.dart
โ”‚   โ””โ”€โ”€ white_label.g.dart          # โšก Generated typed tenant constants
โ””โ”€โ”€ pubspec.yaml

โš™๏ธ Configuration File (white_label.yaml)

Define all tenant properties in white_label.yaml:

white_label:
  default_tenant: acme

  tenants:
    acme:
      name: "Acme App"
      version:
        name: "1.0.0"
        build_number: 1

      android:
        application_id: "com.example.acme"
        app_name: "Acme App"
        # version:                # optional โ€” overrides the shared
        #   name: "1.0.0"         # `version:` above for Android only, if
        #   build_number: 1       # this platform's release cadence diverges

      ios:
        bundle_id: "com.example.acme"
        app_name: "Acme App"
        # version: { ... }        # same override shape as android.version

      theme:
        primary_color: "#1E88E5"
        secondary_color: "#FFC107"
        # brand_colors: { logo_accent: "#FF0000" }    # optional, arbitrary
        # feature_colors: { courses: "#00FF00" }      # keyed hex-color maps
        # section_colors: { header: "#0000FF" }       # for apps whose UI
        # gradient_colors: { start: "#111111" }       # needs more than one
        #                                              # primary/secondary

      environment:                            # the DEFAULT โ€” used whenever
        api_base_url: "https://api.example.com"  # `--env` isn't passed
        # custom:                              # optional, arbitrary string
        #   sentry_dsn: "https://..."           # key-values for anything else
        #   cdn_url: "https://cdn.example.com"  # the build needs at runtime

      environments:                # optional โ€” NAMED overrides of the
        staging:                   # `environment:` block above, selected
          api_base_url: "https://staging-api.example.com"  # via `--env`
          custom:                                        # (see ยง6 above).
            sentry_dsn: "https://staging-sentry.example.com"
        production:
          api_base_url: "https://api.example.com"
      # `staging`/`production` are just names picked for this example โ€”
      # any key you write under `environments:` becomes a valid `--env`
      # value (e.g. `qa`, `uat`, `demo` all work equally). A tenant that
      # never declares `environments:` behaves exactly as before this
      # existed โ€” the whole block is optional. Each named environment is
      # an independent override, NOT a patch on top of `environment:` โ€”
      # declare everything that environment needs.

      features:
        enable_push_notifications: true
        enable_downloads: true
        # icon_generate: true      # see ยง5 above
        # splash_generate: true

      assets:
        logo: "tenants/acme/logo.png"
        # icon: "tenants/acme/icon.png"       # optional
        # splash: "tenants/acme/splash.png"   # optional

      firebase:
        google_services_json: "tenants/acme/firebase/google-services.json"
        google_service_info_plist: "tenants/acme/firebase/GoogleService-Info.plist"

๐Ÿ“ฑ Accessing Tenant Data in Flutter (Dart)

Access your active tenant's branding, API endpoints, and feature flags anywhere in your Dart code:

dart run white_label_kit:generate compiles the current build's tenant (and, if --env was passed, that specific environment) into lib/white_label.g.dart as a single whiteLabelRuntime constant (a WhiteLabelRuntime) โ€” never a map of every tenant, so no other tenant's data is ever compiled into a build that isn't theirs:

import 'package:flutter/material.dart';
import 'white_label.g.dart';

void main() {
  print('Tenant ID: ${whiteLabelRuntime.tenantId}');
  print('App Name: ${whiteLabelRuntime.tenantName}');
  print('Environment: $whiteLabelEnvironmentName');   // "" if --env wasn't used
  print('API URL: ${whiteLabelRuntime.environment.apiBaseUrl}');
  print('Sentry DSN: ${whiteLabelRuntime.environment.custom['sentry_dsn']}');
  print('Primary Color: ${whiteLabelRuntime.theme.primaryColorHex}');

  final hasPush = whiteLabelRuntime.isFeatureEnabled('enable_push_notifications');
  print('Push Notifications: $hasPush');

  runApp(const MyApp());
}

๐Ÿงฉ Optional: build_runner Integration

You do not need build_runner for anything above โ€” generate/ configure/build/run are plain, direct CLI commands, the same shape as flutter_native_splash:create. If your app already runs dart run build_runner build for freezed/json_serializable/ injectable_generator and you'd like that same command to also regenerate lib/white_label.g.dart, this package ships an optional builder for it (lib/builder.dart) โ€” but it is not enabled automatically, and needs two things added to your own project's build.yaml before it does anything:

# your app's build.yaml
targets:
  $default:
    sources:
      - white_label.yaml   # lives at the project root, outside build_runner's default input set
      - lib/**             # default lib/** scan โ€” add it explicitly
builders:
  white_label_kit:white_label_generator:
    enabled: true          # NOT auto-applied โ€” must opt in explicitly

Why this isn't auto-applied: without the sources: override above, build_runner would activate the builder but it could never actually find its white_label.yaml input โ€” while still treating lib/white_label.g.dart as an output it owns, and deleting it on the next build_runner build (the file generate/configure had already written correctly gets wiped with no warning). If you don't need build_runner to regenerate this file, don't add the build.yaml block above โ€” generate/ configure are unaffected either way.


๐Ÿ“– CLI Commands Reference

Command Description
dart run white_label_kit Opens the interactive terminal runner & builder menu
dart run white_label_kit:init [--example] [--force] [--path <dir>] Creates a starter white_label.yaml file
dart run white_label_kit:add-tenant <id> "<Name>" <bundleId> [--logo <path>] [--default] Adds a new tenant and creates its asset directory
dart run white_label_kit:update-tenant <id> [options] Updates tenant configuration fields
dart run white_label_kit:remove-tenant <id> [--keep-assets] Removes the tenant's entry from white_label.yaml, deletes its tenants/<id>/ asset folder (unless --keep-assets), and cleans up its generated Android Gradle flavor, iOS Xcode build configs/scheme, and IDE run configurations
dart run white_label_kit:generate [--tenant <id>] [--env <name>] [--config <path>] (Re)generates lib/white_label.g.dart for one tenant/environment
dart run white_label_kit:configure [--tenant <id>] [--env <name>] [--platform android|ios|all] [--dry-run] [--skip-generate] Patches Android Gradle, iOS Xcode, IDE configs, icon/splash (if opted in), and regenerates lib/white_label.g.dart
dart run white_label_kit:build [--tenant <id>] [--env <name>] [--platform android|android-aab|ios|all] [--mode debug|release] [--dry-run] [--clean] [--stage-only] Stages tenant assets, regenerates lib/white_label.g.dart, and invokes the real flutter build. --mode release always adds --obfuscate --split-debug-info=build/outputs/symbols/<tenant>/<platform> (see Flutter's obfuscation guide) โ€” not optional, so a release build can't accidentally ship un-obfuscated.
dart run white_label_kit:run [--tenant <id>] [--env <name>] Stages tenant assets and regenerates lib/white_label.g.dart for a debug run
dart run white_label_kit:validate Validates white_label.yaml syntax and asset paths
dart run white_label_kit:list Lists all declared tenants and the default tenant
dart run white_label_kit:doctor [id] [--all] [--json] [--strict] Performs a multi-tenant health check

๐Ÿ”’ Security

white_label.yaml and everything it generates (lib/white_label.g.dart, native Gradle/Xcode config) end up baked into the built binary โ€” the same way any other compiled Flutter asset does. Anyone who unzips a shipped APK/IPA can read whiteLabelRuntime's data, including everything under environment.custom/environments.*.custom.

Never put in white_label.yaml: signing keys/certificates, private API secrets, database credentials, or anything else that would matter if extracted from the built app. environment/environment.custom is for public runtime config only (a base URL, a public DSN meant to be client-visible, a CDN URL) โ€” not a place to smuggle a secret in because it was convenient. Firebase's google-services.json/ GoogleService-Info.plist (via firebase:) are the one exception this package handles directly, and only because Firebase itself designs those files to ship inside the client app.


๐Ÿšซ What this does NOT do (yet)

Deliberate scope boundaries, not oversights โ€” flagged here instead of silently discovered later:

  • No per-environment theme/icon/splash. environments:/--env (ยง6) only ever changes environment (API URL + custom). Staging and production of the same tenant are expected to look identical; there is no environments.staging.theme or similar. If you need visually distinct staging builds, that's a real gap today, not a documented design choice โ€” open an issue rather than hand-rolling around it.
  • environment/environments.*.custom are flat string maps, not arbitrary JSON. Same deliberate constraint as features (bool-only) โ€” richer/nested structured runtime content isn't modeled here.
  • ASSETCATALOG_COMPILER_APPICON_NAME and LAUNCH_SCREEN_STORYBOARD_NAME (the two remaining iOS Xcode keys icons_launcher/flutter_native_splash themselves don't manage, beyond what generateIosConfig sets) are not touched by this package at all โ€” see maybeGenerateNativeSplash's storyboard-registration note in ยง5 for the one exception (the storyboard file reference).
  • No CI/CD orchestration. This package configures native files and generates Dart code; it does not run or generate pipeline definitions (Bitbucket/GitHub Actions/etc.) โ€” wire its CLI commands into whatever CI you already run.

๐Ÿค Contributing

Contributions, issues, and feature requests are welcome! Feel free to check the issues page.


๐Ÿ“„ License

This project is licensed under the MIT License โ€” see the LICENSE file for details.

Libraries

builder
OPTIONAL, advanced alternative to dart run white_label_kit:generate (see bin/generate.dart โ€” that's the primary, recommended path, the same way flutter_native_splash:create/icons_launcher:create work: one direct command, no build_runner involved). If your project already runs dart run build_runner build for freezed/json_serializable/ injectable_generator, this lets that same command also regenerate lib/white_label.g.dart โ€” but it requires a build.yaml sources: override (white_label.yaml sits at the project root, outside build_runner's default lib/** scan) that generate/init do not. Both paths call the exact same generation logic (lib/src/generation/dart_config_generator.dart) โ€” pick whichever fits your project, they produce identical output.
white_label_kit
Unified white-label build platform for Flutter apps.