Skip to content

🎨 Changed the upgrade destination on host limits to be configurable - #29843

Open
betschki wants to merge 4 commits into
TryGhost:mainfrom
magicpages:feat/configurable-limit-upgrade-url
Open

🎨 Changed the upgrade destination on host limits to be configurable#29843
betschki wants to merge 4 commits into
TryGhost:mainfrom
magicpages:feat/configurable-limit-upgrade-url

Conversation

@betschki

@betschki betschki commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Every limit dialogue sent people to Ghost(Pro)'s billing app at a hardcoded '/pro', so a managed host running its own billing had no way to redirect them. Adds hostSettings.billing.upgradeUrl, read through a shared helper alongside the existing upgradeUrl settings for the pre-launch and trial banners.

Each surface keeps its own fallback so Ghost(Pro) lands where it always has: the publish-limit modal still opens checkout, and the inline publish messages still point at the billing root.

hostSettings holds an href, so the React hook drops a leading '#' before handing it to updateRoute(), which reads that as an internal route.

Got some code for us? Awesome 🎊!

Please take a minute to explain the change you're making:

  • Why are you making it?
  • What does it do?
  • Why is this something Ghost users or developers need?

Please check your PR against these items:

  • I've read and followed the Contributor Guide
  • I've explained my change
  • I've written an automated test to prove my change works

We appreciate your contribution! 🙏

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e67f87c5-ba84-4b8b-a430-c1f2001d30e4

📥 Commits

Reviewing files that changed from the base of the PR and between 6ba9aab and 53d020c.

📒 Files selected for processing (11)
  • apps/admin/src/settings/app/components/settings/advanced/integrations.tsx
  • apps/admin/src/settings/app/components/settings/advanced/integrations/add-integration-modal.tsx
  • apps/admin/src/settings/app/components/settings/email/newsletters/add-newsletter-modal.tsx
  • apps/admin/src/settings/app/components/settings/email/newsletters/newsletter-detail-modal.tsx
  • apps/admin/src/settings/app/components/settings/general/user-detail-modal.tsx
  • apps/admin/src/settings/app/components/settings/membership/stripe/stripe-connect-modal.tsx
  • apps/admin/src/settings/app/components/settings/membership/tiers.tsx
  • apps/admin/src/settings/app/components/settings/site/change-theme.tsx
  • apps/admin/src/settings/app/components/settings/site/design-and-theme-modal.tsx
  • apps/admin/src/settings/app/components/settings/site/theme-modal.tsx
  • apps/admin/src/settings/app/components/settings/site/theme/advanced-theme-settings.tsx
🚧 Files skipped from review as they are similar to previous changes (11)
  • apps/admin/src/settings/app/components/settings/site/design-and-theme-modal.tsx
  • apps/admin/src/settings/app/components/settings/site/change-theme.tsx
  • apps/admin/src/settings/app/components/settings/site/theme-modal.tsx
  • apps/admin/src/settings/app/components/settings/general/user-detail-modal.tsx
  • apps/admin/src/settings/app/components/settings/membership/tiers.tsx
  • apps/admin/src/settings/app/components/settings/membership/stripe/stripe-connect-modal.tsx
  • apps/admin/src/settings/app/components/settings/site/theme/advanced-theme-settings.tsx
  • apps/admin/src/settings/app/components/settings/advanced/integrations/add-integration-modal.tsx
  • apps/admin/src/settings/app/components/settings/email/newsletters/newsletter-detail-modal.tsx
  • apps/admin/src/settings/app/components/settings/advanced/integrations.tsx
  • apps/admin/src/settings/app/components/settings/email/newsletters/add-newsletter-modal.tsx

Walkthrough

The PR adds billing upgrade URL configuration and shared route helpers. Admin analytics and settings limit flows now use configured upgrade destinations instead of fixed /pro routes. Ember publishing-limit errors and modals use validated and escaped configured URLs with fallback handling. Tests cover default, hash, relative, absolute, and rejected scheme cases.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: making host-limit upgrade destinations configurable.
Description check ✅ Passed The description explains the problem, implementation, fallbacks, validation, and test coverage for configurable upgrade destinations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/admin/src/settings/app/components/settings/membership/analytics.tsx (1)

72-78: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use a keyboard-operable control for “Upgrade now”.

