Repository navigation
feat: add per-event capture options and captureAi - #674
turnipdabeets wants to merge 4 commits into
Conversation
Passes options and captureAi through to posthog-ios and posthog-android 4.0; Windows, Linux and web send known options as their legacy properties.
posthog-flutter Compliance ReportDate: 2026-10-27 16:09:08 UTC ✅ All Tests Passed!45/45 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 16/16 tests passed View Details
|
|
Flutter 6 should ship options and captureAi with the major, now that posthog-ios 4.0 (#938, #939) and posthog-android 4.0 (#881, #882, #884) add them. Can this land in 6.0 once those native APIs merge, rather than in a later minor? |
|
On Windows and Linux, Flutter 6 already sends events to capture v1 (#673), but captureAi does nothing there and returns null, so desktop apps silently lose AI events. Could captureAi send to /i/v1/ai/events on desktop too, through the same Dart send path #673 adds? Web would still return null until posthog-js moves to capture v1. |
|
One more question on desktop. On Windows and Linux the Dart core is the whole SDK, and _enrichProperties still writes the SDK's own keys after the caller's: context properties, $timezone, $lib, flag properties, $is_identified and the debug keys. Only $session_id and $process_person_profile honor a caller value. The Dart beforeSend also runs before that merge, so it cannot see or change those keys. posthog-ios 4.0 and posthog-android 4.0 settled on one order for client SDKs: SDK-set, then registered, then the event's own properties, then beforeSend, with no SDK-owned keys. A caller or registered value changes that event only and never the SDK's own state. The description here says desktop follows the native 4.0 behavior, so I want to check whether that was meant to include this. Is the plan to match the native order on desktop in this PR or a follow-up, or to keep the current order and call it out in the 6.0 guide? If matching, it looks like spreading the SDK-set maps before props and the caller's properties, and running beforeSend after enrichment, would do it, but you know this code better than I do. |
Add captureAiCompression, captureAiMaxQueueSize, captureAiTimeout and captureAiMaxEventBytes. Set values are sent to native setup and applied to the posthog-ios and posthog-android config; unset values are omitted so the native defaults apply. They have no effect on Windows, Linux and web. Co-Authored-By: Claude Opus 5.5 <[email protected]>
null means the event wasn't admitted: dropped by beforeSend, oversized on iOS and macOS, SDK disabled or opted out, or an unsupported platform. On Windows, Linux and web captureAi is a no-op that sends nothing. Co-Authored-By: Claude Opus 5.5 <[email protected]>
posthog-android now checks the size before queuing (f46a9711). Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Thanks @eli-r-ph!
|
|
Thanks! Stacking the desktop follow-up on #673 and #674 is fine, but we treat it as part of the 6.0 release: every platform that moves to v1 in 6.0 ships its own AI lane, so 6.0 waits for it. Web as a logged null no-op until posthog-js moves is right. Same follow-up for the desktop property order works for me. |
|
On Windows and Linux, options still go through the legacy $ property mapping from before #673, so unknown option keys are dropped with a debug log. Every v1 SDK passes options through unchanged, including keys it doesn't know. Could the desktop follow-up you mentioned (AI lane and property order) also pass the options map straight into the v1 event on desktop, so it matches iOS and Android? |
|
@eli-r-ph the desktop follow-up is up as a draft: #679. It does the three things:
The changesets are in 280ae5e. |
Stacked on #663 (posthog-android 4.0), on #649 (posthog-ios 4.0), on #637 (6.0 base).
💡 Motivation and Context
posthog-ios 4.0 and posthog-android 4.0 add per-event
optionsandcaptureAi. On iOS, macOS and Android, Flutter forwards capture over the method channel, so the capture v1 transport comes with the native bump, but the new public API needs Dart, platform-interface, channel and native handler changes. This PR adds them.Merge order: draft until the native APIs it calls are merged and released.
options) and feat: add captureAi on the capture V1 AI endpoint posthog-ios#939 (captureAi)options) and feat!: add captureAi on the capture v1 AI endpoint posthog-android#884 (captureAi)$option properties intooptions, which this PR writes on Windows and Linux. This PR doesn't touch the desktop send path (posthog_core_stateless.dart)Public API (new, additive)
options: Map<String, Object?>?oncapture,screen,identify,setPersonProperties,alias,groupandcaptureException(same methods as the native 4.0 APIs), and on the platform interfacePostHogEvent.options, which DartbeforeSendcallbacks can read and changeFuture<String?> captureAi({eventName, properties, userProperties, userPropertiesSetOnce, options}), which returns the event's UUID, ornullwhen the event isn't admitted: dropped bybeforeSend, oversized, SDK disabled or opted out, or an unsupported platform. It goes through the DartbeforeSendcallbacks and the$screen_namestamping likecapturePostHogConfig.captureAiCompression,captureAiMaxQueueSize,captureAiTimeout(aDuration, sent as whole seconds) andcaptureAiMaxEventBytes, all nullable. Set values are sent to native setup and applied to the nativecaptureAi*config fields; unset values are omitted so the native defaults apply. They have no effect on Windows, Linux and webprocess_person_profile,cookieless_mode,disable_skew_correction,product_tour_id. A null value means unset: null entries are stripped before the channelPer platform
optionscaptureAioptions:on each native method.captureExceptionsendscapture("$exception", ..., options:)because the plugin already builds the exception in Dart. Unknown keys pass throughPostHogSDK.shared.captureAi(...); the UUID string comes back over the channeloptionsargument;captureExceptionas abovePostHog.captureAi(...);UUID.toString()comes back$process_person_profile,$cookieless_mode,$ignore_sent_at,$product_tour_idon the event, winning over a legacy property already there. Unknown keys are dropped with a debug lognull. ThecaptureAi*config settings are ignoredoptions, unknown keys included; an option wins over its legacy$property/i/v1/ai/eventsfrom its own on-disk queue throughbeforeSend, returning the UUID ornull; thecaptureAi*config settings applycapture,screenandcaptureException. posthog-jsCaptureOptionshas none of these keys, and posthog-js sets$process_person_profilelast, soprocess_person_profilehas no effect on web.identify,setPersonProperties,aliasandgroupoptions are dropped with a debug log, because posthog-js sends those events without event propertiesnull. ThecaptureAi*config settings are ignoredbeforeSend: on every platform the Dart callbacks run before the event leaves Dart, so options are visible and editable there forcapture,captureAi,screenandcaptureException, including after a callback renames a screen or exception event. As before,identify,setPersonProperties,aliasandgroupevents are built natively (or inside the desktop client) and don't go through the Dart callbacks.Desktop person processing (behavior change): the desktop client wrote
$process_person_profileafter the caller's properties, so aprocess_person_profileoption (or the legacy property) was always overwritten. A boolean$process_person_profilepassed for an event now wins for that event only and doesn't persist person processing. A$setsent withprocess_person_profile: falseno longer marks or matches the person-properties dedup hash, like the native 4.0 SDKs.identify,setPersonProperties,aliasandgroupon the desktop client take an extrapropertiesmap for this. This is inposthog_core.dart, which the desktop v1 PR may also touch.Spec divergences
capture/spec.mddescribes the legacy options bag and says the SDK sets$process_person_profileafter the caller's properties ("Caller can't override person-processing state"). This PR follows the native 4.0 behavior instead (caller option > SDK default) on every platform except web, where posthog-js still overrides it. Options onscreen,identify,setPersonProperties,alias,groupandcaptureExceptionfollow posthog-ios#938 and posthog-android#882; nothing in sdk-specs defines them.capture-aispec applies to server SDKs. Like the native SDKs,captureAitakes nouuid(Flutter'scapturedoesn't either) but returns the UUID. Windows, Linux and web don't send AI events at all instead of routing them throughcapture.💚 How did you test it?
flutter analyze: no issues.dart format --set-exit-if-changed .: clean.make checkFormatKotlinandmake checkFormatSwift: clean.scripts/check-api-dart.sh: snapshot updated and up to dateflutter testinposthog_flutter: 1305 passed, 1 skipped. New tests:captureAi*config settings sent to native setup when set, omitted when unset, and a sub-second timeout sent as 1 secondcaptureAi, null stripping, nooptionskey when empty,beforeSendreading and changing options, a renamed screen keeping its options, and thecaptureAiUUID/nullreturnsPosthogforwarding options and thecaptureAiresult to the platformbeforeSendedits, options onidentify/setPersonProperties/group/alias, the per-eventprocess_person_profilenot persisting, the personless$setdedup, andcaptureAisending nothing. Reverting theposthog_core.dartprecedence change fails 2 of themflutter test --platform chrome, the CONTRIBUTING list): the new web tests pass (legacy mapping forcapture/screen/captureException,captureAino-op). 3 app-version tests inposthog_flutter_web_handler_test.dartfail locally on Flutter 3.44.1 with and without this changecapture_v0andencoding_gziponly. Thecapture_ai_v1cases are deferred until desktop gets an AI lane (after feat!: send Windows and Linux events through capture v1 #673). The adapter runs on Linux underflutter test, with its own Dart platform implementation and no native SDK, socaptureAihas nothing to send there./gradlew :posthog_flutter:testDebugUnitTestagainst posthog-androidfeat/v4-capture-ai(b59865a), published to a scratch Maven repo as 4.0.0: 61 passed (rerun againstfeat/v4-capture-ai4ffa8191 for the config settings), including the new RobolectricEventOptionsChannelTest(options reachPostHogEvent.optionsfor all 7 methods through the real SDK, andcaptureAireturns the UUID). Dropping thealiasforwarding fails it.PosthogFlutterPluginTestchecks that the fourcaptureAi*settings reach the native config, and that the native defaults stay when they're unset. The composite build in CONTRIBUTING didn't work with this native branch (its buildSrc readsgradle.propertiesfrom the working directory, then a Kotlin lock mismatch)flutter build ios --simulator --no-codesign --debugandflutter build macos --debugsucceed against posthog-iosfeat/v4-capture-ai(a58da3c) through a local, uncommittedPackage.swiftpath.flutter build macos --debugreran against 5a584cb80 with thecaptureAi*config handling. The plugin has no Swift unit tests, so the Swift handlers are compile-checked only📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file (.changeset/v6-capture-options-ai.md,minor; the stack releases as 6.0.0)🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Built with Claude Code from the posthog-ios #938/#939 and posthog-android #882/#884 APIs, sdk-specs
captureandcapture-ai, and posthog-jsCaptureOptions. Decisions:optionsis a plain map like the native APIs, not a typed class$properties so the options work on/batchnow and are hoisted tooptionsby the desktop v1 PR later.captureAiis a logged no-op there; desktop AI transport is a follow-up on top of desktop v1capture()as before, now with the optionsNot done: docs on posthog.com for
optionsandcaptureAiin Flutter.🤖 Generated with Claude Code