How to Take Screenshots and Generate PDFs in TypeScript and Deno
Puppeteer and Playwright both have TypeScript bindings, but they both require a bundled Chromium download and don't run in Deno, Cloudflare Workers, or any edge runtime. If you're working in a TypeScript-first environment and need screenshots or PDFs, the headless browser path breaks quickly.
Here's the typed alternative: fetch-based, zero Chromium, full TypeScript inference.
Typed client (works in Node.js, Deno, and Bun)
interface ScreenshotOptions {
url: string;
fullPage?: boolean;
blockBanners?: boolean;
blockAds?: boolean;
darkMode?: boolean;
viewportDevice?: string;
}
interface PdfOptions {
url?: string;
html?: string;
blockBanners?: boolean;
}
class PageBoltClient {
private readonly apiKey: string;
private readonly baseUrl = "https://pagebolt.dev/api/v1";
constructor(apiKey: string) {
this.apiKey = apiKey;
}
async screenshot(options: ScreenshotOptions): Promise<Uint8Array> {
const res = await fetch(`${this.baseUrl}/screenshot`, {
method: "POST",
headers: {
"x-api-key": this.apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify(options),
});
if (!res.ok) throw new Error(`PageBolt error ${res.status}: ${await res.text()}`);
return new Uint8Array(await res.arrayBuffer());
}
async pdf(options: PdfOptions): Promise<Uint8Array> {
const res = await fetch(`${this.baseUrl}/pdf`, {
method: "POST",
headers: {
"x-api-key": this.apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify(options),
});
if (!res.ok) throw new Error(`PageBolt error ${res.status}: ${await res.text()}`);
return new Uint8Array(await res.arrayBuffer());
}
}
Deno — screenshot to file
// screenshot.ts
const pagebolt = new PageBoltClient(Deno.env.get("PAGEBOLT_API_KEY")!);
const image = await pagebolt.screenshot({
url: "https://example.com",
fullPage: true,
blockBanners: true,
});
await Deno.writeFile("screenshot.png", image);
console.log(`Screenshot saved (${image.length} bytes)`);
deno run --allow-net --allow-env --allow-write screenshot.ts
Deno — PDF from HTML template
// invoice.ts
import { renderInvoice } from "./templates/invoice.ts";
const pagebolt = new PageBoltClient(Deno.env.get("PAGEBOLT_API_KEY")!);
const html = renderInvoice({ id: "INV-001", total: 149.99 });
const pdfBytes = await pagebolt.pdf({ html });
await Deno.writeFile("invoice.pdf", pdfBytes);
Hono (TypeScript web framework — runs on Deno, Bun, Cloudflare Workers)
import { Hono } from "hono";
const app = new Hono();
const pagebolt = new PageBoltClient(Deno.env.get("PAGEBOLT_API_KEY")!);
app.get("/invoices/:id/pdf", async (c) => {
const id = c.req.param("id");
const html = await renderInvoiceHtml(id);
const pdfBytes = await pagebolt.pdf({ html });
return new Response(pdfBytes, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": `attachment; filename="invoice-${id}.pdf"`,
},
});
});
Deno.serve(app.fetch);
The same Hono handler runs unchanged on Cloudflare Workers — fetch() is native, no Node.js compatibility layer needed.
Node.js with ts-node
// Using Node 18+ native fetch
import * as fs from "fs/promises";
const pagebolt = new PageBoltClient(process.env.PAGEBOLT_API_KEY!);
const image = await pagebolt.screenshot({
url: "https://example.com",
fullPage: true,
blockBanners: true,
});
await fs.writeFile("screenshot.png", image);
Express + TypeScript controller
import express, { Request, Response } from "express";
const router = express.Router();
const pagebolt = new PageBoltClient(process.env.PAGEBOLT_API_KEY!);
router.get("/invoices/:id/pdf", async (req: Request, res: Response) => {
const { id } = req.params;
const html = await renderInvoiceHtml(id);
const pdfBytes = await pagebolt.pdf({ html });
res.setHeader("Content-Type", "application/pdf");
res.setHeader("Content-Disposition", `attachment; filename="invoice-${id}.pdf"`);
res.send(Buffer.from(pdfBytes));
});
Cloudflare Workers
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname.startsWith("/screenshot")) {
const target = url.searchParams.get("url");
if (!target) return new Response("Missing url param", { status: 400 });
const pagebolt = new PageBoltClient(env.PAGEBOLT_API_KEY);
const image = await pagebolt.screenshot({ url: target, blockBanners: true });
return new Response(image, {
headers: { "Content-Type": "image/png" },
});
}
return new Response("Not found", { status: 404 });
},
};
No npm install, no Chromium, no wrangler size limit issues from bundling a headless browser. The PageBoltClient class above is 30 lines and works identically in Deno, Bun, Node 18+, and Cloudflare Workers.
Try it free — 100 requests/month, no credit card. → Get started in 2 minutes
Top comments (0)