Stirling-Tools/Stirling-PDF · error · StripeFunctionError
quote PDF response was not a file
Error message
quote PDF response was not a file
What it means
Thrown by fetchBundleQuotePdf when the GET invocation resolves without error but the returned data is not a Blob. The route is expected to stream application/pdf; any other body shape (JSON error object, text, undefined) fails the instanceof Blob check.
Source
Thrown at frontend/editor/src/portal/billing/stripe.ts:481
*/
export async function fetchBundleQuotePdf(quoteId: number): Promise<Blob> {
ensureSaasSupabase();
const supabase = getSupabaseClient();
if (!supabase) {
throw new StripeFunctionError(
"SaaS Supabase not configured — set VITE_SUPABASE_URL.",
"unconfigured",
);
}
const { data, error } = await supabase.functions.invoke<Blob>(
`create-payg-bundle-quote?quote_id=${quoteId}`,
{ method: "GET" },
);
if (error) {
throw new StripeFunctionError(error.message ?? "quote PDF fetch failed");
}
if (!(data instanceof Blob)) {
throw new StripeFunctionError("quote PDF response was not a file");
}
return data;
}
/**
* {@code VITE_STRIPE_PUBLISHABLE_KEY} — the Stripe pk used by embedded Checkout. Coalesces to "" when
* unset so the declared `string` return type is honest (Vite substitutes `undefined` for a missing
* env var); callers guard with a falsy check.
*/
export function getStripePublishableKey(): string {
return import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY ?? "";
}
// Process-wide memoized Stripe.js loader, shared by the embedded-checkout modals so the SDK promise
// is created once rather than per-modal. loadStripe is dynamically imported so its chunk only loads
// when a checkout modal reaches its payment step.
let stripePromise: Promise<Stripe | null> | null = null;
export function loadStripeOnce(pk: string): Promise<Stripe | null> {View on GitHub (pinned to 9ef20dcab8)
Solutions
- Confirm the GET route always streams the PDF with Content-Type: application/pdf on success.
- On the function's error paths, return a proper HTTP error status (not 200 + JSON) so the invoke error branch (157) fires instead.
- Check the supabase-js version — Blob parsing depends on the response content-type.
- Log the actual data type/constructor in dev to identify what was returned.
Example fix
// edge function (GET route) — before
if (!pdf) return json({ error: 'not_found' }, { status: 200 });
// after — error paths use a non-2xx status so invoke rejects
if (!pdf) return json({ error: 'not_found' }, { status: 404 });
return new Response(pdf, { headers: { 'Content-Type': 'application/pdf' } }); Defensive patterns
Strategy: type-guard
Type guard
function isPdfBlob(data: unknown): data is Blob {
return typeof Blob !== "undefined" && data instanceof Blob && data.size > 0;
} Try / catch
try {
const data = await fetchBundleQuotePdf(quoteId); // throws if not a Blob
downloadBlob(data, `quote-${quoteId}.pdf`);
} catch (e) {
if (e instanceof StripeFunctionError && /not a file/i.test(e.message)) {
notifications.show({ message: "Quote PDF came back in the wrong format.", color: "red" });
} else throw e;
} Prevention
- Ensure the GET route streams the PDF with Content-Type: application/pdf on success.
- Return a non-2xx status (not 200 + JSON) on error paths so invoke rejects rather than returning a non-Blob.
- Verify the supabase-js version handles application/pdf as a Blob.
When it happens
Trigger: create-payg-bundle-quote GET returns successfully but the body is not a Blob — e.g. the function returned a JSON error object instead of streaming the PDF, or supabase-js parsed the response into a plain object because the content-type wasn't application/pdf.
Common situations: The function returned an error as JSON (e.g. { error: 'not found' }) with status 200; the route forgot to set Content-Type: application/pdf; a proxy re-wrapped the body; supabase-js version changed Blob handling for non-binary content-types.
Related errors
- Edge function ${name} returned no data
- create-checkout-session failed
- create-checkout-session returned neither client_secret nor U
- create-payg-bundle-quote failed
- accept-payg-bundle-quote failed
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/211f9090c96e619f.
Report an issue: GitHub.