White-Label Flutter Architecture: One Codebase, Two App Stores

Most Flutter multi-tenant tutorials stop at colors and logos. Real white-label mobile platforms are harder: different app stores, different signing teams, different OAuth clients, different backend infrastructure - all while maintaining a single codebase. We faced exactly that problem while building two veterinary telemedicine apps running in production: PawSquad and VetCloud. Both needed to coexist independently, with separate identities across iOS and Android, while sharing the same engineering foundation underneath.

Available on LinkedIn LinkedIn

Why Theme Switching Wasn't Enough

PawSquad started as a veterinary telemedicine platform serving more than 350,000 pet owners, with a Flutter-based mobile application acting as the primary client-facing layer across iOS and Android.

At the same time, VetCloud was evolving as the broader platform architecture behind the system itself, the operational and multi-tenant foundation from which PawSquad had originally evolved.

The challenge was not a simple visual rebrand. Both applications needed to coexist as fully separate production products: different app store identities, different signing teams, different OAuth clients, different backend environments, while still sharing a single engineering codebase underneath.

Both applications required full deployment-level separation:

  • Independent App Store listings
  • Independent Google Play listings
  • Separate bundle identifiers
  • Separate signing certificates
  • Separate OAuth clients
  • Separate payment infrastructure
  • Separate backend environments
  • Separate monitoring stacks

At the same time, both apps had to coexist from the same repository, without code duplication and without maintaining separate branches.

Flutter's built-in theming system handles runtime concerns well: colors, typography, assets, and UI styling. But production white-labeling extends beyond Dart. Bundle identifiers, signing teams, native app icons, Info.plist values, Gradle configuration, OAuth URL schemes, and platform-level metadata all live in native build systems outside Flutter itself.

The architecture needed to work at two separate layers: runtime branding inside Flutter, and native build-time branding at the platform level.

The Two-Layer Branding Architecture

The solution separates branding into two clearly defined layers.

Layer 1 - Flutter Runtime Branding

At the Flutter level, every tenant is represented by a dedicated BrandConfig object. The configuration contains:

  • Colors and theme settings
  • Logos and assets
  • App naming
  • Support information
  • Social links
  • Tenant-specific UI behavior

Each application instance selects its active brand through a flavor configuration loaded from the application entry point. Different entry points initialize different environments: QA, UAT, Production, and VetCloud production.

The flavor configuration carries more than visual branding. It also contains:

  • API endpoints
  • Authentication configuration
  • Stripe identifiers
  • Twilio credentials
  • OAuth client IDs
  • App store identifiers
  • Tenant-specific feature configuration

The important architectural decision was centralization. Every environment-specific value lives in one place. Views access branding and configuration through a single global flavor object rather than scattered conditional logic throughout the application. That keeps tenant separation predictable and maintainable.

Layer 2 - Native Build-Time Branding

Flutter controls the runtime experience. Native platforms control application identity. To manage that layer, we built a tenant switching script called before every build. The script updates native platform configuration automatically:

  • Bundle identifiers
  • App display names
  • Signing configuration
  • URL schemes
  • App launcher icons
  • Apple team identifiers
  • Android application IDs
  • Platform metadata

The script accepts a single argument - the tenant identifier - and applies the correct native identity for the target application automatically.

On Android, it modifies Gradle configuration and copies tenant-specific assets into the appropriate launcher icon directories. On iOS, it updates values inside Info.plist, project.pbxproj, asset catalogs, and related build configuration files.

The result is two fully separate mobile applications distributed through independent app store accounts while still sharing the same Flutter codebase underneath.

White-label Flutter architecture: PawSquad (350,000+ users) and VetCloud both deployed from one git repository via BrandConfig, Build Scripts, and Final App. Stats: 0 duplicate lines, 4 flavor configs, under 3 days to add a new tenant, 2 live App Store listings.
Two production apps, one git repository - the two-layer branding architecture that makes it work

Connecting Branding to the Build Pipeline

The two branding layers integrate directly into the build toolchain. Local development environments trigger tenant switching automatically before launching the application. Build scripts and Makefile targets apply the correct configuration before generating Android App Bundles or iOS archives.

For iOS specifically, Xcode archive builds run the branding script automatically during the build phase. This prevents accidental archive generation with incorrect bundle identifiers or signing configuration, a common problem in white-label mobile projects.

One issue surfaced early during implementation: both the branding script and Xcode attempted to modify the project.pbxproj file simultaneously during archive builds. The fix was simple but important. This eliminated archive-time conflicts and stabilized the release pipeline.

Production white-label Flutter systems tend to expose edge cases that most tutorials never cover. We have solved many of them the hard way, and we are always open to sharing practical engineering experience with teams building similar platforms.

Asset Organization and Tenant Isolation

Asset management follows the same tenant split. Each tenant owns its own asset directory containing:

  • Logos
  • Launcher icons
  • Branding assets
  • Tenant-specific illustrations

Shared assets: fonts, common SVG icons, reusable UI elements, and generic application graphics, remain centralized. The application resolves tenant-specific assets dynamically through the active brand configuration. This allows both apps to share the same UI components while still presenting completely different branding and visual identity to end users.

Authentication is also fully tenant-isolated. Each app uses separate:

  • OAuth clients
  • Scopes
  • Redirect schemes
  • API credentials
  • Authentication flows

No code changes are required to introduce new authentication configurations. Everything is driven through the flavor system.

What This Architecture Enables

The biggest advantage of the architecture is operational scalability. Adding a new tenant does not require a new repository or a forked mobile application. In practice, onboarding another brand means:

  • Creating a new brand configuration
  • Adding a new flavor file
  • Adding tenant assets
  • Registering the tenant in the build script

The underlying application logic remains untouched.

Today, PawSquad and VetCloud coexist in production as fully separate mobile products:

  • Separate App Store listings
  • Separate Google Play listings
  • Separate Apple developer teams
  • Separate monitoring environments
  • Separate payment infrastructure

But underneath: one repository, one shared architecture, one CI/CD pipeline, one engineering team.

The tradeoff is intentional. Some rarely changing values still require manual platform-level updates. For example deep link domains or certain provider keys. We deliberately automated the configuration that changes frequently and documented the rest. The goal was not theoretical perfection. The goal was a maintainable production system.

Beyond Theme Switching

Most discussions about Flutter white-labeling focus on colors and logos because those are the easy parts. The real complexity lives in bundle identifiers, signing systems, OAuth configuration, app store separation, deployment pipelines, and native platform tooling.

Our two-layer approach - Flutter runtime branding combined with native build-time switching - allowed us to ship two fully independent production apps from a single codebase without duplication or parallel repositories.

It is not the smallest architecture. It is the architecture that solved the real production problem.

The Flutter app is one piece of the broader platform. Read about how we designed the full veterinary telemedicine architecture that powers both brands - from real-time video to AI-assisted consultation reporting.

Need to ship multiple branded apps from one Flutter codebase?

Smartnet Technologies builds white-label mobile platforms for healthcare and telemedicine - from architecture through to app store deployment.

Contact us →