Repository navigation
feat: read the project token from POSTHOG_PROJECT_TOKEN - #311
Conversation
The PHP SDK and the Laravel integration read the project token from POSTHOG_TOKEN, with POSTHOG_API_KEY kept as a fallback. The Laravel config key becomes `token`, and configs published with `api_key` keep working. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
PostHog Review · Standard · reviewed 3096d96: 2 Should fix.
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.
posthog-php-lib_curl Compliance ReportDate: 2026-11-10T14:50:57.400967+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 31ms |
| Format Validation.Event Has Uuid | ✅ | 527ms |
| Format Validation.Event Has Lib Properties | ✅ | 527ms |
| Format Validation.Distinct Id Is String | ✅ | 527ms |
| Format Validation.Token Is Present | ✅ | 527ms |
| Format Validation.Custom Properties Preserved | ✅ | 527ms |
| Format Validation.Event Has Timestamp | ✅ | 528ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 528ms |
| Retry Behavior.Retries On 503 | ✅ | 5836ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2529ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2529ms |
| Retry Behavior.Respects Retry After Header | ❌ | 6338ms |
| Retry Behavior.Implements Backoff | ✅ | 16251ms |
| Retry Behavior.Retries On 500 | ✅ | 5636ms |
| Retry Behavior.Retries On 502 | ✅ | 5636ms |
| Retry Behavior.Retries On 504 | ✅ | 5636ms |
| Retry Behavior.Max Retries Respected | ✅ | 17041ms |
| Deduplication.Generates Unique Uuids | ✅ | 243ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5635ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10843ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5639ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 534ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 529ms |
| Compression.Sends Gzip When Enabled | ✅ | 527ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 529ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 522ms |
| Batch Format.Multiple Events Batched Together | ✅ | 513ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2530ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2530ms |
| Error Handling.Retries On 408 | ✅ | 5634ms |
Failures
retry_behavior.respects_retry_after_header
Retry delay too short: 804ms < 2500ms
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 527ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 525ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 525ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 524ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 526ms |
| Request Payload.Groups Round Trip | ✅ | 525ms |
| Request Payload.Groups Default To Empty Object | ✅ | 524ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 524ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 524ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 524ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 519ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 511ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 529ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 525ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 627ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 628ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 527ms |
posthog-php-socket Compliance ReportDate: 2026-11-10T14:51:00.438364+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 30ms |
| Format Validation.Event Has Uuid | ✅ | 524ms |
| Format Validation.Event Has Lib Properties | ✅ | 526ms |
| Format Validation.Distinct Id Is String | ✅ | 526ms |
| Format Validation.Token Is Present | ✅ | 526ms |
| Format Validation.Custom Properties Preserved | ✅ | 525ms |
| Format Validation.Event Has Timestamp | ✅ | 526ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 526ms |
| Retry Behavior.Retries On 503 | ✅ | 5836ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2528ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2527ms |
| Retry Behavior.Respects Retry After Header | ❌ | 6335ms |
| Retry Behavior.Implements Backoff | ✅ | 16250ms |
| Retry Behavior.Retries On 500 | ✅ | 5635ms |
| Retry Behavior.Retries On 502 | ✅ | 5634ms |
| Retry Behavior.Retries On 504 | ✅ | 5633ms |
| Retry Behavior.Max Retries Respected | ✅ | 16248ms |
| Deduplication.Generates Unique Uuids | ✅ | 534ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5642ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10840ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5637ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 532ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 528ms |
| Compression.Sends Gzip When Enabled | ✅ | 526ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 525ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 522ms |
| Batch Format.Multiple Events Batched Together | ✅ | 512ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2528ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2527ms |
| Error Handling.Retries On 408 | ✅ | 5633ms |
Failures
retry_behavior.respects_retry_after_header
Retry delay too short: 803ms < 2500ms
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 525ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 524ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 523ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 523ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 523ms |
| Request Payload.Groups Round Trip | ✅ | 523ms |
| Request Payload.Groups Default To Empty Object | ✅ | 524ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 524ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 523ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 523ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 519ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 508ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 527ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 525ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 625ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 625ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 526ms |
posthog-php-fork_curl Compliance ReportDate: 2026-11-10T14:50:55.865054+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 26ms |
| Format Validation.Event Has Uuid | ✅ | 522ms |
| Format Validation.Event Has Lib Properties | ✅ | 524ms |
| Format Validation.Distinct Id Is String | ✅ | 522ms |
| Format Validation.Token Is Present | ✅ | 522ms |
| Format Validation.Custom Properties Preserved | ✅ | 524ms |
| Format Validation.Event Has Timestamp | ✅ | 524ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 522ms |
| Retry Behavior.Retries On 503 | ✅ | 5842ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2526ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2527ms |
| Retry Behavior.Respects Retry After Header | ❌ | 6336ms |
| Retry Behavior.Implements Backoff | ✅ | 16260ms |
| Retry Behavior.Retries On 500 | ✅ | 5635ms |
| Retry Behavior.Retries On 502 | ✅ | 5638ms |
| Retry Behavior.Retries On 504 | ✅ | 5638ms |
| Retry Behavior.Max Retries Respected | ✅ | 16258ms |
| Deduplication.Generates Unique Uuids | ✅ | 32ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5635ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10855ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5638ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 537ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 527ms |
| Compression.Sends Gzip When Enabled | ✅ | 529ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 526ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 517ms |
| Batch Format.Multiple Events Batched Together | ✅ | 518ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2529ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2530ms |
| Error Handling.Retries On 408 | ✅ | 5638ms |
Failures
retry_behavior.respects_retry_after_header
Retry delay too short: 808ms < 2500ms
Feature_Flags Tests
✅ 17/17 tests passed
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 522ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 517ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 518ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 519ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 519ms |
| Request Payload.Groups Round Trip | ✅ | 518ms |
| Request Payload.Groups Default To Empty Object | ✅ | 518ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 519ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ✅ | 518ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 517ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 515ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 513ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 520ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 520ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 621ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 620ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 526ms |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Matches the variable PostHog suggests for the project token in its other SDK setups. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
marandaneto
left a comment
There was a problem hiding this comment.
Two reproduced compatibility regressions and a separate configuration naming suggestion.
ioannisj
left a comment
There was a problem hiding this comment.
🤖 An AI agent wrote this on behalf of @ioannisj.
The two threads already marked blocking reproduce on 169d128: with POSTHOG_API_KEY exported, a runtime config(['posthog.api_key' => ...]) is ignored, and the example app boots disabled with only the old variable set. Past those, the phpcs job is red on test/PostHogTest.php, a config published before this release never reads POSTHOG_PROJECT_TOKEN, and I have one question on the spec's projectToken naming for init().
The Laravel config now carries `project_token` from POSTHOG_PROJECT_TOKEN and the deprecated `api_key` from POSTHOG_API_KEY as separate keys. ProjectToken reads `project_token` first and falls back to `api_key`, so runtime api_key overrides, legacy published configs and the example app keep working without the provider guard. Also clears the phpcs warnings in test/PostHogTest.php. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
marandaneto
left a comment
There was a problem hiding this comment.
The code looks good; no qualifying findings. Verdict: correct.
|
Follow-up: please check the PHP and Laravel code snippets across posthog.com and the PostHog app (including onboarding/setup examples) and update them where needed to use POSTHOG_PROJECT_TOKEN and the Laravel project_token config key. Keep examples aligned with the supported API signatures and note the legacy POSTHOG_API_KEY/api_key fallback where relevant. |
|
thanks for the pr @rafaeelaudibert |
|
Ping me when this merges to merge PostHog/context-mill#501 |
💡 Motivation and Context
PostHog calls the project's public key a project token across its SDKs and docs. The PHP SDK and the Laravel integration name it
POSTHOG_API_KEY, so.envfiles and setup guides for PHP apps use a different name from everything else.This makes
POSTHOG_PROJECT_TOKENthe public name:PostHog::init()with no token argumentPOSTHOG_API_KEYPOSTHOG_PROJECT_TOKEN, thenPOSTHOG_API_KEY'api_key' => env('POSTHOG_API_KEY')'project_token' => env('POSTHOG_PROJECT_TOKEN'), plus the deprecated'api_key' => env('POSTHOG_API_KEY')posthog.api_keyposthog.project_token, thenposthog.api_keyposthog:installandposthog:doctortext, queued delivery errorBackwards compatible, so a minor bump for both packages:
POSTHOG_API_KEYis still read whenPOSTHOG_PROJECT_TOKENis unset or blank.project_tokenandapi_keyare separate keys with no cross fallback in the config file, soproject_tokenonly ever carriesPOSTHOG_PROJECT_TOKEN. That keeps existing behavior:posthog.api_keyat runtime still takes effect whenPOSTHOG_PROJECT_TOKENis unset.api_keykey. They also pick upPOSTHOG_PROJECT_TOKENonce the app sets it, because the merged package default suppliesproject_token.POSTHOG_PROJECT_TOKEN(orproject_token) falls back toPOSTHOG_API_KEY(orapi_key).PostHog::ENV_API_KEYstays and is marked@deprecated. The newPostHog::ENV_PROJECT_TOKENis in the public API snapshot.Unchanged: the
$apiKeyparameter names ininit()andClient, because renaming them would break callers using named arguments. Internally the Laravel package reads the token through one@internalhelper,ProjectToken.The Laravel README, the Laravel example app, and the
init()docblocks use the new name.test/PostHogTest.phphad 23 phpcs warnings onmain, which fail the changed-files phpcs job once the file is touched. This PR wraps its long lines, gives its constant a visibility, and disablesPSR1.Files.SideEffectsfor it the way other files intest/already do.Divergence from the setup spec
sdk-specs setup asks SDKs to name the option
projectTokenand keepapiKeyas a deprecated alias:init()andnew Client()keep their$apiKeyparameter. They take the token as their first positional argument, which the spec exempts. PHP named arguments do expose$apiKey, so renaming it would breakinit(apiKey: …)callers. Adding a second, trailing$projectTokenparameter would make the call shape worse for no behavior gain.api_keyconfig key is deprecated with a comment in the published config and a README note, not a logged warning. A config key has no compile-time deprecation, and warning on every boot for a value the app may not control felt noisy. It's easy to add if we want it.💚 How did you test it?
Run in Docker (
composer:2, PHP 8.5):mainPOSTHOG_PROJECT_TOKENalone,POSTHOG_API_KEYalone, both (token wins), blank token falls backscripts/test-laravel.sh 12and13api_key(with and withoutPOSTHOG_PROJECT_TOKEN), runtimeapi_keyoverrides and clears, and the example app with onlyPOSTHOG_API_KEYcomposer api:check,composer api:check:laravelENV_PROJECT_TOKEN. The Laravel snapshot is unchangedphpcson changed core filesNot run: the PHP 8.2 to 8.4 matrix, which CI covers.
📝 Checklist
If releasing new changes
pnpm changeto generate a change intent fileThe intent bumps
posthog-php4.22.1 → 4.23.0 andposthog-laravel0.3.1 → 0.4.0.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
POSTHOG_PROJECT_TOKEN, the variable PostHog's setup examples for its other SDKs already use.$apiKeyparameters. PHP named arguments make that a breaking change.tokenkey filled from either variable. It let the environment override runtime and publishedapi_keyvalues, so it's now split intoproject_tokenandapi_key.POSTHOG_API_KEY.