PDF generation in Supabase Edge Functions
Deno runtime, so the fetch is standard — but the natural next step is writing the PDF into Supabase Storage and handing back a signed URL, which is a different shape from returning bytes.
The code
import { createClient } from 'jsr:@supabase/supabase-js@2';
Deno.serve(async (req) => {
const { html, path } = await req.json();
const upstream = await fetch('https://api.pdfcraft.dev/v1/render', {
method: 'POST',
headers: {
authorization: `Bearer ${Deno.env.get('PDFCRAFT_API_KEY')}`,
'content-type': 'application/json',
},
body: JSON.stringify({ html, options: { printBackground: true } }),
});
if (!upstream.ok) {
return new Response(await upstream.text(), { status: upstream.status });
}
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
);
await supabase.storage
.from('documents')
.upload(path, await upstream.arrayBuffer(), { contentType: 'application/pdf', upsert: true });
const { data } = await supabase.storage.from('documents').createSignedUrl(path, 3600);
return Response.json({ url: data?.signedUrl });
});What to watch for
- Set the key with `supabase secrets set PDFCRAFT_API_KEY=…`. Anything in the function source is visible to anyone who can read the project.
- The service-role key bypasses RLS, so scope the bucket policy carefully — this function can write anywhere in it.
- For large batches, `output: "url"` returns a signed URL from us directly and skips the round trip through your function entirely.
Why not run the browser yourself
You can. Puppeteer and Playwright both work, and for a script you run by hand they are the right answer. The cost arrives at the point it becomes a service someone depends on: one long-lived browser per process rather than one per request, a semaphore so four renders do not become forty, a hard timeout that force-closes a hung page before it holds a slot forever, a scheduled restart because Chromium leaks, and a Chromium version you are now responsible for patching.
None of that is hard. All of it is work that has nothing to do with your product, and it fails under concurrency rather than in testing — which is the worst time to discover it.
Other frameworks
- PDF generation in Next.js
- PDF generation in Django
- PDF generation in Ruby on Rails
- PDF generation in Laravel
- PDF generation in AWS Lambda
- PDF generation in Cloudflare Workers
- PDF generation in Spring Boot
- PDF generation in FastAPI
- PDF generation in Express
- PDF generation in ASP.NET Core
- PDF generation in n8n