NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #4153 most downloaded on npm
Type definitions for the PostHog JavaScript SDK
Last release 5 days ago
29 Sep 2026
Ships on a steady schedule
a new release about every 9 days
Nearly every release is documented
notes for 60 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
9 months old
261 releases · first in 2026
One column per month.
1.413.0 Minor Changes #4794 e89d224 Thanks @AyobamiH ! - Add onActiveMatchingSurveysChanged to subscribe to survey eligibility updates with safe unsub
1.412.4 Patch Changes #5008 3a8035f Thanks @nachogarcia ! - Retry /flags in the browser SDK on HTTP 502/504 and on request timeouts, bounded by the ne
3a8035f Thanks @nachogarcia! - Retry /flags in the browser SDK on HTTP 502/504 and on request timeouts, bounded by the new feature_flag_request_max_retries config (default 1, set 0 to disable). Plain transport failures are deliberately left to the existing status-zero circuit breaker.1.412.3 Patch Changes #5018 9cd8ebd Thanks @turnipdabeets ! - Stop dropping long spans that end: maxSpanAgeMs now evicts spans only once maxLiveSpans
9cd8ebd Thanks @turnipdabeets! - Stop dropping long spans that end: maxSpanAgeMs now evicts spans only once maxLiveSpans is reached, so a span that runs past the age limit and then ends is exported, and its children are no longer orphaned.1.412.2 Patch Changes #5014 5e86154 Thanks @pauldambra ! - Rename the metrics.network default attributes to the OTel HTTP client semantic conventions:
5e86154 Thanks @pauldambra! - Rename the metrics.network default attributes to the OTel HTTP client semantic conventions: http.request.method, server.address, server.port, url.scheme, url.template, http.response.status_code and error.type replace method, host, path and status_class.1.412.1 Patch Changes #4962 85b775a Thanks @posthog ! - Docstrings for identity_hash and setIdentity() now say the hash is signed with the Secret API
### Minor Changes - #4918 `c666606` Thanks @pauldambra! - Add metrics.network config to record HTTP and HTTPS fetch and XMLHttpRequest durations as hi
c666606 Thanks @pauldambra! - Add metrics.network config to record HTTP and HTTPS fetch and XMLHttpRequest durations as histograms, with an optional custom metric name and attributes.
(2026-09-14)1.411.1 Patch Changes #4860 8207df8 Thanks @posthog ! - Back off automatic feature flag refreshes on idle visible pages only when remote_config_refres
### Minor Changes - #4579 `19e78cc` Thanks @turnipdabeets! - Add experimental distributed tracing to posthog-node: startSpan, withSpan and getActiveSp
19e78cc Thanks @turnipdabeets! - Add experimental distributed tracing to posthog-node: startSpan, withSpan and getActiveSpan record spans against a new traces client option. A service with tracing off still forwards an inbound traceparent, including from spans nested inside the one that received it, so a distributed trace is not severed. A traceparent may be passed as the one-element array req.headersDistinct gives. A beforeSpanSend hook sees every span before it is exported and may edit or drop it, and maxAttributesPerSpan, maxEventsPerSpan, maxAttributeValueLength, maxLiveSpans and maxSpanAgeMs bound what a single span and a single process may hold.
(2026-09-10)### Patch Changes - #4878 `14ba783` Thanks @dustinbyrne! - Allow null bootstrap values and treat null or empty distinct IDs as missing. (2026-09-10) -
### Minor Changes - #4707 `b441eb2` Thanks @posthog! - Segment integration: allow segment to accept an integration config with filterProperties, so cu
b441eb2 Thanks @posthog! - Segment integration: allow segment to accept an integration config with filterProperties, so customers can filter PostHog-generated enrichment properties before Segment sends an event to its destinations. Returning null or throwing leaves the original Segment event unenriched.
(2026-09-09)#4876 0c2a15f Thanks @dustinbyrne! - Fix dead-click detection for text selection and editable caret gestures when mouse release is delayed, while continuing to report inert text clicks.
(2026-09-09)
#4733 24fa541 Thanks @dustinbyrne! - fix(web): avoid reporting clicks that select or unselect text as dead clicks
(2026-09-09)
### Patch Changes - #4869 `891eefa` Thanks @robbie-c! - Apply replay URL privacy settings to URL values in captured JSON-LD payloads. (2026-09-09)
### Patch Changes - #4864 `e8b2be1` Thanks @robbie-c! - Include the masked page URL with JSON-LD replay events. (2026-09-09)
1.409.2 Patch Changes #4791 b2affdc Thanks @posthog ! - Prefer sendBeacon for unbatched events, such as { send_instantly: true } captures, once PostHo
b2affdc Thanks @posthog! - Prefer sendBeacon for unbatched events, such as { send_instantly: true } captures, once PostHog's own pagehide handler (or unload fallback) marks the page as unloading. Captures from beforeunload or earlier pagehide listeners retain their normal transport. Preserve response-capable transports on active pages so failed requests can be retried, including when fetch is unavailable.### Patch Changes - #4785 `74ca945` Thanks @marandaneto! - Clarify feature flag return-value terminology across SDK APIs. A false value is a conclusiv
#4785 74ca945 Thanks @marandaneto! - Clarify feature flag return-value terminology across SDK APIs. A false value is a conclusive off evaluation, while undefined means no evaluation is available. Remote evaluation omits globally inactive flags, whereas backend local evaluation can resolve cached inactive definitions to false.
(2026-09-07)
#4666 5e74132 Thanks @robbie-c! - Preserve universally safe JSON-LD properties and allowlisted tree structure when replay redacts other fields. Keep only DOM-backed ID fragments. Keep only @type values shaped like a Schema.org term, and limit types and payloads. Publish a reusable sanitization contract fixture.
(2026-09-07)
1.409.0 Minor Changes #4774 426e1fd Thanks @Fhatu12 ! - Add reuseAnonymousId support to browser identify calls. (2026-09-04)
### Patch Changes - #4760 `41ed3af` Thanks @marandaneto! - Require a distinct ID when calling identify and correct its API documentation to match the
41ed3af Thanks @marandaneto! - Require a distinct ID when calling identify and correct its API documentation to match the existing runtime validation.
(2026-09-03)1.408.0 Minor Changes #4741 61a26e5 Thanks @veryayskiy ! - Forward generic server-signed identity claims with conversations widget requests. (2026-09-
61a26e5 Thanks @veryayskiy! - Forward generic server-signed identity claims with conversations widget requests.### Patch Changes - #4712 `7f009dc` Thanks @posthog! - Document the session replay input masking defaults in the session_recording config reference: i
7f009dc Thanks @posthog! - Document the session replay input masking defaults in the session_recording config reference: inputs are masked by default, password inputs stay masked on a partial maskInputOptions override, non-input text and images need maskTextSelector, and client-side masking options override the project privacy setting.
(2026-09-01)1.407.1 Patch Changes #4665 d5ef459 Thanks @fasyy612 ! - Session replay can now bound DOM mutation bytes with an opt-in budget. Set __mutationBytesBuc
d5ef459 Thanks @fasyy612! - Session replay can now bound DOM mutation bytes with an opt-in budget. Set __mutationBytesBucketSize (e.g. 1MB) to enable: mutations beyond the sustained budget (__mutationBytesRefillRate, default 25KB/s) are dropped and the recording resyncs with a full snapshot, keeping recordings from apps with very high DOM churn playable. Off by default.1.407.0 Minor Changes #4655 4f80973 Thanks @robbie-c ! - Add the 2026-08-30 config defaults. These defaults enable sanitized JSON-LD replay events. (2
1.406.2 Patch Changes #4635 ab1383a Thanks @robbie-c ! - Capture paste interactions with clipboard autocapture without collecting pasted text. (2026-0
#4635 ab1383a Thanks @robbie-c! - Capture paste interactions with clipboard autocapture without collecting pasted text.
(2026-08-26)
#4640 0d2cf49 Thanks @robbie-c! - Add opt-in Schema.org JSON-LD capture to session replay through session_recording.captureJsonLd. When enabled, the recorder emits sanitized JSON-LD as custom replay events and excludes all script elements from replay snapshots.
(2026-08-26)
Feature flags now own their automatic refresh timer and visibility listener. Hidden tabs reload due flags when they become visible. The existing five-
#4476 ed4dd97 Thanks @posthog! - fix(browser): refresh configured feature flags when a hidden tab becomes visible
Feature flags now own their automatic refresh timer and visibility listener.
Hidden tabs reload due flags when they become visible. The existing five-minute
default and remote_config_refresh_interval_ms behavior remain unchanged. (2026-08-25)
### Minor Changes - #4598 `334159b` Thanks @posthog! - Web vitals now capture attribution by default for INP and LCP, so a slow interaction or paint a
334159b Thanks @posthog! - Web vitals now capture attribution by default for INP and LCP, so a slow interaction or paint arrives with the target element and phase breakdown that make it diagnosable. CLS stays without attribution by default, because its attribution holds detached DOM nodes and can leak memory in single-page apps. Set capture_performance.web_vitals_attribution to false to opt out, true for every metric, or an array to name the metrics. The captured metric also drops the empty entries array and bounds attribution to a small set of useful fields, and the attributed INP observer no longer collects the processedEventEntries we never read.
(2026-08-25)### Patch Changes - #4607 `7ec4f0d` Thanks @posthog! - Drop exceptions thrown by user scripts the browser injects into every page (Firefox for iOS, Ch
capture_pageview now accepts an object with path, search, and hash options. Each selected URL component triggers a $pageview when it changes, includin
#4418 be2161d Thanks @posthog! - feat: add granular automatic pageview options for SPA navigation
capture_pageview now accepts an object with path, search, and hash options. Each selected URL component triggers a $pageview when it changes, including direct hash changes used by hash-based routers. The existing 'history_change' option continues to capture pathname changes. (2026-08-24)
### Patch Changes - #4583 `6322f09` Thanks @turnipdabeets! - Fix logs and metrics being silently dropped when an attribute holds a very large integer,
6322f09 Thanks @turnipdabeets! - Fix logs and metrics being silently dropped when an attribute holds a very large integer, a function, a symbol, a sparse array, or a truncated emoji.
Cap log and metric attributes at 20 levels of nesting, 1,000 entries per object and 10,000 values in total, marking anything beyond as [Truncated].
Type OtlpAnyValue.intValue as string | number — code reading that field must handle both. (2026-08-21)### Minor Changes - #4496 `1ade666` Thanks @marandaneto! - Add cookieWinsOnConflict to keep shared cross-subdomain identity and session state ahead of
1ade666 Thanks @marandaneto! - Add cookieWinsOnConflict to keep shared cross-subdomain identity and session state ahead of stale per-origin localStorage, deprecate __preview_cookie_wins_on_conflict, and enable the new behavior for the 2026-08-29 defaults.
(2026-08-18)The dead-click detector treated a visibilitychange as evidence a click was dead: it measured Math.abs(clickTimestamp - lastVisibilityChange) and, once
#4503 eb05237 Thanks @pauldambra! - fix(dead-clicks): treat visibility and focus changes as liveness signals, not dead-click evidence
The dead-click detector treated a visibilitychange as evidence a click was dead: it measured Math.abs(clickTimestamp - lastVisibilityChange) and, once that exceeded the threshold, timed the click out as dead. Because it only recorded the tab becoming visible, any click in a session where the tab had ever been backgrounded (median gap ~1 minute) was flagged.
A visibility or focus change near a click is the opposite — a sign the click did something (it woke/focused the tab, opened a new tab, or opened a new window/popup) — so these signals now only ever suppress a dead click, never cause one:
hidden), and a window focus/blur observer is added, since a click that opens a new window/popup may leave the tab visible and only surface as the current window losing focus.$dead_click_visibility_changed_timeout stays in the payload (always false) for shape compatibility, and a new $dead_click_focus_changed_delay_ms is emitted for observability.## 1.404.0 ### Minor Changes - #4485 `8bc63c3` Thanks @dustinbyrne! - Default external dependency loading to versioned asset paths with automatic fall
8bc63c3 Thanks @dustinbyrne! - Default external dependency loading to versioned asset paths with automatic fallback to legacy paths, and add a strict_script_versioning: 'fallback' mode.
(2026-08-13)The default budget (10,000 rules) moves from the recorder chunk into posthog-js session recording options, so npm-pinned or cached bundles keep their
b2c6830 Thanks @arnohillen! - Harden the session replay stylesheet inlining budget (inlineStylesheetBudgetRules):
0 to disable) and direct rrweb.record() consumers keep unbounded inlining unless they opt in._cssText atomically, so monolithic sheets no longer produce one long task and partial CSS never reaches the wire.pagehide; residual failure modes are counted via $sdk_debug_replay_deferred_stylesheets_failed / _abandoned.insertRule output, adoptedStyleSheets) no longer charge the budget, since deferring <link> sheets buys those pages nothing.$sdk_debug_replay_discarded_duration_samples). (2026-08-13)## 1.403.0 ### Minor Changes - #4495 `e4b9947` Thanks @marandaneto! - feat(browser): add rewriteRequestPath to customize API, feature flag, and asset
#4495 e4b9947 Thanks @marandaneto! - feat(browser): add rewriteRequestPath to customize API, feature flag, and asset paths for reverse proxies
(2026-08-12)
#4493 e34ebf9 Thanks @marandaneto! - Add reset options for applying bootstrapped identity, feature flag, and session values after posthog.reset() while preserving the legacy boolean argument.
(2026-08-12)
## 1.402.3 ### Patch Changes - #4494 `deb6bb0` Thanks @marandaneto! - fix(types): accept current and legacy Segment Analytics SDK types in the Segment
deb6bb0 Thanks @marandaneto! - fix(types): accept current and legacy Segment Analytics SDK types in the Segment integration config
(2026-08-11)## 1.402.2 ### Patch Changes - #4434 `75fb719` Thanks @arnohillen! - Make the session replay attribute masking options mutually exclusive: when both m
75fb719 Thanks @arnohillen! - Make the session replay attribute masking options mutually exclusive: when both maskAllElementAttributes and maskAttributeFn are set, the coarse option wins and the callback is ignored (with a console warning), so a callback can no longer accidentally unmask what maskAllElementAttributes hides.
(2026-08-06)Initial navigation and performance-timing entries are now passed through maskCapturedNetworkRequestFn, including when they have no method. URL rewrite
#4286 d108d66 Thanks @posthog! - fix(replay): preserve privacy masking for initial network metadata
Initial navigation and performance-timing entries are now passed through maskCapturedNetworkRequestFn, including when they have no method. URL rewrites are respected. When the callback returns nullish for an initial entry, replay-required timing metadata is retained without its URL, headers, or body so method-gated callbacks do not drop the metadata or expose deliberately filtered customer data. Derived server-timing entries are also suppressed when this strict fallback is used. Enforced PostHog filtering and payload cleaning still run first. (2026-08-05)
## 1.402.0 ### Minor Changes - #4376 `2da12b8` Thanks @posthog! - Add attribute-level masking to session replay: maskAttributeFn provides per-attribut
2da12b8 Thanks @posthog! - Add attribute-level masking to session replay: maskAttributeFn provides per-attribute control over the final serialized value, while maskAllElementAttributes masks all source DOM string attributes (including rendering attributes and synthesized form values) at the cost of replay fidelity.
(2026-08-05)## 1.401.1 ### Patch Changes - #4380 `3c40b6c` Thanks @marandaneto! - Keep request timeouts active through response body consumption and clarify event
3c40b6c Thanks @marandaneto! - Keep request timeouts active through response body consumption and clarify eventual event UUID deduplication semantics.
(2026-08-05)Client-side route changes in SPAs previously left web vitals (LCP especially) accumulating against the original hard-navigation timestamp, inflating t
#4266 43d1850 Thanks @posthog! - feat: add opt-in capture_performance.__preview_web_vitals_soft_navs to fix inflated web vitals on single-page apps
Client-side route changes in SPAs previously left web vitals (LCP especially) accumulating against the original hard-navigation timestamp, inflating the top tail of Core Web Vitals. Setting capture_performance: { __preview_web_vitals_soft_navs: true } now scopes metrics to the browser's Soft Navigation entries so each route change starts a fresh measurement window. It's a preview option because it relies on Chrome's experimental Soft Navigation Detection API and loads pinned stable web-vitals 6.x callbacks; when disabled (the default), the existing web-vitals 5.x behavior remains unchanged. (2026-08-04)
## 1.400.2 ### Patch Changes - #4339 `f865818` Thanks @posthog! - Report privacy-aware dropped-event count, page and session context in the client rat
reset() clears stored consent along with the rest of the user's state. With opt_out_capturing_by_default, this returns the instance to the opted-out d
#4314 feb9e2a Thanks @posthog! - fix: warn when reset() silently opts the user back out
reset() clears stored consent along with the rest of the user's state. With opt_out_capturing_by_default, this returns the instance to the opted-out default, so calling reset() after opt_in_capturing() would stop capturing without warning. It now logs a warning when that happens and documents the required ordering. (2026-08-04)
## 1.400.0 ### Minor Changes - #4125 `fde7145` Thanks @DerGeraetK! - Add session_recording.sampling to disable or throttle mousemove capture (and opti
fde7145 Thanks @DerGeraetK! - Add session_recording.sampling to disable or throttle mousemove capture (and optionally mouseInteraction) in session replay. Canvas recording now merges its canvas sampling with user-provided sampling instead of overwriting it.
(2026-08-03)The return value decides what happens to that canvas's frame:
#4270 92427a1 Thanks @turnipdabeets! - Add canvas mask regions to session replay canvas capture: session_recording.canvasCapture.maskRegionsFn is called once per canvas per captured frame, and the returned regions (CSS pixels, relative to the canvas) are painted black in the captured frame before it is encoded — letting apps that render into canvas (e.g. Flutter web via CanvasKit) mask content that DOM-based masking cannot see.
The return value decides what happens to that canvas's frame:
[] — nothing to mask; the frame is recorded as is.null — regions could not be computed; the frame is skipped rather than recorded unmasked.maskRegionsFn not set — canvases are recorded unmasked and canvas capture behavior is unchanged.Configuring maskRegionsFn also disables canvas pixel serialization in DOM full snapshots (rr_dataURL) — that path never sees the mask regions, so skipping it closes the route that could otherwise embed unmasked canvas stills in a snapshot; the canvas repaints from the masked frame stream instead. Every canvas the provider answers — with regions or [] — re-sends an unchanged frame as a keyframe every 30s, so after a full snapshot or a seek an idle canvas repaints within at most 30s.
An app whose real provider only exists once its runtime has booted chooses what happens in between by what it declares in posthog.init: a function covering the whole canvas blacks those frames out, () => null skips them, and declaring nothing records them. Client-side only, cannot be set via remote configuration. (2026-07-29)
isFeatureEnabled(key, { defaultValue: false }) now returns the given default when the flag has no value — flags not loaded yet, or no flag with that k
#4222 0f2407b Thanks @turnipdabeets! - feat: add a default-value option to isFeatureEnabled
isFeatureEnabled(key, { defaultValue: false }) now returns the given default when the flag has no value — flags not loaded yet, or no flag with that key — and the return type narrows to boolean. The option name is the same in posthog-js, posthog-js-lite, and posthog-react-native. Without defaultValue, behavior is unchanged: boolean | undefined. (2026-07-22)
## 1.397.1 ### Patch Changes - #4198 `fbfc84f` Thanks @pauldambra! - feat: make the pending session recording trigger buffer interval configurable (20
fbfc84f Thanks @pauldambra! - feat: make the pending session recording trigger buffer interval configurable
(2026-07-20)## 1.397.0 ### Minor Changes - #4149 `607bf54` Thanks @pauldambra! - Add dead swipe detection to dead clicks autocapture. When dead clicks autocapture
607bf54 Thanks @pauldambra! - Add dead swipe detection to dead clicks autocapture. When dead clicks autocapture is enabled, touch swipe gestures that produce no observable screen change (no scroll, mutation, selection or visibility change) are now captured as $dead_swipe events, surfacing failed navigations on touch devices. Configurable via capture_dead_swipes (default true) and swipe_threshold_px (default 30) on the capture_dead_clicks config. Swipes over surfaces whose response cannot be observed (canvas, video and other media elements under the finger) are skipped, and captures are limited per page load via max_dead_swipes_per_page_load (default 10).
(2026-07-16)$feature_flag_called events now carry a $feature_flag_has_experiment boolean sourced from the server's has_experiment flag metadata (the /flags?v=2 re
#4159 fad6d9a Thanks @haacked! - add $feature_flag_has_experiment to $feature_flag_called events
$feature_flag_called events now carry a $feature_flag_has_experiment boolean sourced from the server's has_experiment flag metadata (the /flags?v=2 response for remote evaluation, the /api/feature_flag/local_evaluation definitions for posthog-node local evaluation). The property is only sent when the server explicitly reports has_experiment; it is omitted entirely when the value is unknown (older servers, missing metadata, bootstrapped or locally injected flags). (2026-07-16)
## 1.395.0 ### Minor Changes - #4129 `800af7c` Thanks @pauldambra! - feat: add session_recording.attributeFilter option that passes an attribute allow
800af7c Thanks @pauldambra! - feat: add session_recording.attributeFilter option that passes an attribute allowlist through to the native MutationObserver, so mutations to unlisted attributes (e.g. animation-driven inline style churn) never cost recording CPU (port of upstream rrweb #1873)
(2026-07-15)## 1.394.0 ### Minor Changes - #4101 `dc2aa5b` Thanks @posthog! - Normalize the error tracking rate-limiter config to first-class options. The browser
dc2aa5b Thanks @posthog! - Normalize the error tracking rate-limiter config to first-class options. The browser SDK now reads exceptionRateLimiterRefillRate / exceptionRateLimiterBucketSize on error_tracking, with the previous double-underscore __exceptionRateLimiterRefillRate / __exceptionRateLimiterBucketSize options deprecated but still honoured as a fallback. The option shape (ExceptionRateLimiterConfig) and default-resolution logic (resolveExceptionRateLimiterConfig) now live in @posthog/core and are shared between the browser and Node SDKs.
(2026-07-14)A statsd-style pre-aggregating metrics client for the PostHog Metrics product (alpha). Samples are folded into per-series aggregates in memory (counts
#4115 86bb3a5 Thanks @DanielVisca! - add the posthog.metrics API (count, gauge, histogram) — alpha
A statsd-style pre-aggregating metrics client for the PostHog Metrics product (alpha). Samples are folded into per-series aggregates in memory (counts sum, gauges keep the last value, histograms accumulate buckets) and flushed periodically as OTLP/JSON to /i/v1/metrics — one data point per series per flush window, no matter how many calls. No OpenTelemetry SDK setup required:
posthog.metrics.count('orders_created', 1)
posthog.metrics.gauge('active_connections', 42)
posthog.metrics.histogram('api_latency', 187, { unit: 'ms' })
Configure via metrics: { serviceName, environment, flushIntervalMs, maxSeriesPerFlush, beforeSend, ... }. (2026-07-08)
## 1.392.1 ### Patch Changes - #4053 `45d1b36` Thanks @posthog! - feat(web): add a graceful shutdown() to the browser client for parity with posthog-n
45d1b36 Thanks @posthog! - feat(web): add a graceful shutdown() to the browser client for parity with posthog-node, so isomorphic teardown code (e.g. the Nuxt module) that calls posthog.shutdown() on the client no longer throws TypeError: shutdown is not a function. It best-effort flushes the queued events and always resolves.
(2026-07-03)### Minor Changes - #3987 `74cc6bb` Thanks @TueHaulund! - Add a get_current_url config option that overrides the URL used for client-side URL targetin
74cc6bb Thanks @TueHaulund! - Add a get_current_url config option that overrides the URL used for client-side URL targeting — session replay URL triggers, the session replay URL blocklist, survey URL display conditions, product tour URL conditions, web experiment URL conditions, and autocapture URL allow/ignore lists. These match against window.location.href directly, which does not reflect a $current_url rewritten in before_send. Apps where the browser URL is not meaningful for targeting (e.g. Electron/desktop builds served from a generated host) can now return the logical URL to match against. Defaults to window.location.href when not set.
(2026-06-29)### Patch Changes - #3919 `99bad9c` Thanks @pauldambra! - Session replay network capture: add an opt-in streaming reader for request/response bodies t
99bad9c Thanks @pauldambra! - Session replay network capture: add an opt-in streaming reader for request/response bodies that stops at the payload size limit instead of buffering the whole body and then discarding it — bounding memory and pre-request latency when a body is very large. It reads only a clone of the body, so it never consumes the stream the page itself reads, and always resolves (never rejects) into the page's fetch. Off by default; enabled for defaults: '2026-06-25' and settable directly via session_recording.streamNetworkBody.
(2026-06-24)If you only want to capture some hashes, leave hash capture enabled and use before_send to remove or redact sensitive hash values before events are se
#3921 c28b161 Thanks @marandaneto! - Add disable_capture_url_hashes to strip URL fragments from automatically captured URLs. It is disabled by default for backwards compatibility, and enabled automatically when config.defaults is '2026-06-25' or later. Enabling it (either explicitly or via the '2026-06-25' defaults) is a breaking behavior change for SPAs that rely on URL hashes for routing or analytics, because hash-based routes will be collapsed to the same URL without the fragment in fields such as $current_url, $initial_current_url, $session_entry_url, autocapture $elements[*].attr__href, $external_click_url, replay href URLs, heatmaps, web vitals $current_url, logs url.full, conversations current_url/request_url, or Next.js Pages Router $pageview $current_url.
If you only want to capture some hashes, leave hash capture enabled and use before_send to remove or redact sensitive hash values before events are sent. (2026-06-23)
### Patch Changes - #3903 `6b21f77` Thanks @marandaneto! - Validate custom event UUID overrides and generate new UUIDs when invalid. (2026-06-19)
6b21f77 Thanks @marandaneto! - Validate custom event UUID overrides and generate new UUIDs when invalid.
(2026-06-19)Adds session_recording.canvasCapture.resolutionScale - a (0, 1] fraction of the canvas display size to capture replay frames at. The captured bitmap i
#3885 5392a55 Thanks @pauldambra! - feat(replay): capture canvas at reduced resolution
Adds session_recording.canvasCapture.resolutionScale - a (0, 1] fraction of the canvas display size to capture replay frames at. The captured bitmap is downscaled (pixel-area savings are quadratic) while the canvas's true display size is still recorded, so playback stretches the smaller frame back to the correct dimensions and aspect ratio - only sharpness drops, never layout. It defaults to 1 (full resolution, matching today's behaviour), and the latest defaults bundle (2026-05-30) opts new installs into 0.6.
The canvas's true display size travels with each frame through the encode worker (as required message fields), so the encoded reply is always drawn back to the correct dimensions — no per-canvas state is retained on the main thread, and downscaling can never mislabel a canvas's dimensions. At full resolution the captured pixels are unchanged (the quality resampling hint is only applied when actually downscaling); the emitted drawImage now always uses the explicit destination-size form, which is pixel-equivalent on replay.
Mechanically, @posthog/rrweb's canvas FPS-snapshot observer takes an optional canvasResolutionScale record option and downscales each captured frame accordingly. (2026-06-19)
`js posthog.init(" ", { logs: { beforeSend: (log) => { // return null to drop the log, or return the (optionally modified) log to keep it if (log.body
#3869 81b79fb Thanks @turnipdabeets! - Add a beforeSend option to the logs config, so you can inspect, redact, or drop log records before they're sent:
posthog.init("<token>", {
logs: {
beforeSend: (log) => {
// return null to drop the log, or return the (optionally modified) log to keep it
if (log.body.includes("password")) {
return null;
}
return log;
},
},
});
beforeSend accepts a single function or an array of functions (applied left to right); returning null from any of them drops the record. It runs for logs sent via both posthog.captureLog() and posthog.logger.*. (2026-06-17)
### Minor Changes - #3865 `b469830` Thanks @turnipdabeets! - The browser's programmatic logs API (posthog.captureLog() / posthog.logger.*) now runs th
b469830 Thanks @turnipdabeets! - The browser's programmatic logs API (posthog.captureLog() / posthog.logger.*) now runs through the shared @posthog/core logs pipeline that React Native already uses — no change to the public API or existing behavior. Log delivery is more resilient as a result: oversized batches are split automatically, failed sends retry with exponential backoff, and delivery resumes when the browser comes back online.
(2026-06-17)### Minor Changes - #3863 `b6bc9be` Thanks @marandaneto! - Add autocapture-only CSS selector opt-outs for web interactions. (2026-06-17)
b6bc9be Thanks @marandaneto! - Add autocapture-only CSS selector opt-outs for web interactions.
(2026-06-17)### Minor Changes - #3709 `c6c163a` Thanks @posthog! - Add unsetPersonProperties() to remove person properties, the counterpart to setPersonProperties
c6c163a Thanks @posthog! - Add unsetPersonProperties() to remove person properties, the counterpart to setPersonProperties(). Previously the only way to unset a person property was to hand-pass a $unset array inside a capture() call.
(2026-06-16)c9c7df1 Thanks @marandaneto! - Add $unset to capture options and pass it through in browser capture payloads.
(2026-06-16)### Patch Changes - #3837 `29bf8e3` Thanks @marandaneto! - Add missing bugs metadata to package manifests. (2026-06-15)
29bf8e3 Thanks @marandaneto! - Add missing bugs metadata to package manifests.
(2026-06-15)A session now rotates only when every tab has been idle past the timeout, rather than whenever a single background tab decides it is idle. On the acti
#3690 dbf2377 Thanks @pauldambra! - fix(sessionid): keep the session id stable across tabs
A session now rotates only when every tab has been idle past the timeout, rather than whenever a single background tab decides it is idle. On the active event path an idle tab re-reads the session id from storage before rotating: if a sibling tab kept the session alive it does not rotate, and if a sibling already rotated it adopts that id instead of minting a new one. This removes spurious cross-tab session fragmentation (inflated session counts, truncated session durations, split replays). When a sibling session is adopted, onSessionId handlers fire with changeReason.crossTabAdoption: true so session recording, pageview state, and session-scoped properties follow the new session. When persistence_save_debounce_ms > 0 (the 2026-05-30 default) the refresh reads only the session-id key so it cannot clobber a sibling's write.
Note: projects with significant multi-tab usage will see fewer but longer sessions after upgrading — this is a correction of previously over-counted sessions, not a traffic change. (2026-06-11)
Your coding agent can read these notes before it upgrades. Set up the MCP server →