From 58a2d4f9b325937d30cbbb4471cc2042518a4729 Mon Sep 17 00:00:00 2001 From: Dominik Toton Date: Wed, 27 May 2026 18:57:42 +0200 Subject: [PATCH 1/2] feat(analytics): expose serverURL in Mixpanel.init() Adds an optional serverURL named parameter on Mixpanel.init() and forwards it to the native iOS, Android, macOS, and web SDKs at initialization time. Lets callers pin a proxy host or the EU endpoint without making a separate setServerURL call after init. --- .../MixpanelFlutterPlugin.java | 5 ++ .../lib/mixpanel_flutter.dart | 9 +++- .../lib/mixpanel_flutter_web.dart | 5 ++ .../Classes/SwiftMixpanelFlutterPlugin.swift | 3 ++ .../test/mixpanel_flutter_test.dart | 53 +++++++++++++++++++ 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/packages/mixpanel_flutter/android/src/main/java/com/mixpanel/mixpanel_flutter/MixpanelFlutterPlugin.java b/packages/mixpanel_flutter/android/src/main/java/com/mixpanel/mixpanel_flutter/MixpanelFlutterPlugin.java index 82afcf8e..f14436d4 100644 --- a/packages/mixpanel_flutter/android/src/main/java/com/mixpanel/mixpanel_flutter/MixpanelFlutterPlugin.java +++ b/packages/mixpanel_flutter/android/src/main/java/com/mixpanel/mixpanel_flutter/MixpanelFlutterPlugin.java @@ -239,6 +239,7 @@ private void handleInitialize(MethodCall call, Result result) { Boolean optOutTrackingDefault = call.argument("optOutTrackingDefault"); Boolean trackAutomaticEvents = call.argument("trackAutomaticEvents"); + String serverURL = call.argument("serverURL"); // Parse feature flags config if provided Map featureFlagsMap = call.>argument("featureFlags"); @@ -273,6 +274,10 @@ private void handleInitialize(MethodCall call, Result result) { .optOutTrackingDefault(optOutTrackingDefault == null ? false : optOutTrackingDefault) .superProperties(superAndMixpanelProperties); + if (serverURL != null && !serverURL.isEmpty()) { + optionsBuilder.serverURL(serverURL); + } + if (featureFlagsEnabled != null && featureFlagsEnabled) { FeatureFlagOptions.Builder ffBuilder = new FeatureFlagOptions.Builder().enabled(true); if (featureFlagsContext != null) { diff --git a/packages/mixpanel_flutter/lib/mixpanel_flutter.dart b/packages/mixpanel_flutter/lib/mixpanel_flutter.dart index 1f51e73e..1a38f8a4 100644 --- a/packages/mixpanel_flutter/lib/mixpanel_flutter.dart +++ b/packages/mixpanel_flutter/lib/mixpanel_flutter.dart @@ -364,13 +364,17 @@ class Mixpanel { /// * [superProperties] Optional super properties to register /// * [config] Optional A dictionary of config options to override (WEB ONLY) /// * [featureFlags] Optional Feature flags configuration + /// * [serverURL] Optional base URL used for Mixpanel API requests. Useful if you + /// need to proxy Mixpanel requests, or to route data to Mixpanel's EU servers + /// (`https://api-eu.mixpanel.com`). Defaults to `https://api.mixpanel.com`. /// static Future init(String token, {bool optOutTrackingDefault = false, required bool trackAutomaticEvents, Map? superProperties, Map? config, - FeatureFlagsConfig? featureFlags}) async { + FeatureFlagsConfig? featureFlags, + String? serverURL}) async { var allProperties = {'token': token}; allProperties['optOutTrackingDefault'] = optOutTrackingDefault; allProperties['trackAutomaticEvents'] = trackAutomaticEvents; @@ -380,6 +384,9 @@ class Mixpanel { if (featureFlags != null) { allProperties['featureFlags'] = featureFlags.toMap(); } + if (serverURL != null && _MixpanelHelper.isValidString(serverURL)) { + allProperties['serverURL'] = serverURL; + } await _channel.invokeMethod('initialize', allProperties); return Mixpanel(token); } diff --git a/packages/mixpanel_flutter/lib/mixpanel_flutter_web.dart b/packages/mixpanel_flutter/lib/mixpanel_flutter_web.dart index 24c62457..c530a3b3 100644 --- a/packages/mixpanel_flutter/lib/mixpanel_flutter_web.dart +++ b/packages/mixpanel_flutter/lib/mixpanel_flutter_web.dart @@ -218,6 +218,11 @@ class MixpanelFlutterPlugin { dynamic config = args['config']; Map initConfig = Map.from(config ?? {}); + final serverURL = args['serverURL']; + if (serverURL is String && serverURL.isNotEmpty) { + initConfig['api_host'] = serverURL; + } + // Handle feature flags configuration dynamic featureFlags = args['featureFlags']; if (featureFlags != null && featureFlags is Map) { diff --git a/packages/mixpanel_flutter/swift/Classes/SwiftMixpanelFlutterPlugin.swift b/packages/mixpanel_flutter/swift/Classes/SwiftMixpanelFlutterPlugin.swift index 76859c3c..27213152 100644 --- a/packages/mixpanel_flutter/swift/Classes/SwiftMixpanelFlutterPlugin.swift +++ b/packages/mixpanel_flutter/swift/Classes/SwiftMixpanelFlutterPlugin.swift @@ -192,6 +192,8 @@ public class SwiftMixpanelFlutterPlugin: NSObject, FlutterPlugin { let superProperties = arguments["superProperties"] as? [String: Any] self.token = token let trackAutomaticEvents = arguments["trackAutomaticEvents"] as! Bool + let serverURLArg = arguments["serverURL"] as? String + let serverURL = (serverURLArg != nil && !serverURLArg!.isEmpty) ? serverURLArg : nil // Check for feature flags configuration var featureFlagOptions: FeatureFlagOptions? = nil @@ -213,6 +215,7 @@ public class SwiftMixpanelFlutterPlugin: NSObject, FlutterPlugin { trackAutomaticEvents: trackAutomaticEvents, optOutTrackingByDefault: optOutTrackingDefault ?? false, superProperties: MixpanelTypeHandler.mixpanelProperties(properties: superProperties, mixpanelProperties: mixpanelProperties), + serverURL: serverURL, featureFlagOptions: featureFlagOptions ) instance = Mixpanel.initialize(options: options) diff --git a/packages/mixpanel_flutter/test/mixpanel_flutter_test.dart b/packages/mixpanel_flutter/test/mixpanel_flutter_test.dart index 7b202ed5..2c07e12d 100644 --- a/packages/mixpanel_flutter/test/mixpanel_flutter_test.dart +++ b/packages/mixpanel_flutter/test/mixpanel_flutter_test.dart @@ -97,6 +97,59 @@ void main() { }); + test('check initialize with serverURL forwards to native', () async { + _mixpanel = await Mixpanel.init( + "test token", + optOutTrackingDefault: false, + trackAutomaticEvents: true, + serverURL: 'https://api-eu.mixpanel.com', + ); + expect( + methodCall, + isMethodCall( + 'initialize', + arguments: { + 'token': "test token", + 'optOutTrackingDefault': false, + 'trackAutomaticEvents': true, + 'mixpanelProperties': { + '\$lib_version': sdkVersion, + 'mp_lib': 'flutter', + }, + 'superProperties': null, + 'config': null, + 'serverURL': 'https://api-eu.mixpanel.com', + }, + ), + ); + }); + + test('check initialize omits serverURL when blank', () async { + _mixpanel = await Mixpanel.init( + "test token", + optOutTrackingDefault: false, + trackAutomaticEvents: true, + serverURL: '', + ); + expect( + methodCall, + isMethodCall( + 'initialize', + arguments: { + 'token': "test token", + 'optOutTrackingDefault': false, + 'trackAutomaticEvents': true, + 'mixpanelProperties': { + '\$lib_version': sdkVersion, + 'mp_lib': 'flutter', + }, + 'superProperties': null, + 'config': null, + }, + ), + ); + }); + test('check setServerURL', () async { _mixpanel.setServerURL("https://api-eu.mixpanel.com"); expect( From f1867eeb3e5b2e6977dba9d945d0dac741357727 Mon Sep 17 00:00:00 2001 From: Dominik Toton Date: Tue, 2 Jun 2026 08:23:29 +0200 Subject: [PATCH 2/2] test(analytics): cover null serverURL in init; simplify swift normalization --- .../Classes/SwiftMixpanelFlutterPlugin.swift | 3 +-- .../test/mixpanel_flutter_test.dart | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/mixpanel_flutter/swift/Classes/SwiftMixpanelFlutterPlugin.swift b/packages/mixpanel_flutter/swift/Classes/SwiftMixpanelFlutterPlugin.swift index 27213152..2db58d4c 100644 --- a/packages/mixpanel_flutter/swift/Classes/SwiftMixpanelFlutterPlugin.swift +++ b/packages/mixpanel_flutter/swift/Classes/SwiftMixpanelFlutterPlugin.swift @@ -192,8 +192,7 @@ public class SwiftMixpanelFlutterPlugin: NSObject, FlutterPlugin { let superProperties = arguments["superProperties"] as? [String: Any] self.token = token let trackAutomaticEvents = arguments["trackAutomaticEvents"] as! Bool - let serverURLArg = arguments["serverURL"] as? String - let serverURL = (serverURLArg != nil && !serverURLArg!.isEmpty) ? serverURLArg : nil + let serverURL = (arguments["serverURL"] as? String).flatMap { $0.isEmpty ? nil : $0 } // Check for feature flags configuration var featureFlagOptions: FeatureFlagOptions? = nil diff --git a/packages/mixpanel_flutter/test/mixpanel_flutter_test.dart b/packages/mixpanel_flutter/test/mixpanel_flutter_test.dart index 2c07e12d..5d72a9dd 100644 --- a/packages/mixpanel_flutter/test/mixpanel_flutter_test.dart +++ b/packages/mixpanel_flutter/test/mixpanel_flutter_test.dart @@ -150,6 +150,32 @@ void main() { ); }); + test('check initialize omits serverURL when null', () async { + _mixpanel = await Mixpanel.init( + "test token", + optOutTrackingDefault: false, + trackAutomaticEvents: true, + serverURL: null, + ); + expect( + methodCall, + isMethodCall( + 'initialize', + arguments: { + 'token': "test token", + 'optOutTrackingDefault': false, + 'trackAutomaticEvents': true, + 'mixpanelProperties': { + '\$lib_version': sdkVersion, + 'mp_lib': 'flutter', + }, + 'superProperties': null, + 'config': null, + }, + ), + ); + }); + test('check setServerURL', () async { _mixpanel.setServerURL("https://api-eu.mixpanel.com"); expect(