The upgrade action at apps/admin/src/settings/app/components/settings/membership/analytics.tsx:77 is a clickable span with only onClick, so keyboard and assistive technology users cannot activate it. Use a <button> or <a>, still calling updateRoute({route: upgradeUrl, isExternal: true}) as needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/admin/src/settings/app/components/settings/membership/analytics.tsx`
around lines 72 - 78, The “Upgrade now” action inside the isWebAnalyticsLimited
membership notice is a clickable span that is not keyboard-operable. Replace
that span with an accessible button or link while preserving the existing
updateRoute call using upgradeUrl and isExternal: true.
apps/admin/src/settings/app/components/settings/advanced/integrations/add-integration-modal.tsx (1)

27-33: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep limit-modal callbacks synchronized with the resolved upgrade URL.

upgradeUrl comes from useGlobalData().config, and these effects miss it from their dependency arrays. Include upgradeUrl in each callback effect so a config refresh does not leave the onOk callback pointing at the old upgrade destination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/admin/src/settings/app/components/settings/advanced/integrations/add-integration-modal.tsx`
around lines 27 - 33, Keep the limit-modal callback effects synchronized with
the resolved upgradeUrl by adding upgradeUrl to the dependency arrays that
capture it. Update the affected effects in
apps/admin/src/settings/app/components/settings/advanced/integrations/add-integration-modal.tsx
lines 27-33,
apps/admin/src/settings/app/components/settings/email/newsletters/add-newsletter-modal.tsx
lines 80-86, and
apps/admin/src/settings/app/components/settings/membership/stripe/stripe-connect-modal.tsx
lines 280-291; no other callback behavior should change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/admin/src/settings/app/components/settings/site/design-and-theme-modal.tsx`:
- Around line 29-31: Update the useCallback dependency list for
showThemeLimitModal to include upgradeUrl alongside updateRoute, so the modal
callback refreshes whenever the configured billing upgrade route changes.

---

Outside diff comments:
In
`@apps/admin/src/settings/app/components/settings/advanced/integrations/add-integration-modal.tsx`:
- Around line 27-33: Keep the limit-modal callback effects synchronized with the
resolved upgradeUrl by adding upgradeUrl to the dependency arrays that capture
it. Update the affected effects in
apps/admin/src/settings/app/components/settings/advanced/integrations/add-integration-modal.tsx
lines 27-33,
apps/admin/src/settings/app/components/settings/email/newsletters/add-newsletter-modal.tsx
lines 80-86, and
apps/admin/src/settings/app/components/settings/membership/stripe/stripe-connect-modal.tsx
lines 280-291; no other callback behavior should change.

In `@apps/admin/src/settings/app/components/settings/membership/analytics.tsx`:
- Around line 72-78: The “Upgrade now” action inside the isWebAnalyticsLimited
membership notice is a clickable span that is not keyboard-operable. Replace
that span with an accessible button or link while preserving the existing
updateRoute call using upgradeUrl and isExternal: true.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4981e2b5-e230-487b-b141-cccedc7a92ed

📥 Commits

Reviewing files that changed from the base of the PR and between 32d22de and 326033b.

📒 Files selected for processing (20)
  • apps/admin-x-framework/src/api/config.ts
  • apps/admin/src/analytics/views/stats/overview/components/overview-kpis.tsx
  • apps/admin/src/settings/app/components/settings/advanced/integrations/add-integration-modal.tsx
  • apps/admin/src/settings/app/components/settings/email/newsletters/add-newsletter-modal.tsx
  • apps/admin/src/settings/app/components/settings/email/newsletters/newsletter-detail-modal.tsx
  • apps/admin/src/settings/app/components/settings/general/user-detail-modal.tsx
  • apps/admin/src/settings/app/components/settings/membership/analytics.tsx
  • apps/admin/src/settings/app/components/settings/membership/stripe/stripe-connect-modal.tsx
  • apps/admin/src/settings/app/components/settings/membership/tiers.tsx
  • apps/admin/src/settings/app/components/settings/site/change-theme.tsx
  • apps/admin/src/settings/app/components/settings/site/design-and-theme-modal.tsx
  • apps/admin/src/settings/app/components/settings/site/theme-modal.tsx
  • apps/admin/src/settings/app/components/settings/site/theme/advanced-theme-settings.tsx
  • apps/admin/src/settings/app/hooks/use-upgrade-url.test.tsx
  • apps/admin/src/settings/app/hooks/use-upgrade-url.ts
  • apps/ember-admin/app/components/editor/modals/publish-flow/confirm.js
  • apps/ember-admin/app/components/modals/limits/publish-limit.hbs
  • apps/ember-admin/app/components/modals/limits/publish-limit.js
  • apps/ember-admin/app/utils/publish-options.js
  • apps/ember-admin/app/utils/upgrade-url.js

Comment thread apps/admin/src/settings/app/components/settings/site/design-and-theme-modal.tsx Outdated
no ref

Every upgrade prompt on a host limit sent people to Ghost(Pro)'s billing app at a
hardcoded '/pro', so a managed host running its own billing had no way to
redirect them. Adds hostSettings.billing.upgradeUrl, resolved through
upgradeRoute() in admin-x-framework so both admin apps read it the same way
whichever config provider they have, and through a matching util in Ember, which
can't import it.

The setting is stored as an href to match the existing upgradeUrl settings for
the pre-launch and trial banners. Navigation takes a route, so React drops a
leading '#'; Ember uses the value as-is.

The publish-limit modal's Upgrade button became an anchor to carry an arbitrary
destination, and its default is now the billing root rather than opening checkout
directly.
@betschki
betschki force-pushed the feat/configurable-limit-upgrade-url branch from 326033b to b24283e Compare August 10, 2026 12:06

@9larsons 9larsons left a comment

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.

Thanks for this — the approach is right and the React side is in good shape. The config plumbing works with no server changes (hostSettings is passed through to the config endpoint wholesale), and routing overrides through navigateTo() means absolute URLs get scheme-validated for free. Nice tests on the hook.

Main blocker before merge: the publish-limit modal loses its direct-to-checkout behavior for Ghost(Pro) — details inline. Also a small escaping concern on the htmlSafe interpolations, plus a couple of nits.

Comment thread apps/ember-admin/app/components/modals/limits/publish-limit.hbs
Comment thread apps/ember-admin/app/utils/upgrade-url.js Outdated
Comment thread apps/ember-admin/app/components/editor/modals/publish-flow/confirm.js Outdated
Comment thread apps/admin/src/settings/app/hooks/use-upgrade-url.ts Outdated
Comment thread apps/ember-admin/app/components/editor/modals/publish-flow/confirm.js Outdated
no ref

- keeps the publish-limit modal going straight to checkout, via a per-surface
  fallback on the Ember util, with a test for it
- anchors a configured value that is neither a hash nor a URL, and rejects a
  scheme that isn't http(s), matching navigateTo() on the React side
- escapes the URL before interpolating it into the limit message markup
- renames the React hook to useUpgradeRoute, since it returns a route
@betschki

Copy link
Copy Markdown
Contributor Author

@9larsons thank you, all addressed :)

Copy link
Copy Markdown
Contributor

One host-limit upgrade surface is still bypassing the new setting:

apps/admin/src/settings/app/components/settings/advanced/integrations.tsx:59 still calls:

updateRoute({route: 'pro', isExternal: true});

Those Zapier and Transistor cards are disabled by hostSettings.limits.customIntegrations, so a managed host with billing.upgradeUrl configured is still sent to Ghost(Pro) from this CTA. Could this use useUpgradeRoute() as well? The existing acceptance test at apps/admin/src/settings/advanced/integrations.acceptance.test.tsx:281 currently asserts the hardcoded pro route and can be updated to cover the configured destination.

@9larsons 9larsons left a comment

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.

Sorry, found one more.

no ref

The Zapier and Transistor cards are disabled by the customIntegrations limit and
their CTA still routed to 'pro' directly. The route is resolved in
BuiltInIntegrations and passed down: reading it per item subscribed every card to
global data, which re-rendered the custom integration modals mid-edit.

Updates the acceptance test to the resolved default and adds one for a
configured destination.
…imit-upgrade-url

# Conflicts:
#	apps/admin/src/settings/app/components/settings/advanced/integrations/add-integration-modal.tsx
#	apps/admin/src/settings/app/components/settings/email/newsletters/add-newsletter-modal.tsx
#	apps/admin/src/settings/app/components/settings/membership/stripe/stripe-connect-modal.tsx
#	apps/admin/src/settings/app/components/settings/site/change-theme.tsx
#	apps/admin/src/settings/app/components/settings/site/design-and-theme-modal.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants