feat(subscriptions): bring subscriptions to the free tier (freemium, synced to alerts)#59624
Draft
vdekrijger wants to merge 8 commits into
Draft
feat(subscriptions): bring subscriptions to the free tier (freemium, synced to alerts)#59624vdekrijger wants to merge 8 commits into
vdekrijger wants to merge 8 commits into
Conversation
…synced to alerts) Free orgs get 5 subscriptions (synced live to the alert free-tier limit), then the existing paywall on the next create; viewing/editing/deleting existing subscriptions is never gated; paid orgs stay unlimited. - Subscription.check_subscription_limit mirrors AlertConfiguration.check_alert_limit - POST-only enforcement in SubscriptionSerializer.validate - remove PremiumFeaturePermission from both subscription viewsets - frontend: subscriptionCountLogic + FreeTierCreateGate in EditSubscription - remove wholesale PayGateMini wrappers; unhide insight menu item
…uto-login + count/feature mocks)
…te gate The free-tier create gate rendered the generic PayGateMini 'Upgrade to use this feature', implying subscriptions are paid-only. They are freemium with a 5-subscription cap, so swap in UsageLimitPaywall with 'Subscription limit reached' framing showing the limit and current usage. Update the e2e to anchor on the new title instead of the PayGateMini learn-more testid.
Contributor
Author
This was referenced May 22, 2026
Contributor
|
Size Change: 0 B Total Size: 80 MB ℹ️ View Unchanged
|
Contributor
|
👋 Visual changes detected for this PR. Review and approve in PostHog Visual Review If these changes are unexpected, they may be caused by a flaky test or a broken snapshot on master. Don't approve — rerun the job or wait for a fix. |
This was referenced May 22, 2026
Draft
a817f23 to
6b8f5cb
Compare
Contributor
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
posthog/models/subscription.py:212-228
**Count-then-insert has no transaction boundary**
`existing_count` is read and then the caller inserts in a separate statement with no `SELECT FOR UPDATE` or atomic block. Two concurrent POSTs from a free-tier team sitting at `limit − 1` will both see a count below the cap and both succeed, leaving the team one subscription over the cap.
This is the same trade-off already present in `AlertConfiguration.check_alert_limit`, so it's consistent with existing behaviour. Worth noting that for a billing gate it means the hard limit can be exceeded by at most 1 per race window.
Reviews (1): Last reviewed commit: "refactor(subscriptions): single-source f..." | Re-trigger Greptile |
vdekrijger
commented
May 26, 2026
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 1 · PR risk: 0/10 |
…ed subscription The free-tier cap was only checked on create (POST), so a team could create up to the limit, soft-delete some, create more, then PATCH the soft-deleted rows back to deleted=false to exceed the cap. Run the cap check on the deleted->active restore transition too, with regression tests for the blocked and allowed cases. Also drop the test asserting the subscription limit equals the alerts free-tier limit (we don't want CI enforcing that the two stay in sync) and trim the SubscriptionFreeTierLimit schema doc comment accordingly (regenerated schema).
Contributor
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
posthog/models/subscription.py:27
**Subscription and alert free-tier limits are not actually in lockstep**
The PR description says the subscription limit reads from `AlertConfiguration.ALERTS_ALLOWED_ON_FREE_TIER` to stay synchronised. But the implementation defines `SUBSCRIPTION_COUNT_ALLOWED_ON_FREE_TIER` independently via `SubscriptionFreeTierLimit().root` (= 5). Meanwhile `AlertConfiguration.ALERTS_ALLOWED_ON_FREE_TIER = 5` lives separately in `products/alerts/backend/models/alert.py`. These are two independent constants: if the alert limit is changed to 10, the subscription limit silently stays at 5, violating the stated design intent and the Once-And-Only-Once principle.
To honour the lockstep goal, the subscription constant should derive from the existing alert constant (or vice-versa), not define the value a second time.
Reviews (2): Last reviewed commit: "fix(subscriptions): enforce free-tier ca..." | Re-trigger Greptile |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
Insight & dashboard subscriptions were a paid-plus feature, gated behind a premium permission. We want them available on the free tier — but capped, in lockstep with the alerts free-tier allowance — and unlimited once on a paid plan.
Changes
Subscription.check_subscription_limit, reading the allowance live from the alert free-tier constant (AlertConfiguration.ALERTS_ALLOWED_ON_FREE_TIER) so the two limits stay in lockstep. Orgs that have thesubscriptionsbilling feature use the billing-provided limit (unlimited on paid); free orgs fall back to the shared constant.UsageLimitPaywall("Subscription limit reached", showing the limit and current usage) when a free org is at the cap. Editing and viewing existing subscriptions is never gated — only adding an additional subscription past the cap. Fail-open when the count is unknown (still loading / fetch failed); the backend POST check is the hard limit.How did you test this code?
I'm an agent (Claude Code) — no manual testing claimed beyond the automated suites below, which I ran locally:
ee/api/test/test_subscription.py— free-tier limit, create/patch cap cases.frontend/.../EditSubscription.gate.test.tsx— boundary cases for the create gate.playwright/e2e/subscriptions-freemium.spec.ts— browser e2e: free org under the limit sees the create form; at the limit sees the upgrade paywall.After hitting the limit:
Before hitting the limit:
Publish to changelog?
Yes — subscriptions are now available on the free tier.
Docs update
Follow-up PRs (separate repos): billing (
subscriptionsin the free plan, mirroring the alerts pattern) and posthog.com (docs + pricing reflecting free-tier availability).🤖 Agent context
Authored with Claude Code (human-directed). Key decisions:
UsageLimitPaywallrather than the genericPayGateMini"Upgrade to use this feature" — subscriptions are a freemium feature with a cap, not a paid-only one, so the copy reflects "limit reached" with the live count.