White-Label Mobile App Development
Imagine: you have 10 clients, each needs their own app with unique branding but unified business logic. Without white-label, you risk drowning in code copying and errors, and the budget for maintaining multiple codebases can exceed the cost of the product itself. Our solution is a single codebase with parameterization that allows launching a new client in days. Typical savings when using white-label instead of building from scratch: 60-80% time and budget. Each client gets custom icons, colors, and content, while you get one repository and unified CI/CD. Our experience—50+ projects over 10+ years—guarantees a clean architecture from the first commit.
Order white-label app development and get a free initial audit of your current architecture.
For example, recently we launched a platform for a franchise network: five brands, each with its own color scheme and feature set, on a single codebase. Development costs were cut by 70%, time-to-market for a new brand was 10 days. In this article, we'll discuss architectural decisions, CI/CD, and common mistakes.
Why white-label approach is better than building from scratch?
A single repository means all logic is in one place; updates propagate to all clients. Customization without duplication is achieved via Product Flavors (Android) and Targets (iOS), which override only what differs. As a result, a new tenant launches in 1-2 weeks instead of 3-6 months. Scalability: adding a new client does not require rewriting code—just configure configs. More than 90% of the code remains common across all tenants.
Architectural Decisions
Product Flavors (Android) and Schemes/Targets (iOS)
The basic platform mechanism—Build Flavors on Android and Xcodeconfig/Targets on iOS—allows changing resources, string constants, and compiler flags without modifying code. As documented by Apple on Xcode Targets, each Target can have its own set of resources. On Android similarly, as described in the Product Flavors documentation.
Android: product flavors
// app/build.gradle.kts
android {
flavorDimensions += "tenant"
productFlavors {
create("brandA") {
dimension = "tenant"
applicationId = "com.brandA.app"
resValue("string", "app_name", "Brand A")
buildConfigField("String", "API_BASE_URL", "\"https://api.brand-a.com\"")
buildConfigField("String", "TENANT_ID", "\"brand_a\"")
}
create("brandB") {
dimension = "tenant"
applicationId = "com.brandB.app"
resValue("string", "app_name", "Brand B")
buildConfigField("String", "API_BASE_URL", "\"https://api.brand-b.com\"")
buildConfigField("String", "TENANT_ID", "\"brand_b\"")
}
}
}
Resources (icons, colors, strings) are overridden via directories: app/src/brandA/res/drawable/ic_launcher.png and similarly for brandB.
iOS: Xcodeconfig + Multiple Targets
Each client gets a separate Target sharing the same code:
// Config.xcconfig
API_BASE_URL = https://api.brand-b.com
TENANT_ID = brand_b
FEATURE_PREMIUM_ENABLED = YES
FEATURE_CHAT_ENABLED = NO
Values from xcconfig are read into Info.plist and then into code.
Feature Flags per Tenant
Different clients often have different feature sets. Flags embedded in xcconfig/BuildConfig determine what gets compiled:
// Conditional compilation via BuildConfig
if (BuildConfig.FEATURE_PREMIUM_ENABLED) {
setupPremiumFeatures()
}
For dynamic flags (changeable without recompilation), we use Firebase Remote Config with a project per tenant or a single project with tenant-specific parameters.
Theming Engine
Colors, fonts, spacing—not hardcoded in code but loaded from a Theme config:
// Android: AppTheme via theme attributes
data class TenantTheme(
val primaryColor: Color,
val accentColor: Color,
val fontFamily: String,
val cornerRadius: Float,
val logoResId: Int
)
object ThemeProvider {
fun getTheme(tenantId: String): TenantTheme = when (tenantId) {
"brand_a" -> TenantTheme(
primaryColor = Color(0xFF1A73E8),
accentColor = Color(0xFFFB8C00),
fontFamily = "Roboto",
cornerRadius = 8f,
logoResId = R.drawable.logo_brand_a
)
"brand_b" -> TenantTheme(/* ... */)
else -> defaultTheme
}
}
For React Native and Flutter, the architecture is similar but through Theme Context (RN) or ThemeData (Flutter).
How to Organize CI/CD for Multiple Artifacts?
Every push to main must build all tenant builds. On GitHub Actions:
Example CI/CD matrix
strategy:
matrix:
flavor: [brandA, brandB, brandC]
steps:
- name: Build APK for ${{ matrix.flavor }}
run: ./gradlew assemble${{ matrix.flavor }}Release
- name: Upload to Play Store
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJson: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.${{ matrix.flavor }}.app
releaseFiles: app/build/outputs/apk/${{ matrix.flavor }}/release/*.apk
Each flavor is deployed to a separate Play Store account or to one account with different Package Names. For iOS, use Fastlane with a scheme matrix.
How to Ensure Build Stability for Each Tenant?
Automated testing is critical: for each flavor in CI, run unit tests and UI tests on simulators. This prevents regressions when adding a new brand. We recommend keeping coverage >80% for modules with tenant logic.
Comparison of Approaches: Product Flavors vs Runtime Configuration
| Parameter | Product Flavors (Android) / Targets (iOS) | Runtime Configuration |
|---|---|---|
| Resource customization | Full (icons, strings, colors) | Limited (runtime only) |
| Build speed | Slower (many artifacts) | Faster (single APK) |
| Code isolation | Full (compiler cuts unused code) | Partial (all code in build) |
We recommend a hybrid: static resources via flavors, dynamic features via runtime flags.
Stages of White-Label App Development
| Stage | Description | Duration |
|---|---|---|
| Analysis | Gather requirements, define tenants, set up servers | 1-2 weeks |
| Design | Architecture, configure flavors/targets, CI/CD | 1-2 weeks |
| Development | Implement modules, theme engine, feature flags | 4-8 weeks |
| Testing | Auto-tests for each tenant, regression | 2-3 weeks |
| Deployment | Deploy to stores, set up monitoring | 1 week |
Common Mistakes and How to Avoid Them
Tenant logic in business code. For example, if (tenantId == "brand_a") showSpecialButton() in ViewModel quickly leads to chaos. Correct: tenant-specific behavior through DI—inject strategies depending on flavor.
Shared strings with hardcoded brand names. The brand name should only be in the tenant directory. Use resources (strings.xml/Localizable.strings) for each brand separately.
Single Firebase project. Each tenant must have its own google-services.json, otherwise analytics and push notification conflicts arise.
Lack of auto tests for each flavor. Run tests for each flavor in CI. This is the only way to ensure a new brand doesn't break existing ones.
Typical tenant onboarding checklist: 1. Copy tenant directory template 2. Set icons, colors, strings 3. Create API keys and Firebase project 4. Add flavor to CI matrix 5. Test build and deploy.
What's Included
- Documentation: architecture diagram, instructions for adding a new tenant, description of CI/CD pipelines.
- Access: source code, repository, CI/CD configuration, store keys.
- Training: workshop for your team on working with white-label architecture.
- Support: 2-3 months after launch to resolve potential issues.
Time Estimates
MVP white-label app with 2-3 tenants on one platform (iOS or Android): 8-14 weeks. Cross-platform implementation with 5+ tenants and full CI/CD: 16-24 weeks. Cost is calculated individually after requirement analysis. Get a free consultation for your white-label project—contact us for an assessment.







