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
checkForUpdatecall 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:
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');