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

  1. Read data.error if present for the server-provided reason (e.g. 'No Stripe customer found')
  2. Only show the 'manage billing' / customer portal button for teams that have an active Stripe subscription
  3. Verify the Stripe Customer Portal is configured in the Stripe dashboard
  4. 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

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


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/b4986820f2bbe733. Report an issue: GitHub.