PostHog Flutter SDK migration guides
Migrating from posthog_flutter 5.x to 6.0
Requirements
| 5.x | 6.0 | |
|---|---|---|
| iOS deployment target | 13.0 | 15.0 |
| macOS deployment target | 10.15 | 11.0 |
Android minSdkVersion declared by the plugin | 21 | 23 |
| iOS and macOS dependency manager | CocoaPods or Swift Package Manager | Swift Package Manager only |
| Flutter SDK | 3.27 | 3.41 |
posthog_flutter 6.0 needs the Flutter SDK 3.41 or later on every platform: flutter pub get fails on an older Flutter, including in Android-only apps. The Dart minimum (>=3.6.0) is unchanged. Only iOS and macOS builds need Swift Package Manager.
iOS and macOS
Raise your app's deployment targets. A lower target fails the build. Set Minimum Deployments for the Runner target in Xcode (Runner > General > Minimum Deployments), which sets IPHONEOS_DEPLOYMENT_TARGET and MACOSX_DEPLOYMENT_TARGET.
If you keep a Podfile for other plugins, raise its platform too:
Move from CocoaPods to Swift Package Manager
posthog_flutter 6.0 has no podspec. On iOS and macOS, it installs only through Swift Package Manager.
If you ignore this, the build stops. With Swift Package Manager turned off, flutter build exits with Plugin posthog_flutter is only compatible with Swift Package Manager.
-
Use Flutter 3.41 or later. The plugin's
Package.swiftdepends on theFlutterFrameworkpackage, which Flutter generates starting with 3.41. -
Turn Swift Package Manager on if it's off. It's on by default from Flutter 3.44. On 3.41 to 3.43, or if you turned it off, run:
sh -
Run your app. Flutter adds Swift Package Manager to the Xcode project on its own.
Other plugins that only support CocoaPods keep working: Flutter installs them through your Podfile and installs posthog_flutter as a Swift package. Keep the Podfile in that case.
If every plugin in your app supports Swift Package Manager, you can remove CocoaPods. This is optional. Follow Flutter's steps.
Add-to-app: flutter build swift-package still installs a Flutter module's plugins with CocoaPods on current stable releases, so it stops with Unable to find a specification for 'posthog_flutter' depended upon by 'FlutterPluginRegistrant' because posthog_flutter has no podspec. Flutter fixed this in flutter/flutter#184590, which isn't in a stable release yet. If you embed Flutter as a module in an iOS or macOS app, stay on posthog_flutter 5.x until it is. On Flutter 3.44 or later, you can instead embed Flutter in an iOS or macOS app from a Flutter app project (flutter create, not -t module) by following Flutter's Swift Package Manager add-to-app steps for iOS or macOS; this works with posthog_flutter 6.
Android
posthog-android has required minSdkVersion 23 since 3.39.0, so an app on 21 or 22 already could not build with this plugin. The plugin now declares the same value, and you have nothing to change.
Native SDKs and Xcode
| 5.x | 6.0 | |
|---|---|---|
posthog-ios | >= 3.92.3, < 4.0.0 | >= 4.0.0, < 5.0.0 |
posthog-android | [3.75.0, 4.0.0) | [4.0.0, 5.0.0) |
| Xcode | Not set by the plugin | 26 or later |
Don't add these native SDKs to your app separately at a different major version.
Breaking API changes
These all fail at compile time unless noted otherwise.
getFeatureFlagResult(sendEvent:) is now sendFeatureFlagEvent:
The parameter is also now bool?. See the default change below.
PostHogConfig.apiKey is removed
Read projectToken instead. The constructor already takes the token as its first argument.
com.posthog.posthog.API_KEY is removed
Apps that start the SDK from AndroidManifest.xml or Info.plist must rename the key. This does not fail at build time. With only the old key, the SDK logs an error and does not start, so the app sends no data.
Posthog().enable() and disable() are removed
PostHogConfig.sendFeatureFlagEvents is removed
Posthog().getFeatureFlagPayload() is removed
getFeatureFlagPayload() never captured $feature_flag_called on iOS, macOS, Android, Windows or Linux. Pass sendFeatureFlagEvent: false to keep it that way. Without it, the replacement call captures the event when your config allows it.
PostHogSessionReplayConfig.debouncerDelay is removed
PostHogDataMode.cellular is removed
It always behaved like any, so sending does not change.
toMap() and PostHogFeatureFlagResult.fromMap() are removed
They serialized the internal platform-channel format and have no replacement. toMap() is gone from PostHogConfig, PostHogBootstrapConfig, PostHogLogsConfig, PostHogRageClickConfig, PostHogSessionReplayConfig, PostHogErrorTrackingConfig and PostHogExceptionStepsConfig. To build a result, for example in a test, call the PostHogFeatureFlagResult constructor, which stays public.
beforeSend callbacks are synchronous
BeforeSendCallback is now PostHogEvent? Function(PostHogEvent), and BeforeSendLogCallback is PostHogLogRecord? Function(PostHogLogRecord). An async callback, or one declared to return FutureOr<PostHogEvent?>, is a compile error. Load what it needs ahead of time and read it synchronously.
Platform interface changes
These affect only code that calls, extends or fakes PosthogFlutterPlatformInterface, PosthogFlutterIO or PosthogFlutterWeb directly, such as test mocks. Posthog().optIn() and optOut() are unaffected.
enable()anddisable()are nowoptIn()andoptOut().getFeatureFlagPayload()is removed.getFeatureFlagResult({required String key, bool sendEvent = true})is nowgetFeatureFlagResult({required String key, bool? sendFeatureFlagEvent}).capture,screen,identify,setPersonProperties,alias,groupandcaptureExceptiontake anoptionsmap.captureAi()is new. The base class throwsUnimplementedErrorunless you override it.isFeatureEnabled()takesdefaultValue.reloadFeatureFlags()returns aFuture<PostHogFeatureFlagsLoaded>.setAppLanguage(String? language)is new. The base class does nothing unless you override it.
PostHogConfig.onFeatureFlags is removed
Call Posthog().onFeatureFlags() before setup(), which also covers the first load. The callback receives a PostHogFeatureFlagsLoaded, with flags, variants and errorsLoading, and reloadFeatureFlags() now returns a Future<PostHogFeatureFlagsLoaded>.
Behavior changes
Events go to capture v1
This is a silent change if you use a reverse proxy or self-host. On iOS, macOS, Android, Windows and Linux, events go to /i/v1/analytics/events instead of /batch/, and captureAi events go to /i/v1/ai/events. PostHog Cloud and the managed reverse proxy already serve the new paths. Most apps upgrade without code changes, and nothing here produces a compile error.
| Platform | Sent by | 6.0 |
|---|---|---|
| iOS and macOS | posthog-ios 4.x | capture v1 |
| Android | posthog-android 4.x | capture v1 |
| Windows and Linux | posthog_flutter's own Dart client | capture v1 |
| Web | the posthog-js your app loads | unchanged |
Web doesn't move to capture v1 in this release. It keeps posthog-js and its current endpoints. On web, options are sent as the legacy option properties, and captureAi sends nothing.
For the changes every capture v1 SDK shares, such as event options, limits and per-event results, see Upgrading to capture v1 SDKs. On iOS, macOS and Android, capture v1 comes from the native SDKs, so the Capture v1 sections of the iOS and Android guides apply there too. Windows and Linux are covered below.
On every platform except web, an event's properties are now the SDK's, then registered properties, then the ones you pass, so your value wins over the SDK's for that event. In 5.x, the SDK's values won.
Checklist
You need to change code if your app does any of these:
- sends events to a self-hosted PostHog or a reverse proxy that doesn't serve
/i/v1/analytics/eventsand/i/v1/ai/events - sends events through a reverse proxy set up from PostHog's proxy guides before 29 October 2026, which strips
Authorization - identifies users with a distinct ID over 200 UTF-8 bytes, such as a JWT
- captures events whose name is over 200 UTF-8 bytes
- sets
projectTokento a personal API key (phx_...) - reads
$session_idor$window_idfrom the raw request body, for example in a proxy - relies on a
Retry-Afterlonger than 30 seconds - registers or passes a property with the same key as one the SDK sets, such as
$osor$is_identified, and expects the SDK's value to win - on any platform except web, drops the event's
optionsinbeforeSendby returning a newPostHogEventor replacingevent.options - on Windows or Linux, alerts on the SDK's debug log lines
- on Windows or Linux, calls
exit()without awaiting the calls before it orPosthog().close()
Reverse proxies and self-hosting
Before you upgrade, make your own proxy:
- forward
/i/v1/analytics/eventsand/i/v1/ai/events - pass
Authorization(the project token, sent asBearer <token>), everyPostHog-*header,Content-Type,Content-EncodingandUser-Agentthrough unchanged. PostHog answers401withoutAuthorizationand400without the others, and the batch is dropped. - if your proxy strips other credentials, keep
Authorizationvalues that start withBearer phc_,Bearer phs_orBearer phx_, plus the exactBearer <key>of an older key without a prefix - accept request bodies up to 20 MB. AI requests are often larger than 1 MB, the default limit in nginx and ingress-nginx.
- pass PostHog's response body through unchanged, because a 2xx whose body isn't PostHog's
{"results": {...}}drops the events instead of retrying them
PostHog's managed reverse proxy already does this and needs no change. A proxy set up from PostHog's proxy guides before 29 October 2026 strips Authorization, so PostHog answers 401 and the events are dropped. Update it from the current guide.
The SDK doesn't fall back to /batch/. On iOS, macOS, Android, Windows and Linux, a 404 is retried, events stay queued until the route is served, and the SDK prints one warning, also without debug. Other statuses that aren't retried, such as 401, 403 or 405, drop the batch. Web keeps posthog-js, which doesn't send to these routes. For iOS and Android details, see the iOS and Android guides.
Feature flag payloads
On iOS, macOS and Android, a malformed (such as a plain-text hello), empty or whitespace-only payload from getFeatureFlagResult() is now null instead of the raw string. On Android, a JSON null payload is also null instead of the string "null".
On iOS and macOS, string values in PostHogBootstrapConfig.featureFlagPayloads, such as "123", stay strings instead of being decoded to numbers or booleans.
A flag whose variant is an empty string reads as disabled on iOS, macOS, Android, Windows and Linux, as it already did on web. isFeatureEnabled() returns false, with or without defaultValue, getFeatureFlagResult() returns enabled: false, onFeatureFlags listeners and reloadFeatureFlags() leave the flag out, and so does $active_feature_flags on captured events. variant, getFeatureFlag() and the event's $feature/<key> property still return "".
beforeSend on iOS, macOS and Android
This is a silent change. Callbacks now get the event the native SDK built, including its system properties, such as $device_type and $session_id, registered properties and options, such as the process_person_profile: false the SDK adds while person processing is off. Their changes are final. In 5.x they saw only the properties and options you passed, so a callback that loops over or strips every property now also touches the SDK's. PostHogEvent also has uuid, distinctId and timestamp, set on every platform except web. A callback can change the distinct ID and timestamp, and the event keeps its uuid.
Person processing is decided before the callbacks run. In 5.x, a callback that removed $set prevented a person profile. Now the event is still person-processed, and so are later events. Return null to drop an event that must not create a profile. A callback that adds $set to an anonymous event must also set the process_person_profile: true option.
Nested maps are Map<String, dynamic>, and a non-empty list of maps is List<Map<String, dynamic>>. Any other list is List<dynamic>, even a List<String> you passed to capture, so cast its items instead of the list.
The PostHogEvent the callbacks return replaces the event's options. A callback that returns a new PostHogEvent, or replaces event.options, without the options it got drops them, including process_person_profile: false, which turns person processing on for that event. Copy event.options to the event you return.
Events the native SDK captures itself don't go through Dart callbacks. Use PosthogFlutterPlugin.addBeforeSend in Swift or Kotlin for those.
Lifecycle events on iOS and macOS
Application Installed, Application Updated and Application Opened no longer include version and build. Update insights, cohorts and filters that use them to $app_version and $app_build. Android still sends them.
$locale and $app_language
$locale now means the device's language, with its region, on iOS, Android, macOS, Windows and Linux: de-CH instead of de on iOS, macOS, Windows and Linux. On Android it no longer follows a per-app language or Locale.setDefault(). On older Android versions, Hebrew, Indonesian and Yiddish values also change from legacy codes, such as in-ID to id-ID. If a user picks a language for your app in iOS or macOS Settings, the system reports that language as the first preferred language, so $locale follows it.
Update cohorts, insights and actions that match $locale: a filter on $locale = de stops matching de-CH. To keep matching a language, use a regex such as ^de(-|$), because "contains" also matches en-DE. Events already stored keep the old values. Feature flags aren't affected: the SDK doesn't send $locale or $app_language with flag requests, so flags only see these if you set them as person properties yourself.
The new $app_language property is the language your app shows. PosthogObserver reports the locale exactly as your MaterialApp resolved it every time a route changes. An app that doesn't declare supportedLocales reports en-US, and one declaring Locale('en') reports en, so use a regex such as ^en(-|$) to match English. Native detection is off because Flutter apps translate in Dart. If you translate outside Localizations, don't use the observer, or want to send another value, call Posthog().setAppLanguage('de'). Web doesn't send $app_language yet, because posthog-js doesn't support it. To send it there now, call Posthog().register(r'$app_language', 'de').
Network reachability on iOS and macOS
A path that needs user action, such as a VPN waiting for a password, counts as reachable, and queues pause while cellular data is off for the app. Events captured in the first 100 ms after setup may leave out $network_wifi and $network_cellular. Events still on disk from a posthog-ios 2.x SDK are deleted instead of sent.
Personal API keys
A personal API key (phx_...) in projectToken now skips setup() with one log line, so the SDK doesn't start and the key is never sent. Use your project token (phc_...).
Capture v1 on Windows and Linux
Windows and Linux send events through posthog_flutter's own Dart client, not a native SDK. This section covers what the native guides cover for the other platforms.
Event validation
Capture v1 checks each event on its own and drops only the invalid ones, so the rest of the batch is delivered.
- The SDK drops an event with an empty event name before sending it, as in 5.x. With
debugon, it logs the drop. - PostHog drops an event whose name is over 200 UTF-8 bytes. Legacy capture had no limit on event name length.
- PostHog drops an event whose distinct ID is over 200 UTF-8 bytes. Legacy capture shortened the ID to 200 characters and kept the event. If you identify users with a long ID, such as a JWT or a long email address, shorten or hash it before you upgrade. Otherwise every event for that user is dropped.
identifywith an empty or whitespace-only distinct ID is ignored, as in 5.x.- A
NaNor infinitedoubleproperty value is removed and the event is still sent, as in 5.x. Inside a list, it's sent asnull. - A lone UTF-16 surrogate, such as a string cut in the middle of an emoji, becomes U+FFFD and the event is sent. This covers the event name, distinct ID,
$session_id,$window_id, properties and options, the IDs passed toidentify,aliasandgroup, and the person and group properties sent to/flags. The SDK keeps each ID as you passed it and repairs only what it sends. In 5.x, legacy capture rejected the whole batch for a lone surrogate anywhere in it. iOS, macOS and Android already send the event with U+FFFD in place of each lone surrogate. - The SDK doesn't report these drops to your code. With
debugon, it logs how many events PostHog dropped and why (see Logs).
Property precedence
An event's properties are the SDK's, then registered properties, then the event's own, so your value now wins for that event. In 5.x, the SDK's values won.
beforeSend runs after that merge and sees the result.
A registered or passed $session_id that is a non-empty string replaces the SDK's session ID, which can detach events from their session.
Session and window IDs
Capture v1 sends $session_id and $window_id as top-level session_id and window_id fields, not as properties.
The SDK moves them out of properties for you.
- A string value, including an empty string, is sent unchanged.
- Any other value is removed and not sent. With
debugon, the SDK logs a warning that names the key and the value's JSON type, never the value. - A
nullvalue counts as unset and is removed without a warning.
In PostHog, the events still have $session_id and $window_id.
Event options
The legacy option properties, the $-prefixed properties legacy capture reads as settings, still work.
When the event is sent, the SDK removes each one from properties and uses it for its option:
| Legacy option property | Option |
|---|---|
$process_person_profile | process_person_profile |
$cookieless_mode | cookieless_mode |
$ignore_sent_at | disable_skew_correction |
$product_tour_id | product_tour_id |
- An option you pass or set in
beforeSendwins over its legacy option property. Anulloption counts as unset, so the legacy option property fills it. - Option values are sent as given. PostHog drops an event whose option value it can't read.
- Other option keys are sent unchanged.
- There are no global options. To set one for every event, register its legacy option property, for example
Posthog().register(r'$cookieless_mode', true). - A registered legacy option property also reaches
$identify,$create_aliasand$groupidentify. If it turns person processing off, PostHog accepts those events and then drops them with an ingestion warning, and the SDK doesn't report it. - Person processing is sent as the
process_person_profile: falseoption only while it's off, instead of a$process_person_profileproperty on every event. - Person processing is decided before
beforeSendruns, as on iOS, macOS and Android. A callback that adds$setto an anonymous event must also set theprocess_person_profile: trueoption. - Events still queued on disk by 5.x are sent to capture v1 after the update, with their legacy option properties moved to options.
Review your beforeSend callbacks for the new event shape. On Windows and Linux, beforeSend sees the options the SDK adds, including process_person_profile: false. A callback that returns a new PostHogEvent or replaces event.options without that key turns person processing on for that event. A legacy option property that a callback adds doesn't replace an option that is already set. beforeSend doesn't see $identify, $create_alias, $groupidentify or $feature_flag_called.
$exception_steps is added to $exception events before beforeSend runs, so a callback can read or remove it. Steps recorded while a callback runs, and an event a callback renames to $exception, don't get them.
Retries
- The SDK retries network errors and 404, 408, 429, 500, 502, 503 and 504 responses. Other statuses drop the batch, such as a 402 when you're over your billing limit. A product quota, such as for AI events, instead drops only those events inside a 2xx. 5.x retried 408, 429, every 5xx and 3xx responses, and didn't retry 404.
- When the response asks to retry only some events, the SDK keeps those events queued and removes the rest.
- After a failed send, the wait before the next one doubles from 1 second up to 30 seconds, with up to 25% random jitter. Retries have no limit: queued events stay until they're delivered or a full queue drops the oldest. In 5.x, a failed batch was retried up to 3 times, 3 seconds apart, within the same flush.
- A
Retry-Afterheader is a floor under that wait, capped at 30 seconds. In 5.x it had no cap. - A 2xx response without PostHog's per-event results drops the batch. It isn't retried, because PostHog may have stored some of the events.
- A request times out after 10 seconds without progress, and an AI request after 60 seconds. In 5.x, a request timed out after 10 seconds in total.
Redirects
The SDK follows a 307 or 308 redirect only to the origin of host (same scheme, host and port), at most 5 times, and resends the same body and headers.
Any other redirect drops the batch.
In 5.x, no redirect was followed and the batch stayed queued.
If a proxy redirects capture requests to another host, point host at the final host.
Batches and size limits
- A request carries at most
maxBatchSizeevents and 5 MiB. The SDK sends as soon as 5 MiB of events is queued, even beforeflushAtis reached. In 5.x, batches were limited by event count only. - A larger event is sent alone.
- A
413for a batch of more than one event halvesmaxBatchSizeandflushAt, and the events are sent again in smaller batches. A413for a single event drops it. - The SDK doesn't limit the size of an analytics event, as in 5.x.
AI events
captureAievents have their own on-disk queue of up tocaptureAiMaxQueueSizeevents (100 by default) and 50 MiB, and the oldest are dropped first.maxQueueSizedoesn't apply.- An event over
captureAiMaxEventBytes(8 MiB plus 64 KiB by default) is dropped, andcaptureAireturnsnull. You can only lower the limit. - AI requests are gzipped unless you set
captureAiCompressiontoPostHogCompression.none.compressiondoesn't apply. - AI requests time out after 60 seconds without progress, or
captureAiTimeout. - AI events use
flushAtandmaxBatchSize, like other events. flush()andclose()also send AI events, and AI events left on disk are sent after the nextsetup().- PostHog drops an event sent with
captureAiwhose name doesn't start with$ai_. Withdebugon, the SDK logs a warning for it.capturenever sends events to the AI endpoint.
Logs
All of these log only with debug on, except the 404 warning.
- One line per batch reports the events PostHog dropped, with their reasons, and the repeated-uuid copies it dropped, for example
Capture /i/v1/analytics/events dropped 1 of 20 events for good (event_name_too_long: 1) and 0 repeated-uuid copies.5.x had no such line. - A failed flush logs
Error while flushing PostHog /i/v1/analytics/eventsor/i/v1/ai/eventsand the error, without the response body. In 5.x the line wasError while flushing PostHogand the error included the response body. Update any log-based alerts that match the old text. - The debug log no longer includes the properties of captured and sent events. To inspect events, use
beforeSend.
Timestamps and IDs
- An event's timestamp is taken before
beforeSendruns, and a callback can change it. - Timestamps are UTC with 3 or 6 fractional digits, as in 5.x. The SDK doesn't check for future timestamps.
- Event uuids, request IDs and anonymous distinct IDs are UUID v7. You can't set an event's uuid, so events never repeat one.
Writes to disk
An event is no longer on disk when capture, screen, captureException, captureFeatureView, captureFeatureInteraction, identify, alias, group or setPersonProperties returns, only when the returned future completes. One you didn't await can be lost if the app crashes or calls exit() right after. Await the call, or Posthog().close(), before exit(). Only close() also covers events the SDK captures itself, such as $feature_flag_called. An identify not awaited before exit() can lose its $identify event for good, because the new ID is already saved. close() can now take up to 4 seconds, and a later setup() waits for the queue's file work.
getFeatureFlagResult() follows your config
This is a silent change with no compile error. When you omit sendFeatureFlagEvent, getFeatureFlagResult() now uses PostHogConfig.sendFeatureFlagEvent instead of always capturing $feature_flag_called. This matches getFeatureFlag() and isFeatureEnabled().
It only affects apps that set config.sendFeatureFlagEvent = false. Those apps stop sending $feature_flag_called from getFeatureFlagResult(), so insights and experiments that rely on that event can receive less data. To keep the event for a call:
On web, PostHogConfig.sendFeatureFlagEvent is not applied to posthog-js, so an omitted value keeps the posthog-js default, which captures the event. Pass sendFeatureFlagEvent: false to suppress it on web.
New features
- An
optionsargument oncapture,screen,identify,setPersonProperties,alias,groupandcaptureException, andPostHogEvent.optionsinbeforeSend.process_person_profile,cookieless_mode,disable_skew_correctionandproduct_tour_idwin over their legacy option properties ($process_person_profile,$cookieless_mode,$ignore_sent_atand$product_tour_id). Other keys are sent unchanged. On web, known keys are sent as those legacy option properties and other keys are dropped. On web,process_person_profilehas no effect, and the options ofidentify,setPersonProperties,aliasandgroupare dropped. isFeatureEnabled(defaultValue:), returned when the flag has no value because flags haven't loaded, the request failed or the key doesn't exist. A flag that has a value always wins.PostHogEvent.uuid,distinctIdandtimestampinbeforeSend, set on every platform except web. A callback can change the distinct ID and timestamp.captureAifor$ai_*events. It returns the event's UUID, ornullwhen the event isn't admitted. On web it sends nothing and returnsnull.Posthog().onFeatureFlags()to get notified after every feature flags load. It returns aPostHogFeatureFlagsSubscriptionyou canunsubscribe(). You can register it beforeWidgetsFlutterBinding.ensureInitialized(), but other SDK calls, includingsetup(), are skipped with a warning until the binding is initialized.$app_languageandPosthog().setAppLanguage(). See$localeand$app_language.PosthogFlutterPlugin.addBeforeSendin Swift and Kotlin, to filter or edit events the native SDK captures itself, such as crashes, app lifecycle and$feature_flag_calledevents.PostHogConfig.captureAiCompression,captureAiMaxQueueSize,captureAiTimeoutandcaptureAiMaxEventBytesforcaptureAirequests. They don't apply on web.
Upgrade checklist
- Use the Flutter SDK 3.41 or later on every platform, and make sure Swift Package Manager is on for iOS and macOS.
- Don't upgrade yet if you embed Flutter as an iOS or macOS add-to-app module.
- Raise iOS to 15.0 and macOS to 11.0 in your Xcode projects, and in any
Podfileyou keep. - Rename
com.posthog.posthog.API_KEYtocom.posthog.posthog.PROJECT_TOKENinAndroidManifest.xmlandInfo.plist. - Use Xcode 26 or later.
- If you run your own reverse proxy or self-host, serve
/i/v1/analytics/eventsand/i/v1/ai/eventsfirst. - Shorten or hash distinct IDs over 200 UTF-8 bytes.
- Make
beforeSendcallbacks, includingPostHogLogsConfig.beforeSend, synchronous. - Review your
beforeSendcallbacks for the new event shape, which includes the SDK's properties and options on every platform except web, and for person processing decided before they run. - Replace
PostHogConfig.onFeatureFlagswithPosthog().onFeatureFlags()beforesetup(). - Update cohorts, insights and actions that match
$locale, which now includes the region. Use$app_languagefor the language your app shows. - Move anything that reads
versionorbuildon iOS and macOS lifecycle events to$app_versionand$app_build. - If you set
sendFeatureFlagEvent = false, check whether anything relies on$feature_flag_calledfromgetFeatureFlagResult(). - Add
sendFeatureFlagEvent: falsewherever you replacegetFeatureFlagPayload(). - Fix the remaining compile errors using the sections above, including any test fakes of the platform classes.





