Repository navigation
feat(browser): add a single-tag SDK loader - #5406
marandaneto wants to merge 6 commits into
Conversation
|
Replay incident risk checkThis diff touches code involved in past incidents. This is a heads-up, not a verdict: read the matched sections of INCIDENTS.md and answer their review questions before merging. For a judgment on whether this diff has the same failure mode, run the |
There was a problem hiding this comment.
PostHog Review · Standard · reviewed 7fb57f7: 2 Should fix.
This is the only automatic review while this pull request is a draft. Comment @posthog review to review new changes sooner. Otherwise, a review of the new changes runs when you mark it ready for review.
See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.
Coherence4 spec problems · 0 failing chokepoints · 2 spec gaps · 0 guard failures 44 components, 7 bullets (5 invariants, 2 requirements), 4 problems Spec problems (spec --check)
Chokepoints: none declared. Spec gaps: 2 entrances with outside or unknown trust and no traced control, and 10 more held by the adoption baseline
Close one with |
|
Size Change: 0 B Total Size: 25.2 MB ℹ️ View Unchanged
|
posthog-js Compliance ReportDate: 2026-10-24 13:35:17 UTC ✅ All Tests Passed!26/26 tests passed Capture Tests✅ 26/26 tests passed View Details
|
Node SDK compliance v2Commit: 6eeb591 Capture v0 — acceptance✅ 28 passed / ❌ 3 non-passing / Drill-down: run artifacts → ❌ server_snapshot_payload_false — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: ❌ server_snapshot_payload_zero — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: ❌ server_snapshot_payload_null — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: Harness exit: 1; report validation exit: 1. Capture v1 — acceptance✅ 28 passed / ❌ 3 non-passing / Drill-down: run artifacts → ❌ server_snapshot_payload_false — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: ❌ server_snapshot_payload_zero — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: ❌ server_snapshot_payload_null — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: Harness exit: 1; report validation exit: 1. Capture v0 — migration✅ 55 passed / ❌ 1 non-passing / Drill-down: run artifacts → ❌ migration/yaml-parity-v1/remote-flags-v1.feature::Omitted GeoIP settings default to enabled in flag requests — failed_assertionFlags field differs Code: Failed step: migration/yaml-parity-v1/remote-flags-v1.feature:134 the first flags request field "geoip_disable" should equal JSON false Operation: Field: Expected: false Actual value: true Harness exit: 1; report validation exit: 1. Capture v1 — migration✅ 120 passed / ❌ 1 non-passing / Drill-down: run artifacts → ❌ migration/yaml-parity-v1/remote-flags-v1.feature::Omitted GeoIP settings default to enabled in flag requests — failed_assertionFlags field differs Code: Failed step: migration/yaml-parity-v1/remote-flags-v1.feature:134 the first flags request field "geoip_disable" should equal JSON false Operation: Field: Expected: false Actual value: true Harness exit: 1; report validation exit: 1. |
Problem
Installing the browser SDK through HTML currently requires pasting a minified bootstrap and calling
posthog.init(). We want a single external script tag, like Sentry's loader, without publishing a separate file for every project or changing existing installations.Changes
Add a shared static
loader.js, about 2.7 KB gzipped. It readsproject_tokenfrom its own URL and falls back todata-project-token. Putting the token in the URL path would require additional routing. A query parameter keeps this a normal static asset.The loader generates its queued method list from the SDK class at build time, including nested
peoplemethods. There is no manually maintained method list and the SDK class is not bundled into the loader. The build also watches the SDK source so newly added public methods appear in development builds. Turbo includesscripts/loader-methods.mjsin its build inputs, so changes to the generator invalidate cached loader output.HTML installation
The attribute form also works:
crossorigin="anonymous"is recommended but optional on the loader tag. Omitting it does not prevent initialization. The browser chooses the loader request mode before its code executes, so the loader cannot make its own completed request anonymous. Keeping the attribute enables full cross-origin error reporting and anonymous cross-origin fetching. The full SDK script injected by the loader always usescrossOrigin = 'anonymous', regardless of the outer tag.Use the EU asset host for EU projects. Include the loader before application scripts that call
posthog. Do not addasyncordeferwhen those scripts need the queue immediately.Loading from JavaScript
The loader's
onloadcallback means the bootstrap has executed, not that the full SDK is ready. Methods such ascapture()are queued while the SDK downloads. Synchronous getters cannot return SDK values yet. The loader does not buffer browser interactions or unhandled errors that occur before the SDK installs its listeners.Optional deferred initialization
Auto-init remains the default. Set
data-defer-init="true"to download the SDK but wait for an explicitposthog.init(...)call. This is separate from HTML'sdeferattribute.Call
init()before other SDK methods. If the SDK is still downloading, initialization and subsequent calls are queued. If it has already downloaded,init()uses the normal SDK API. Function-valued configuration such asbefore_sendand named instances are supported. In deferred mode, pass the token and all configuration directly toinit()instead of using the automatic-init token and configuration attributes. No token is required in the loader tag in this mode. Omitdata-defer-init, or set it tofalse, to keep auto-init.Configuration and compatibility
data-api-hostoverrides the ingestion host, including relative proxy paths. Standard US/EU asset hosts select their corresponding ingestion host automatically.data-defaultsselects a defaults snapshot and overridesdata-config.defaults. If neither is provided, the loader leavesdefaultsunspecified and the SDK uses its normalunsetdefault.data-configaccepts a JSON object with SDK options before initialization. Explicitdata-api-hostanddata-defaultsattributes override the corresponding JSON values.data-config='{"opt_out_capturing_by_default":true}'. Deferred initialization accepts the normal SDK configuration object, including callbacks.array.js, npm entrypoints, and recording logic are unchanged. There is no change to the existing setup or remote-config contracts in sdk-specs.Publishing
The existing build discovers the new entrypoint and emits
dist/loader.js. The existing browser publisher uploads it and its source map to both regions under/static/<version>/,/static/1/, and/static/. No new endpoint, publishing workflow, or per-project asset is needed.The mutable
/static/loader.jsand/static/1/loader.jsaliases embed their SDK build version and request/static/<build-version>/array.js. A cached loader therefore keeps using its matching immutable SDK instead of depending on a separately cached/static/array.jsalias. Explicit/static/<version>/loader.jsURLs retain their versioned sibling SDK path. There is no fallback to the mutable core alias. Proxy prefixes are preserved, and proxies or self-hosted deployments must serve the versioned SDK directories.Historical releases are not rebuilt or backfilled. Existing snippets that load
/static/array.jsremain unchanged.This is an opt-in installation path. The hosted bootstrap adds a network request before SDK startup, so analytics and recording can start later on new integrations. Watch recording-volume alerts for a day after rollout.
Validation
1.360.0SDK core. These include first-time JavaScript injection, queued survey callbacks, nested person-property updates, trace-metric delivery, and explicit initialization both before and after SDK download.TypeError, and fatal configuration errors produced no diagnostic without debug mode. The same tests now pass. Generator tests verify that newly added public methods are embedded automatically and the source file is tracked for watch rebuilds.2026-08-30instead of the SDK'sunsetdefault. Unit and browser checks now pass, including explicit override precedence.6eeb5914b4a662adda5f88617d180b7b05c266baagainstorigin/main.caniuse-lite@1.0.30001819, published on October 22, because it is inside the seven-day minimum release age. No dependency policy or unrelated fixture was changed.Release info Sub-libraries affected
Libraries affected
Checklist
If releasing new changes
posthog-js.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Pi assisted with implementation and validation using its read, edit, write, and bash tools, Git, GitHub CLI, Vitest, Playwright, and isolated autoreview. The human-directed design uses a query parameter rather than adding token-specific routing and keeps the attribute form as a fallback. No public session link is available.