DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

How to take screenshots and generate PDFs in TypeScript and Deno

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());
  }
}
Enter fullscreen mode Exit fullscreen mode

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)`);
Enter fullscreen mode Exit fullscreen mode
deno run --allow-net --allow-env --allow-write screenshot.ts
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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));
});
Enter fullscreen mode Exit fullscreen mode

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 });
  },
};
Enter fullscreen mode Exit fullscreen mode

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)