Stirling-Tools/Stirling-PDF · error · Error
data?.error ?? Portal session response missing url
Error message
data?.error ?? Portal session response missing url
What it means
Thrown by createPortalSession() in billing.ts when the create-customer-portal-session edge function returns data without a url field. The error message prioritizes data.error (a server-provided reason) and falls back to a generic string. The Stripe Customer Portal requires an existing Stripe customer, so this commonly fires when the team has never subscribed.
Source
Thrown at frontend/editor/src/saas/services/billing.ts:90
* Mint a Stripe Customer Portal session via the PAYG
* {@code create-customer-portal-session} edge function (its RPC enforces team
* membership). return_url is the current location so Stripe brings the user
* back to this page on close.
*/
export async function createPortalSession(
params: PortalParams,
): Promise<PortalSession> {
const { data, error } = await supabase.functions.invoke<{
url?: string;
error?: string;
}>("create-customer-portal-session", {
body: { team_id: params.teamId, return_url: window.location.href },
});
if (error) {
throw error;
}
if (!data?.url) {
throw new Error(data?.error ?? "Portal session response missing url");
}
return { url: data.url };
}
View on GitHub (pinned to 9ef20dcab8)
Solutions
- Read data.error if present for the server-provided reason (e.g. 'No Stripe customer found')
- Only show the 'manage billing' / customer portal button for teams that have an active Stripe subscription
- Verify the Stripe Customer Portal is configured in the Stripe dashboard
- Check the create-customer-portal-session edge function logs
Example fix
// before
if (!data?.url) {
throw new Error(data?.error ?? "Portal session response missing url");
}
// after (in the caller)
try {
const { url } = await createPortalSession({ teamId });
openExternal(url);
} catch (e) {
if (e.message.includes('No Stripe customer')) {
setShowSubscribePrompt(true); // guide user to subscribe first
}
} Defensive patterns
Strategy: validation
Validate before calling
// Only show the customer portal button if the team has a Stripe customer
if (!currentTeam?.hasStripeCustomer) {
setShowSubscribePromptInstead(true);
return;
} Try / catch
try {
const { url } = await createPortalSession({ teamId });
openExternal(url);
} catch (e) {
const msg = e instanceof Error ? e.message : '';
if (msg.includes('No Stripe customer') || msg.includes('missing url')) {
setPortalError('No billing account found. Please subscribe first.');
} else {
setPortalError(msg);
}
} Prevention
- Only offer the customer portal to teams that have an active Stripe subscription
- Verify the Stripe Customer Portal is configured in Stripe Dashboard > Settings > Billing > Customer Portal
- Check that the edge function has STRIPE_SECRET_KEY configured
- Surface data.error from the edge function to guide the user when available
When it happens
Trigger: The team_id has no associated Stripe customer record; the edge function encounters a Stripe API error; the Stripe customer portal is not configured in the Stripe dashboard; the edge function returns an error string in data.error.
Common situations: User clicks 'manage billing' before ever subscribing (no Stripe customer exists); Stripe Customer Portal not configured in Stripe Dashboard > Settings > Billing > Customer Portal; edge function STRIPE_SECRET_KEY missing; team_id not linked to a Stripe customer ID in the database.
Related errors
- create-customer-portal-session failed
- No pricing data returned
- serverMessage || error?.message || Failed to delete account
- Edge function returned no client_secret
- unconfigured
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/b4986820f2bbe733.
Report an issue: GitHub.