Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contents/docs/feature-flags/cutting-costs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ final config = PostHogConfig('<ph_project_token>');

if (isTestEnv) {
config.preloadFeatureFlags = false;
config.sendFeatureFlagEvents = false;
config.sendFeatureFlagEvent = false;
config.flushAt = 100;
config.flushInterval = const Duration(seconds: 120);
}
Expand Down
6 changes: 3 additions & 3 deletions contents/docs/integrate/_snippets/install-flutter-manual.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,17 @@ Add your PostHog configuration to the `Info.plist` file located in the `ios/Runn
</plist>
```

You'll need to set the minimum platform version to iOS 13.0.
You'll need to set the minimum platform version to iOS 15.0. Building for iOS requires Xcode 26 or later.

For CocoaPods projects, set it in your `Podfile`:

```yaml file=ios/Podfile
platform :ios, '13.0'
platform :ios, '15.0'

# rest of your config
```

For Swift Package Manager projects without a `Podfile`, set the **Minimum Deployments** version to iOS 13.0 for the `Runner` target in Xcode (**Runner > General > Minimum Deployments**).
For Swift Package Manager projects without a `Podfile`, set the **Minimum Deployments** version to iOS 15.0 for the `Runner` target in Xcode (**Runner > General > Minimum Deployments**).

#### Dart setup

Expand Down
6 changes: 3 additions & 3 deletions contents/docs/integrate/_snippets/install-flutter.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,16 @@ Add your PostHog configuration to the `Info.plist` file located in the `ios/Runn
</plist>
```

In both cases, you'll need to set the minimum platform version to iOS 13.0.
In both cases, you'll need to set the minimum platform version to iOS 15.0. Building for iOS requires Xcode 26 or later.

For CocoaPods projects, set it in your `Podfile`:

```yaml file=ios/Podfile
platform :ios, '13.0'
platform :ios, '15.0'
# rest of your config
```

For Swift Package Manager projects without a `Podfile`, set the **Minimum Deployments** version to iOS 13.0 for the `Runner` target in Xcode (**Runner > General > Minimum Deployments**). After you change **Minimum Deployments**, regenerate the iOS project's configuration files:
For Swift Package Manager projects without a `Podfile`, set the **Minimum Deployments** version to iOS 15.0 for the `Runner` target in Xcode (**Runner > General > Minimum Deployments**). After you change **Minimum Deployments**, regenerate the iOS project's configuration files:

