Skip to main content

White-label & multi-tenant

If you ship a single binary to multiple brands (white-label SaaS, B2B workspaces, multi-tenant apps), you don't want to rebuild the app for every tenant. Swiftpatch lets you rotate the deployment key at runtime — the next update check targets the new tenant's release line.

The core API

import { SwiftPatch } from '@swiftpatch/react-native';

const sp = new SwiftPatch({ deploymentKey: 'dep_default' });
await sp.init();

// Soft switch: next checkForUpdate uses the new tenant's releases.
// Current bundle keeps running.
await sp.setDeploymentKey('dep_tenant_a');

// Hard switch: roll back to the store bundle and wipe all slots.
// User boots clean on the new tenant.
await sp.setDeploymentKey('dep_tenant_b', { clearCurrentBundle: true });

// Read the persisted key
const current = sp.getDeploymentKey(); // string | null

// Subscribe to changes — for example, re-theme the app after auth.
const unsub = sp.onTenantChanged((key) => {
console.log('Tenant switched to:', key);
reloadTenantTheme();
});

The key is persisted in native preferences (UserDefaults on iOS, SharedPreferences on Android). It survives cold starts.

Soft vs. hard switch

Soft switch (default)

await sp.setDeploymentKey('dep_tenant_a');
  • The current bundle keeps running.
  • The next checkForUpdate call targets the new tenant.
  • When a release is ready, the user sees the update banner.

Use this when tenants share a compatible codebase and you just want to route to a different release line.

Hard switch

await sp.setDeploymentKey('dep_tenant_b', { clearCurrentBundle: true });
  • Rolls back to the store-embedded bundle.
  • Wipes NEW, STABLE, and temp slots.
  • User reboots on the factory JavaScript.

Use this when the previous tenant's bundle is fundamentally incompatible — different UI shell, different backend, different behavior. You're effectively resetting the app to its shipped state, then letting the new tenant's releases take over.

Integration patterns

Pattern 1 — tenant from auth

function App() {
const { user } = useAuth();

useEffect(() => {
if (!user?.tenantKey) return;

const sp = SwiftPatch.instance; // your shared instance
sp.setDeploymentKey(user.tenantKey).catch((err) => {
console.warn('Tenant switch failed:', err);
});
}, [user?.tenantKey]);

return <YourApp />;
}

Pattern 2 — sign-out with clean slate

async function signOut() {
await auth.signOut();
await sp.setDeploymentKey(DEFAULT_KEY, { clearCurrentBundle: true });
// User now running the shipped bundle, ready to re-auth into any tenant.
}

Pattern 3 — per-tenant branded UI

Listen for tenant changes and re-fetch theme tokens:

sp.onTenantChanged(async (key) => {
const tenantTheme = await fetchTenantTheme(key);
themeStore.setTheme(tenantTheme);
});

CI/CD per tenant

Each tenant gets its own deployment key in the dashboard. In CI, deploy to the right key:

.github/workflows/deploy-tenant.yml
strategy:
matrix:
tenant: [acme, contoso, fabrikam]

steps:
- name: Deploy OTA
env:
SWIFTPATCH_CI_TOKEN: ${{ secrets[format('TOKEN_{0}', matrix.tenant)] }}
run: |
npx swiftpatch-cli deploy \
-a ${{ matrix.tenant }}-app \
-p ios --hermes

The deployment key the binary holds determines which tenant's releases it picks up. Once you rotate the key at runtime (via setDeploymentKey), the same binary consumes a different release line.

Considerations

Crypto keys per tenant

If each tenant has its own signing key, the binary must hold the matching public key. Two options:

  • Ship every tenant's public key in a map, select by tenant id. Slightly larger binary, no extra network.
  • Fetch public keys from a trusted endpoint on first use, cache them. Smaller binary, extra network dependency.

The SDK supports overriding publicKey at init time. For runtime switching, re-initialize with the new key:

// After setDeploymentKey, refresh the SDK to pick up the new public key.
// Only needed if each tenant signs with a different key.

For teams using a single signing key across tenants (a common pattern when tenants share the same engineering team), no extra work is needed.

Channel + tenant

Channels and tenants compose — each tenant can have its own production, staging, beta. Set the channel per-tenant:

await sp.setDeploymentKey(tenantKey);
await setChannel(user.isBetaTester ? 'beta' : 'production');

Next steps

  • Channels — channel switching within a single tenant.
  • Methods — the full SwiftPatch class API.
  • CI/CD — deploy to multiple tenants from one pipeline.