Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
96bafce
chore!: remove deprecated public APIs for v4
turnipdabeets Sep 30, 2026
ad6294a
docs: add Objective-C migration steps to the deprecated-API removal c…
turnipdabeets Oct 5, 2026
fd61c08
chore!: raise minimum iOS deployment target to 15 (#876)
turnipdabeets Oct 5, 2026
98c8850
chore!: hide wrapper-only APIs behind SPI for v4 (#874)
turnipdabeets Oct 5, 2026
6e5f0c7
chore!: remove PostHogDataMode.cellular for v4 (#890)
turnipdabeets Oct 5, 2026
248b360
chore!: require Xcode 26 (swift-tools-version 6.2) (#898)
turnipdabeets Oct 5, 2026
c7a4181
chore!: raise minimum macOS to 11, tvOS to 15 and watchOS to 10 (#906)
turnipdabeets Oct 6, 2026
bb7db84
chore!: rename console log types to PostHogConsoleLog* for v4 (#912)
turnipdabeets Oct 6, 2026
44bbc3a
chore!: remove the v2 legacy queue migration for v4 (#875)
turnipdabeets Oct 6, 2026
f01aafb
chore: update text and names left stale by the 4.0 changes (#910)
turnipdabeets Oct 6, 2026
fe934c5
chore!: drop legacy version and build from lifecycle events (#905)
turnipdabeets Oct 7, 2026
078395c
chore!: pass the reload result to reloadFeatureFlags' callback (#913)
turnipdabeets Oct 7, 2026
34b9735
chore!: return nil for malformed feature flag payloads (#903)
turnipdabeets Oct 7, 2026
f348446
chore!: replace vendored Reachability with NWPathMonitor (#909)
turnipdabeets Oct 7, 2026
7bb79ca
feat: add SessionReplay and CrashReporting package traits (#916)
turnipdabeets Oct 9, 2026
97a0017
chore!: remove wireframe session replay capture (#907)
turnipdabeets Oct 9, 2026
95624b0
chore!: remove experimental screenshotModeBackgroundCapture (#937)
turnipdabeets Oct 10, 2026
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
5 changes: 5 additions & 0 deletions .changeset/ignored-exception-types-watchos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 An AI agent wrote this on behalf of @ioannisj.

[nit] This is the same fix 3.92.0 already shipped (ea22826) under a different file name, so it'd survive the merge and show up again in the 4.0.0 changelog. Drop it when merging main?

'posthog-ios': patch
---

Fix `errorTrackingConfig.ignoredExceptionTypes` not applying to manually captured exceptions on watchOS and visionOS.
5 changes: 5 additions & 0 deletions .changeset/spm-package-traits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ios': minor
---

Add `SessionReplay` and `CrashReporting` Swift package traits (on by default) so SwiftPM apps can leave out session replay with the vendored libwebp, and the vendored PLCrashReporter, with `traits: []`.
5 changes: 5 additions & 0 deletions .changeset/spm-surveys-trait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ios': minor
---

Add a `Surveys` Swift package trait (on by default) so SwiftPM apps that don't use surveys can leave out the survey engine and UI.
23 changes: 23 additions & 0 deletions .changeset/v4-internalize-wrapper-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'posthog-ios': major
---

**Breaking:** hide SDK internals that were public only for PostHog's own wrapper SDKs.

Moved behind `@_spi(PostHogInternal)`. These are for PostHog's wrapper SDKs and carry no stability guarantees:

- `postHogSdkName` and `postHogVersion`
- `PostHogSessionManager`: use `PostHogSDK.getSessionId()`, `startSession()` and `endSession()`.
- `PostHogStorageManager` and `PostHogConfig.storageManager`: use the `PostHogSDK` identity methods such as `identify`, `getDistinctId()` and `reset()`.
- `PostHogConfig.snapshotEndpoint`
- `dateToMillis(_:)`: use `Int64(date.timeIntervalSince1970 * 1000)`.
- `imageToBase64(_:_:)`: no public replacement. It returned a WebP `data:` URI, or JPEG when WebP encoding failed.

No longer public:

- `toISO8601String(_:)`, `toISO8601Date(_:)`, `sanitizeDictionary(_:)`, `deleteSafely(_:)` and `postHogiOSSdkName`
- `Gzip`, `GzipError` and `CompressionLevel`
- `ReachabilityError`
- `UIColor.init(hex:)` and `UIColor.hexDescription(_:)`

`PostHogSessionManager.shared`, `PostHogSessionManager.setSessionId(_:)` and `PostHogConfig.snapshotEndpoint` are no longer exposed to Objective-C.
5 changes: 5 additions & 0 deletions .changeset/v4-malformed-payload-nil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ios': major
---

**Breaking:** return `nil` instead of the raw string for a malformed, empty or whitespace-only feature flag payload from `getFeatureFlagResult(_:)` and `getAllFeatureFlags()`. If you read a malformed payload as a `String`, fix the payload JSON in PostHog. String payloads passed to `PostHogBootstrapConfig.featureFlagPayloads` are no longer JSON-decoded a second time, so a bootstrapped `"123"` stays a `String`.
5 changes: 5 additions & 0 deletions .changeset/v4-min-ios-15.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ios': major
---

**Breaking:** raise the minimum iOS deployment target from 13.0 to 15.0. Mac Catalyst follows iOS.
5 changes: 5 additions & 0 deletions .changeset/v4-nwpathmonitor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ios': major
---

Network reachability now uses `NWPathMonitor` instead of the deprecated `SCNetworkReachability` API. `dataMode = .wifi` still counts any connection that isn't cellular as Wi-Fi, including wired. Some edge cases change. A path that needs a connection now counts as reachable, even when it needs user action (e.g. a VPN waiting for a password). With cellular data turned off for the app, queues now pause instead of sending requests that fail. If no path has arrived in the first 100 ms after setup, `.any` sends and `.wifi` waits for the first path, and events captured in that window leave out `$network_wifi` and `$network_cellular`. The unavailable `ReachabilityChangedNotification` global is removed.
5 changes: 5 additions & 0 deletions .changeset/v4-raise-apple-platform-minimums.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ios': major
---

**Breaking:** raise the minimum deployment targets to macOS 11.0 (from 10.15), tvOS 15.0 (from 13.0) and watchOS 10.0 (from 6.0).
7 changes: 7 additions & 0 deletions .changeset/v4-reload-flags-result.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'posthog-ios': major
---

**Breaking:** `reloadFeatureFlags(_:)` now passes a `PostHogFeatureFlagsLoaded` to its callback, so you can tell whether the reload failed.

`errorsLoading` is `true` when the request failed or the SDK isn't set up, and `flags` and `variants` then hold the last known values. Update closures from `{ ... }` to `{ result in ... }` (or `{ _ in ... }`). In Objective-C, the block now takes a `PostHogFeatureFlagsLoaded *` argument.
5 changes: 5 additions & 0 deletions .changeset/v4-remove-datamode-cellular.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ios': major
---

**Breaking:** remove `PostHogDataMode.cellular`. It always behaved the same as `.any`, so use `.any` instead (`PostHogDataModeAny` in Objective-C). Flushing behavior is unchanged. `.wifi` and `.any` keep their raw values 0 and 2; `PostHogDataMode(rawValue: 1)` now returns `nil`.
19 changes: 19 additions & 0 deletions .changeset/v4-remove-deprecated-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'posthog-ios': major
---

**Breaking:** remove deprecated APIs.

- `PostHogConfig.apiKey`, `PostHogConfig(apiKey:)` and `PostHogConfig(apiKey:host:)`: use `projectToken`, `PostHogConfig(projectToken:)` and `PostHogConfig(projectToken:host:)`.
- `PostHogConfig.propertiesSanitizer` and the `PostHogPropertiesSanitizer` protocol: use `PostHogConfig.setBeforeSend(_:)`.
- `PostHogConfig.evaluationEnvironments`: use `evaluationContexts`.
- `PostHogConfig.remoteConfig`: delete the assignment. Remote config is always loaded.
- `PostHogSDK.getFeatureFlagPayload(_:)`: use `getFeatureFlagResult(_:)?.payload`. Pass `sendFeatureFlagEvent: false` to keep the old behavior of not capturing `$feature_flag_called`.
- `PostHogSessionReplayConfig.debouncerDelay`: use `throttleDelay`.
- `PostHogSessionReplayConfig.maskPhotoLibraryImages`: delete the assignment. It had no effect.

In Objective-C:

- `[[PostHogConfig alloc] apiKey:]` and `apiKey:host:`: use `projectToken:` and `projectToken:host:`.
- `[sdk getFeatureFlagPayload:key]`: use `[sdk getFeatureFlagResultWithKey:key sendFeatureFlagEvent:NO].payload`.
- `config.propertiesSanitizer`: use `[config setBeforeSend:@[...]]` with `BoxedBeforeSendBlock`s.
9 changes: 9 additions & 0 deletions .changeset/v4-remove-legacy-queue-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'posthog-ios': major
---

**Breaking:** remove the migration of the event queue written by SDK 2.x (`posthog.queue.plist`).

Events still queued on disk by a 2.x SDK are deleted instead of sent when the app updates straight from 2.x to 4.0. Distinct ID, anonymous ID, groups and cached feature flags are still carried over. Events created by a 3.x SDK are not affected.

Queued events are no longer read in the 2.x format: a top-level `$set` and `message_id` are ignored. This includes 2.x events that 3.x imported and had not sent yet when the app updated to 4.0.
5 changes: 5 additions & 0 deletions .changeset/v4-remove-legacy-version-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ios': major
---

**Breaking:** remove `version` and `build` from `Application Installed`, `Application Updated` and `Application Opened` events. Use `$app_version` and `$app_build` in insights and filters instead.
12 changes: 12 additions & 0 deletions .changeset/v4-remove-replay-wireframe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'posthog-ios': major
---

**Breaking:** remove wireframe session replay capture. Session replay now always records masked screenshots.

- Apps that didn't set `screenshotMode` now upload screenshots instead of wireframes, which uses more bandwidth and CPU per captured frame.
- Masking covers text, inputs, images and web views. Custom-drawn content (custom `draw(_:)` views, maps, video, charts) is now visible in recordings: wrap anything sensitive in `postHogMask()` or tag it `ph-no-capture`.
- Apps whose window root is a SwiftUI view, which previously recorded nothing, now record sessions.
- `PostHogSessionReplayConfig.screenshotMode` is deprecated and has no effect: delete the assignment. Setting it to `false` no longer prevents screenshots; use the masking options instead.
- The replay screen name now names the visible screen (SwiftUI screens report their view name), and is also sent when `screenshotMode` was enabled. It comes from the view controller's type name only: a plain `UIViewController`'s `title` is no longer sent, since it can contain user data.
- Secure-entry text fields now stay masked inside a `postHogNoMask()` or `ph-no-mask` subtree.
5 changes: 5 additions & 0 deletions .changeset/v4-remove-screenshot-background-capture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ios': major
---

**Breaking:** remove the experimental `PostHogSessionReplayConfig.screenshotModeBackgroundCapture` option. Delete the assignment. Screenshots are now always captured on the main thread, where rendering is fast enough that the off-main workaround is no longer needed.
5 changes: 5 additions & 0 deletions .changeset/v4-rename-console-log-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ios': major
---

**Breaking:** rename `PostHogLogLevel` to `PostHogConsoleLogLevel` and `PostHogLogEntry` to `PostHogConsoleLogEntry`, so the session replay console log types aren't confused with the Logs types `PostHogLogSeverity` and `PostHogLogRecord`. Update custom `logSanitizer` and `minLogLevel` code to the new names (`PostHogConsoleLogLevelInfo`/`Warn`/`Error` in Objective-C). Behavior is unchanged.
7 changes: 7 additions & 0 deletions .changeset/v4-sendable-callbacks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'posthog-ios': major
---

**Breaking:** mark callbacks the SDK can call off the main thread `@Sendable` — `reloadFeatureFlags(_:)`, the `BeforeSendBlock` and `PostHogBeforeSendLogBlock` used by `setBeforeSend(_:)` on `PostHogConfig` and `PostHogLogsConfig`, `PostHogConfig.getAnonymousId`, `PostHogConfig.pushIdentityProvider` and its `completion`, and `sessionReplayConfig.captureLogsConfig.logSanitizer` — and make `PostHogFeatureFlagsLoaded` `Sendable`. This fixes a crash in Swift 6 apps when a `reloadFeatureFlags(_:)` callback is written in main-actor code.

To update main-actor state from `reloadFeatureFlags(_:)` or `pushIdentityProvider`, hop with `Task { @MainActor in ... }`. The other callbacks return a value immediately, so they must not read main-actor state. Declare closures you store in your own variables as `@Sendable` to avoid new Swift 5 warnings. Use the `BeforeSendBlock` and `PostHogBeforeSendLogBlock` typealiases for any `setBeforeSend(_:)` arrays you build or pass around, including your own helpers: in Swift 5 mode, converting an array between the old function type and these types compiles but crashes at runtime, in either direction.
5 changes: 5 additions & 0 deletions .changeset/v4-swift-tools-6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ios': major
---

**Breaking:** require Xcode 26 or later (Swift 6.2 toolchain, `swift-tools-version` 6.2) to build PostHog, the same Xcode the App Store already requires for uploads; the SDK still compiles in Swift 5 language mode and its API is unchanged.
6 changes: 3 additions & 3 deletions .github/workflows/build-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ jobs:
target: buildExamplePodsStaticLib
runner: macos-15
xcode-version: latest-stable
- name: Static Lib (Xcode 16.2)
- name: Static Lib (Xcode 26.0.1)
target: buildExamplePodsStaticLib
runner: macos-14-xlarge
xcode-version: '16.2'
runner: macos-15-xlarge
xcode-version: '26.0.1'
- name: Static Framework
target: buildExamplePodsStaticFramework
runner: macos-15
Expand Down
16 changes: 12 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
command: set -o pipefail && xcrun xcodebuild clean build -scheme PostHog -destination generic/platform=ios | xcpretty
- platform: macos-spm
command: set -o pipefail && xcrun swift build --arch arm64
- platform: spm-no-traits
command: make buildSdkSpmNoTraits buildTestsSpmTraitsIOS
- platform: macos
command: set -o pipefail && xcrun xcodebuild clean build -scheme PostHog -destination generic/platform=macos | xcpretty
- platform: tvos
Expand Down Expand Up @@ -65,10 +67,10 @@ jobs:
- name: Check public API snapshot
run: make apiCheck

build-sdk-swift53-compat:
build-sdk-min-toolchain:
needs: detect-markdown-only
runs-on: macos-14-xlarge
name: Build SDK (Swift 5 compatibility on Xcode 15.4 with CocoaPods)
runs-on: macos-15-xlarge
name: Build SDK (minimum toolchain, Xcode 26.0.1)
steps:
- name: Complete markdown-only PR check
if: needs.detect-markdown-only.outputs.markdown_only == 'true'
Expand All @@ -78,12 +80,18 @@ jobs:
- uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0
if: needs.detect-markdown-only.outputs.markdown_only != 'true'
with:
xcode-version: '15.4'
xcode-version: '26.0.1'
- name: Print toolchain versions
if: needs.detect-markdown-only.outputs.markdown_only != 'true'
run: |
xcodebuild -version
xcrun swift --version
- name: Build SDK via Swift Package Manager
if: needs.detect-markdown-only.outputs.markdown_only != 'true'
run: make buildSdkSpm
- name: Build SDK via Swift Package Manager with package traits off
if: needs.detect-markdown-only.outputs.markdown_only != 'true'
run: make buildSdkSpmNoTraits
- name: Install CocoaPods
if: needs.detect-markdown-only.outputs.markdown_only != 'true'
run: gem install cocoapods
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: macos-14-xlarge
runs-on: macos-15-xlarge
permissions:
security-events: write # for uploading CodeQL results
packages: read
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ jobs:
publish-cocoapods:
name: Publish to CocoaPods
needs: [create-github-release, notify-approval-needed]
runs-on: macos-14
runs-on: macos-15
if: always() && needs.create-github-release.result == 'success'
permissions:
contents: read
Expand All @@ -312,7 +312,7 @@ jobs:

- uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0
with:
xcode-version: '16.2'
xcode-version: '26.0.1'

- name: Publish to CocoaPods
env:
Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,35 @@ jobs:
if: needs.detect-markdown-only.outputs.markdown_only != 'true'
run: make test

# Package traits off: libwebp and PLCrashReporter are not linked, so the crash-reporting stub
# and the trait-gated tests run.
test-no-traits:
needs: detect-markdown-only
runs-on: macos-15
steps:
- name: Complete markdown-only PR check
if: needs.detect-markdown-only.outputs.markdown_only == 'true'
run: echo "Only Markdown files changed; no additional work is required for this check."
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
if: needs.detect-markdown-only.outputs.markdown_only != 'true'
- uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0
if: needs.detect-markdown-only.outputs.markdown_only != 'true'
with:
xcode-version: latest-stable
- name: Cache SPM dependencies
if: needs.detect-markdown-only.outputs.markdown_only != 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
.build
~/Library/Caches/org.swift.swiftpm
key: ${{ runner.os }}-spm-no-traits-${{ hashFiles('Package.swift', 'Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-no-traits-
- name: Test SDK (package traits off)
if: needs.detect-markdown-only.outputs.markdown_only != 'true'
run: make testNoTraits

# `make test` runs `swift test` on macOS, which compiles out every `#if os(iOS)` block, so the
# iOS-only suites (session replay, autocapture, tracing headers, crash reporting, etc.) never run
# there. Run the iOS test target on a simulator so those suites are actually exercised in CI.
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
- SDK entry point: [PostHog/PostHogSDK.swift](PostHog/PostHogSDK.swift); tests: `PostHogTests/`; replay/privacy: `PostHog/Replay/` and `PostHog/Resources/`.

## Repository invariants
- Maintain Swift 5 SDK language compatibility. Tests require Swift 6+ with Swift Testing (Xcode 16+); prefer Swift Testing for new tests. Use `PostHogTests/TestUtils/MockPostHogServer.swift` for HTTP stubbing.
- Preserve deployment minima: iOS/tvOS 13, macOS 10.15, watchOS 6, visionOS 1. Keep platform guards and graceful degradation, especially for session replay. Availability annotations/checks do not require adding platform support or raising targets. Intentional support changes require checking both `Package.swift` and `Package@swift-5.9.swift`, plus CocoaPods/Xcode packaging/build configuration; visionOS declarations belong in the Swift 5.9 manifest.
- Maintain Swift 5 SDK language mode. The package needs swift-tools-version 6.2 (Xcode 26+); prefer Swift Testing for new tests. Use `PostHogTests/TestUtils/MockPostHogServer.swift` for HTTP stubbing.
- Preserve deployment minima: iOS 15, tvOS 15, macOS 11, watchOS 10, visionOS 1. Keep platform guards and graceful degradation, especially for session replay. Availability annotations/checks do not require adding platform support or raising targets. Intentional support changes require checking `Package.swift` plus CocoaPods/Xcode packaging/build configuration.
- Public APIs must remain thread-safe and callable from any thread. Preserve offline operation and queue-based event batching; never assume connectivity. `PostHogSDK.shared` is the default singleton; independent instances use `PostHogSDK.with(_:)` with `PostHogConfig`.
- Preserve replay masking/privacy behavior and privacy-safe error logging; do not expose sensitive user data.
- Prefer no new dependencies. libwebp is embedded; PHPLCrashReporter is vendored, prefixed PLCrashReporter for native crash reporting on iOS/macOS/tvOS, excluded on watchOS/visionOS.
- Prefer no new dependencies. libwebp is embedded; PHPLCrashReporter is vendored, prefixed PLCrashReporter for native crash reporting on iOS/macOS/tvOS, excluded on watchOS/visionOS. Both are behind default-on package traits (`SessionReplay`, which also gates the replay engine, and `CrashReporting`); gate code that uses them with `#if !SWIFT_PACKAGE || <Trait>`. The survey engine and UI are behind the default-on `Surveys` trait the same way. Once released, never add a new default-on trait on its own: apps that list traits would silently lose the feature. Split an existing trait instead, keeping it as an umbrella that enables the new ones.

## Validation selector
- Use `make` wrappers, not direct `swift`/`xcodebuild`. Iterate with focused tests; before submitting SDK changes, run contributor checks: `make lint`, `make test`, `make buildSdk`.
Expand Down
Loading