TryGhost/Ghost · error · Error

Unable to create Stripe billing portal session

Error message

Unable to create Stripe billing portal session

What it means

Thrown by manageBilling() when the POST to members 'create-stripe-billing-portal-session' returns non-2xx. This endpoint creates a Stripe Customer Portal session for the member to manage their subscription (cancel, swap tiers, view invoices). Like editBilling, this throw discards the backend error body and always uses the generic message.

Source

Thrown at apps/portal/src/utils/api.js:797

                const returnUrlObj = new URL(siteUrl);
                returnUrlObj.searchParams.set('stripe', 'billing-portal-closed');
                returnUrl = returnUrlObj.href;
            }

            return makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    identity: identity,
                    subscription_id: subscriptionId,
                    returnUrl
                })
            }).then(function (res) {
                if (!res.ok) {
                    throw new Error('Unable to create Stripe billing portal session');
                }
                return res.json();
            }).then(function (result) {
                return window.location.assign(result.url);
            }).catch(function (err) {
                throw err;
            });
        },

        async updateSubscription({subscriptionId, tierId, cadence, planId, smartCancel, cancelAtPeriodEnd, cancellationReason}) {
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'subscriptions'}) + subscriptionId + '/';
            const body = {
                smart_cancel: smartCancel,
                cancel_at_period_end: cancelAtPeriodEnd,
                cancellation_reason: cancellationReason,
                identity: identity,
                priceId: planId

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Ensure the Stripe Customer Portal is enabled and configured in the Stripe dashboard (a prerequisite Ghost can't set for you).
  2. Confirm the member has a real Stripe subscription (not a complimentary one).
  3. Check the Ghost server log for the create-stripe-billing-portal-session error reason.
  4. Re-authenticate the member and pass a valid subscriptionId.
  5. Patch manageBilling() to surface the backend res.json() error body for better triage.

Example fix

// before: backend reason discarded
if (!res.ok) {
    throw new Error('Unable to create Stripe billing portal session');
}

// after: surface the backend error
if (!res.ok) {
    const errData = await res.json().catch(() => null);
    throw new Error(errData?.errors?.[0]?.message || 'Unable to create Stripe billing portal session');
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the Stripe Customer Portal is configured before offering the link
// (this is a Stripe-dashboard prerequisite, not a Ghost setting)
// Then validate the subscription context:
function canManageBilling(identity, subscriptionId) {
    return Boolean(identity && subscriptionId);
}

Try / catch

try {
    await api.member.manageBilling({subscriptionId});
} catch (err) {
    // Generic message — server log has the reason (often 'portal not configured')
    showToast(err.message);
}

Prevention

When it happens

Trigger: Member opens the billing portal link; backend can't create a Customer Portal session — the member has no Stripe customer record, the subscriptionId doesn't exist or belongs to another member, Stripe Customer Portal isn't configured in the Stripe dashboard, or identity is stale.

Common situations: Stripe Customer Portal not enabled/configured in the Stripe account (admin must enable it in Stripe → Settings → Billing → Customer portal); member only has a complimentary (Ghost-managed) subscription with no Stripe customer; subscriptionId mismatch; Stripe Connect key issue.

Related errors


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/a84a186148116625. Report an issue: GitHub.