TryGhost/Ghost · error · BadRequestError
Cannot disconnect Stripe whilst you have active subscription
Error message
Cannot disconnect Stripe whilst you have active subscriptions.
What it means
Thrown by the `disconnectStripeConnectIntegration` Admin API endpoint when paid members still exist. Before clearing the Stripe keys, Ghost counts members with `status:paid`; if the total is non-zero it refuses to disconnect to avoid orphaning active subscriptions. It is a deliberate data-integrity guard returning a 400 BadRequestError.
Source
Thrown at ghost/core/core/server/api/endpoints/settings.js:93
// We need to return all settings here, because we have calculated settings that might change
const browse = await settingsBREADService.browse(frame.options.context);
return browse;
}
},
disconnectStripeConnectIntegration: {
statusCode: 204,
headers: {
cacheInvalidate: false
},
permissions: {
method: 'edit'
},
async query(frame) {
const paidMembers = await membersService.api.memberBREADService.browse({limit: 0, filter: 'status:paid'});
if (_.get(paidMembers, 'meta.pagination.total') !== 0) {
throw new BadRequestError({
message: 'Cannot disconnect Stripe whilst you have active subscriptions.'
});
}
await stripeService.disconnect();
return models.Settings.edit([{
key: 'stripe_connect_publishable_key',
value: null
}, {
key: 'stripe_connect_secret_key',
value: null
}, {
key: 'stripe_connect_livemode',
value: null
}, {
key: 'stripe_connect_display_name',
value: nullView on GitHub (pinned to 47d8b0e2ad)
Solutions
- Cancel or downgrade all paid members first (so `status:paid` count is zero), then retry disconnect.
- Bulk-cancel subscriptions via the Admin API before disconnecting Stripe Connect.
- Verify no pending Stripe webhooks will recreate paid members between cleanup and disconnect.
- If this is a test environment, reset member subscriptions through the appropriate teardown flow.
Defensive patterns
Strategy: validation
Validate before calling
// Before disconnecting, confirm zero paid members
const paid = await api.members.browse({limit: 0, filter: 'status:paid'});
if (paid.meta.pagination.total !== 0) {
throw new Error(`Cannot disconnect: ${paid.meta.pagination.total} paid member(s) still active`);
} Try / catch
try {
await api.stripe.disconnect();
} catch (err) {
if (/active subscriptions/i.test((err as Error).response.body.errors[0].message)) {
// cancel subscriptions first, then retry
}
throw err;
} Prevention
- Cancel or downgrade all paid members before disconnecting Stripe.
- Verify no pending webhooks will recreate paid members between cleanup and disconnect.
- In test environments, use the proper teardown flow rather than manual disconnect.
When it happens
Trigger: An admin calls `DELETE /ghost/api/admin/stripe/connect/` (disconnect) while at least one member has `status:paid`. The endpoint browses paid members and aborts before `stripeService.disconnect()` and the key-clearing edit.
Common situations: Trying to reconfigure Stripe without first cancelling or downgrading paid members; leftover paid members from prior testing; a subscription webhook created a paid member after the admin believed all were removed; importing members with paid status.
Related errors
- A view with this name already exists
- Invalid donation amount: ${value}
- Failed to convert HTML to Lexical
- Failed to convert HTML to Lexical
- The NQL filter you passed was invalid.
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/79f9b0ee4bf15958.
Report an issue: GitHub.