```bash
flutter build ios --config-only
Expand Down
219 changes: 214 additions & 5 deletions contents/docs/libraries/flutter/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -282,19 +282,21 @@ The PostHog Flutter SDK will continue to capture events when the device is offli

## Opt out of data capture

You can disable data collection for a user at any time using the `disable()` method:
You can disable data collection for a user at any time using the `optOut()` method:

```dart
await Posthog().disable();
await Posthog().optOut();
```

This prevents any future events from being sent. It doesn't remove events already captured for the user.
To opt the user back in:

```dart
await Posthog().enable();
await Posthog().optIn();
```

`optOut()` and `optIn()` require version 5.51.0 or later. Earlier versions use `disable()` and `enable()`, which version 6 removes.

To check if a user is opted out:

```dart
Expand Down Expand Up @@ -367,15 +369,36 @@ config.beforeSend = [
The `beforeSend` callbacks only apply to events captured via Dart APIs:

- `Posthog().capture()` - custom events
- `Posthog().captureAi()` - AI events
- `Posthog().screen()` - screen events (event name is `$screen`)
- `Posthog().captureException()` - exception events (event name is `$exception`)

They do **not** intercept native-initiated events such as:
`identify`, `alias`, `group` and `setPersonProperties` don't go through them, and neither do events the native SDK captures itself, such as:

- Session replay events (`$snapshot`)
- Application lifecycle events (`Application Opened`, etc.)
- Crash events
- `$feature_flag_called` events

Since version 6, the callbacks get the final event on every platform, with the system properties (like `$device_type` and `$session_id`) and registered super properties the SDK adds, and their changes are final. On iOS, macOS and Android, `PostHogEvent` also has the event's `uuid`, `distinctId` and `timestamp`.

### Filtering native events

On iOS, macOS and Android, use `PosthogFlutterPlugin.addBeforeSend` to change or drop the events the native SDK captures itself. Native hooks run on every event, in the order you add them, after the Dart callbacks. Return the event to send it, or `nil`/`null` to drop it. Add them before the SDK is set up.

```swift
// AppDelegate.swift, before GeneratedPluginRegistrant.register(with: self)
PosthogFlutterPlugin.addBeforeSend { event in
event.event == "Application Backgrounded" ? nil : event
}
```

Additionally, only user-provided properties are available in the callback. System properties (like `$device_type`, `$session_id`) are added by the native SDK at a later stage.
```kotlin
// MainApplication.onCreate
PosthogFlutterPlugin.addBeforeSend { event ->
if (event.event == "Application Backgrounded") null else event
}
```

## Debug mode

Expand All @@ -398,3 +421,189 @@ You can also enable debug by calling the `Posthog().debug()` method in your code
await Posthog().debug(true);
await Posthog().debug(false);
```

## Upgrading from version 5 to version 6

Version 6 moves iOS and macOS to `posthog-ios` 4.0 and Android to `posthog-android` 4.0, sends events to PostHog's capture v1 API on every platform except web, raises the minimum iOS and macOS versions, and removes the APIs that version 5 deprecated.

If you send events through a reverse proxy or to a self-hosted PostHog instance, read [Events go to capture v1](#events-go-to-capture-v1) before you upgrade.

Before you upgrade, update to the latest version 5 release and fix its deprecation warnings. The replacements below work in both versions, with one exception: the parameter of `getFeatureFlagResult()` that controls the `$feature_flag_called` event is named `sendEvent` in version 5 and `sendFeatureFlagEvent` in version 6. Version 6 also removes a few members that version 5 never marked as deprecated: the `sendEvent` parameter itself, the `toMap()` and `fromMap()` helpers, and some members of the platform interface classes.

### Platform requirements

| Platform | Version 5 | Version 6 |
| --- | --- | --- |
| iOS | 13.0 | 15.0 |
| macOS | 10.15 | 11.0 |
| Xcode | – | 26 or later |
| Android | API 23 | API 23 (unchanged) |

For CocoaPods projects, raise the platform version in your `Podfile`:

```ruby file=ios/Podfile
platform :ios, '15.0'
```

If your app also targets macOS, do the same in `macos/Podfile`:

```ruby file=macos/Podfile
platform :osx, '11.0'
```

For Swift Package Manager projects without a `Podfile`, set **Minimum Deployments** for the `Runner` target in Xcode (**Runner > General > Minimum Deployments**), then regenerate the project's configuration files:

```bash
flutter build ios --config-only
# If your app also targets macOS
flutter build macos --config-only
```

### Removed APIs

| Removed in version 6 | Use instead |
| --- | --- |
| `PostHogConfig.apiKey` | `PostHogConfig.projectToken` |
| `com.posthog.posthog.API_KEY` in `AndroidManifest.xml` or `Info.plist` | `com.posthog.posthog.PROJECT_TOKEN` |
| `Posthog().disable()` and `Posthog().enable()` | `Posthog().optOut()` and `Posthog().optIn()` |
| `Posthog().getFeatureFlagPayload(key)` | `Posthog().getFeatureFlagResult(key, sendFeatureFlagEvent: false)`, then read `payload` (`sendEvent: false` in version 5) |
| The `sendEvent` parameter of `getFeatureFlagResult()` | `sendFeatureFlagEvent` |
| `PostHogConfig.sendFeatureFlagEvents` | `PostHogConfig.sendFeatureFlagEvent` |
| `PostHogSessionReplayConfig.debouncerDelay` | `PostHogSessionReplayConfig.throttleDelay` |
| `PostHogDataMode.cellular` | `PostHogDataMode.any`, which `cellular` always behaved like |
| `toMap()` on the config classes, and `PostHogFeatureFlagResult.fromMap()` | None. They serialize the SDK's internal format and aren't meant for app code |

For example, to read a flag payload. Pass `false` to keep the old behavior: `getFeatureFlagResult()` captures a `$feature_flag_called` event by default, and `getFeatureFlagPayload()` didn't.

```dart
// Version 5
final payload = await Posthog().getFeatureFlagPayload('my-flag');

// Version 6
final result = await Posthog().getFeatureFlagResult(
'my-flag',
sendFeatureFlagEvent: false,
);
final payload = result?.payload;
```

If you implement or mock `PosthogFlutterPlatformInterface`, `PosthogFlutterIO` or `PosthogFlutterWeb` directly, for example in tests:

- Rename `enable()` and `disable()` to `optIn()` and `optOut()`.
- Remove `getFeatureFlagPayload()`.
- Rename the `sendEvent` parameter of `getFeatureFlagResult()` to `sendFeatureFlagEvent`.

Calls to `Posthog()` aren't affected by these.

### Events go to capture v1

On iOS, macOS, Android, Windows and Linux, version 6 sends events to capture v1 instead of `/batch/`. iOS and macOS do this through `posthog-ios` 4.0, Android through `posthog-android` 4.0, and Windows and Linux in the SDK's Dart client.
Comment thread
turnipdabeets marked this conversation as resolved.

Web is unchanged. Flutter web runs `posthog-js`, which keeps sending events to the endpoints it uses today.

| Data | Version 5 | Version 6 on iOS, macOS, Android, Windows and Linux |
| --- | --- | --- |
| Events | `/batch/` | `/i/v1/analytics/events` |
| AI events (`captureAi`) | – | `/i/v1/ai/events` |

Session replay, logs and feature flags keep their endpoints.

PostHog Cloud serves both capture v1 endpoints. If you self-host PostHog, check that your instance serves `/i/v1/analytics/events` and `/i/v1/ai/events` before you upgrade.

If you send events through your own reverse proxy, update it before you upgrade:

- Forward `/i/v1/analytics/events` and `/i/v1/ai/events` to PostHog.
- Pass `Authorization` (the project token, sent as `Bearer <token>`), every `PostHog-*` header, `Content-Type`, `Content-Encoding` and `User-Agent` through unchanged.
- Accept request bodies up to 20 MB. AI requests are often larger than 1 MB, the default limit in NGINX.
- If your app also runs on web, keep the routes the proxy already forwards for `posthog-js`. One proxy that serves every platform needs both sets of routes.

PostHog's [managed reverse proxy](/docs/advanced/proxy) already does this.

The SDK doesn't fall back to `/batch/`. A 404 is retried, so events stay queued until the path is served.

Other changes that come with capture v1, on every platform except web:

- **Redirects:** the SDK only follows a 307 or 308 redirect to the same origin as `host`. Any other redirect drops the batch. If your proxy redirects capture requests to another host, point `host` at the final host.
- **Event validation:** PostHog checks each event on its own and drops only the invalid ones, so the rest of the batch is delivered. It drops an event with an empty name, an empty distinct ID, or a name or distinct ID over 200 UTF-8 bytes. Version 5 shortened a long distinct ID and kept the event. If you identify users with a long ID, such as a JWT, shorten or hash it before you upgrade.
- **`$lib` and `$lib_version`:** PostHog sets them from the SDK's `PostHog-Sdk-Info` request header. A value you set in properties or in `beforeSend` doesn't reach PostHog.
- **Personal API keys:** a personal API key (`phx_...`) in `projectToken` disables the SDK at setup. Use your project token (`phc_...`).

### Property precedence

On iOS, macOS, Android, Windows and Linux, an event's properties are merged in this order, and later values win:

1. properties the SDK sets itself, such as device and app context, `$is_identified` and `$session_id`
2. registered super properties (`register`)
3. the properties you pass to `capture` or another event-sending method

In version 5, the SDK's own values won over the properties you registered or passed. In version 6, your value wins for that event. A registered `$session_id` now overrides the SDK's session ID, which can detach events from their session replay.
Comment thread
turnipdabeets marked this conversation as resolved.

`beforeSend` runs after the merge, so it sees and can change every property and option the SDK adds. In version 5, on iOS, macOS and Android, it ran before the native SDK added its properties and saw only the properties you passed.

### Event options

Capture v1 reads processing settings from an `options` map sent next to the event's properties. `capture`, `screen`, `identify`, `setPersonProperties`, `alias`, `group` and `captureException` take a new `options` argument, and `beforeSend` can read and change `PostHogEvent.options`.
Comment thread
turnipdabeets marked this conversation as resolved.

```dart
// Version 5
await Posthog().capture(
eventName: 'signed_up',
properties: {r'$cookieless_mode': true},
);

// Version 6
await Posthog().capture(
eventName: 'signed_up',
options: {'cookieless_mode': true},
);
```

| Legacy property | Option |
| --- | --- |
| `$process_person_profile` | `process_person_profile` |
| `$cookieless_mode` | `cookieless_mode` |
| `$ignore_sent_at` | `disable_skew_correction` |
| `$product_tour_id` | `product_tour_id` |

- The legacy properties still work. An option you pass, or set in `beforeSend`, wins over its legacy property. A `null` option counts as unset.
- On iOS, macOS, Android, Windows and Linux, other option keys are sent unchanged.
- On web, the known options are sent as their legacy properties and other keys are dropped. `posthog-js` sets `$process_person_profile` itself, so `process_person_profile` has no effect on web, and the options of `identify`, `setPersonProperties`, `alias` and `group` are dropped.

### AI events

Version 6 adds `captureAi`, which sends AI events such as `$ai_generation` to PostHog's AI endpoint instead of the events endpoint. It takes the same arguments as `capture` and goes through the same `beforeSend` callbacks.

```dart
await Posthog().captureAi(
eventName: r'$ai_generation',
properties: {
r'$ai_trace_id': traceId,
r'$ai_model': 'gpt-4o',
},
);
```

- `captureAi` returns the event's UUID, or `null` when the event isn't queued: `beforeSend` dropped it, it's over `captureAiMaxEventBytes`, or the SDK is disabled or opted out.
- PostHog drops an event sent with `captureAi` whose name doesn't start with `$ai_`. `capture` never sends events to the AI endpoint, even ones named `$ai_*`.
- AI events are queued separately from other events. `PostHogConfig.captureAiCompression`, `captureAiMaxQueueSize`, `captureAiTimeout` and `captureAiMaxEventBytes` configure that queue and its requests.
- The SDK sends AI events unchanged. It doesn't redact or truncate prompts, outputs or media, so use `beforeSend` to remove anything you don't want to send.
- On web, `captureAi` is a no-op: it sends nothing and returns `null`.

### Retries

On iOS, macOS, Android, Windows and Linux:

- The SDK retries network errors and 404, 408, 429, 500, 502, 503 and 504 responses. Other error statuses drop the events.
- When PostHog asks to retry only some events of a batch, the SDK keeps those events queued and removes the rest.
- A 413 splits the batch into smaller requests.
- After a failed send, the wait before the next one doubles from 1 second up to 30 seconds, with up to 25% random jitter.
- A `Retry-After` header pauses sending for at most 30 seconds.
- Retries have no limit. Queued events stay until they're delivered or a full queue evicts the oldest.

### Behavior changes on iOS and macOS

These changes come from `posthog-ios` 4.0 and need no code changes, but they can affect your data:

- **Malformed feature flag payloads:** a payload that is malformed JSON, empty, or only whitespace is now `null` instead of the raw string. PostHog rejects malformed payloads when you save a flag, so this rarely comes up.
- **Bootstrapped payloads:** string values in `PostHogBootstrapConfig.featureFlagPayloads`, such as `"123"` or `"true"`, now stay strings instead of being decoded to numbers or booleans. This matches Android.
- **Lifecycle events:** `Application Installed`, `Application Updated` and `Application Opened` no longer include the legacy `version` and `build` properties. Update any insights, cohorts or filters that use them to `$app_version` and `$app_build`. Android still sends `version` and `build` for now.
4 changes: 2 additions & 2 deletions contents/docs/surveys/installation/flutter.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import { FlutterSurveysInstallationWrapper } from "./_snippets/surveys-installat
## Limitations

- On Android, requires API >= 26.
- On iOS, minimum deployment target is [iOS13](/docs/libraries/ios).
- On iOS, minimum deployment target is [iOS 15](/docs/libraries/ios).
- On Web, surveys are powered by the [JavaScript Web SDK](/docs/surveys/installation/web).
- Requires PostHog Flutter SDK version >= 5.8.0 (5.14.0+ recommended for latest features)

## Troubleshooting

- Update your SDK and iOS Pods.
- Make sure to set the minimum platform version to iOS 13.0 in your `Podfile`, or, for Swift Package Manager projects, in the `Runner` target's **Minimum Deployments** in Xcode.
- Make sure to set the minimum platform version to iOS 15.0 in your `Podfile`, or, for Swift Package Manager projects, in the `Runner` target's **Minimum Deployments** in Xcode.
- Make sure you have disabled automatic SDK initialization.
- Run a clean build if you experience issues.
- If surveys are not being displayed when triggered, make sure you have installed the [PosthogObserver](/docs/surveys/installation/flutter#step-two-install-posthogobserver).
Expand Down
14 changes: 7 additions & 7 deletions contents/tutorials/flutter-ab-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ To start, install [PostHog’s Flutter SDK](/docs/libraries/flutter) by adding `
dependencies:
flutter:
sdk: flutter
posthog_flutter: ^4.0.1
posthog_flutter: ^6.0.0

# rest of your code
```
Expand All @@ -129,21 +129,21 @@ For Android, add your PostHog configuration to your `AndroidManifest.xml` file l
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="your.package.name">
<application>
<!-- ... other configuration ... -->
<meta-data android:name="com.posthog.posthog.API_KEY" android:value="<ph_project_token>" />
<meta-data android:name="com.posthog.posthog.PROJECT_TOKEN" android:value="<ph_project_token>" />
<meta-data android:name="com.posthog.posthog.POSTHOG_HOST" android:value="<ph_client_api_host>" /> <!-- usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com' -->
<meta-data android:name="com.posthog.posthog.TRACK_APPLICATION_LIFECYCLE_EVENTS" android:value="true" />
<meta-data android:name="com.posthog.posthog.DEBUG" android:value="true" />
</application>
</manifest>
```

You'll also need to update the minimum Android SDK version to `21` in `android/app/build.gradle`:
You'll also need to update the minimum Android SDK version to `23` in `android/app/build.gradle`:

```gradle_kotlin file=android/app/build.gradle
// rest of your config

defaultConfig {
minSdkVersion 21
minSdkVersion 23
// rest of your config
}

Expand All @@ -160,7 +160,7 @@ For iOS, you'll need to have [Cocoapods](https://guides.cocoapods.org/using/gett
<plist version="1.0">
<dict>
<!-- rest of your configuration -->
<key>com.posthog.posthog.API_KEY</key>
<key>com.posthog.posthog.PROJECT_TOKEN</key>
<string><ph_project_token></string>
<key>com.posthog.posthog.POSTHOG_HOST</key>
<string><ph_client_api_host></string> <!-- https://us.i.posthog.com or https://eu.i.posthog.com -->
Expand All @@ -172,10 +172,10 @@ For iOS, you'll need to have [Cocoapods](https://guides.cocoapods.org/using/gett
</plist>
```

Then you need to set the minimum platform version to iOS 13.0 in your Podfile:
Then you need to set the minimum platform version to iOS 15.0 in your Podfile:

```yaml file=ios/Podfile
platform :ios, '13.0'
platform :ios, '15.0'

# rest of your config
```
Expand Down
Loading
Loading