TypeScript Tutorial

How to Take Website Screenshots with TypeScript

Published April 4, 2026 · 5 min read · All tutorials

Taking website screenshots in TypeScript is easy with a REST API — no need to install Puppeteer, Playwright, or manage headless Chrome instances. This tutorial shows you how to capture screenshots and generate PDFs using the URLSnap API with full TypeScript type safety.

We'll cover:

Get a free API key

20 free requests/day. No credit card required.

Get Free API Key →

Prerequisites

You'll need:

Install TypeScript and a fetch-compatible HTTP client if needed:

npm install typescript tsx @types/node
# Node.js 18+ has native fetch — no extra packages needed

1. Quick Screenshot (Fetch API)

Here's the simplest way to capture a screenshot and save it to disk:

// screenshot.ts
import fs from "fs";

const API_KEY = process.env.URLSNAP_API_KEY!;
const targetUrl = "https://example.com";

async function takeScreenshot(url: string): Promise<void> {
  const endpoint = new URL("https://api.urlsnap.dev/api/screenshot");
  endpoint.searchParams.set("url", url);
  endpoint.searchParams.set("format", "png");
  endpoint.searchParams.set("full_page", "true");

  const response = await fetch(endpoint.toString(), {
    headers: { "X-Api-Key": API_KEY },
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(`Screenshot failed: ${err.error}`);
  }

  const buffer = Buffer.from(await response.arrayBuffer());
  fs.writeFileSync("screenshot.png", buffer);
  console.log("Saved screenshot.png");
}

takeScreenshot(targetUrl).catch(console.error);

Run it:

URLSNAP_API_KEY=your_key_here npx tsx screenshot.ts
Tip: Use a .env file with dotenv to keep your API key out of source code. Never commit secrets to git.

2. TypeScript Type Definitions

Define types for the API to get full autocomplete and safety:

// types.ts
export type ImageFormat = "png" | "jpeg";
export type PaperFormat = "A4" | "A3" | "Letter" | "Legal" | "Tabloid";

export interface ScreenshotOptions {
  url: string;
  width?: number;        // viewport width, default 1280
  height?: number;       // viewport height, default 800
  format?: ImageFormat;  // default "png"
  full_page?: boolean;   // capture full scrollable page
  delay?: number;        // ms to wait after load (max 5000)
}

export interface PdfOptions {
  url: string;
  format?: PaperFormat;  // default "A4"
  landscape?: boolean;
  margin?: string;       // e.g. "20px" or "1in"
}

export interface ApiErrorResponse {
  error: string;
}

3. Build a Typed URLSnap Client

Wrap the API in a reusable class with full TypeScript support:

// urlsnap.ts
import fs from "fs";
import { ScreenshotOptions, PdfOptions } from "./types";

const BASE = "https://api.urlsnap.dev";

export class URLSnap {
  private apiKey: string;

  constructor(apiKey: string) {
    if (!apiKey) throw new Error("URLSnap: API key is required");
    this.apiKey = apiKey;
  }

  private get headers(): Record<string, string> {
    return { "X-Api-Key": this.apiKey };
  }

  /** Capture a screenshot and return raw buffer */
  async screenshot(options: ScreenshotOptions): Promise<Buffer> {
    const url = new URL(`${BASE}/api/screenshot`);
    url.searchParams.set("url", options.url);
    if (options.width)     url.searchParams.set("width", String(options.width));
    if (options.height)    url.searchParams.set("height", String(options.height));
    if (options.format)    url.searchParams.set("format", options.format);
    if (options.full_page) url.searchParams.set("full_page", "true");
    if (options.delay)     url.searchParams.set("delay", String(options.delay));

    const res = await fetch(url.toString(), { headers: this.headers });
    if (!res.ok) {
      const body = await res.json() as { error: string };
      throw new Error(body.error);
    }
    return Buffer.from(await res.arrayBuffer());
  }

  /** Save a screenshot directly to a file */
  async screenshotToFile(options: ScreenshotOptions, outPath: string): Promise<void> {
    const buf = await this.screenshot(options);
    fs.writeFileSync(outPath, buf);
  }

  /** Generate a PDF and return raw buffer */
  async pdf(options: PdfOptions): Promise<Buffer> {
    const url = new URL(`${BASE}/api/pdf`);
    url.searchParams.set("url", options.url);
    if (options.format)    url.searchParams.set("format", options.format);
    if (options.landscape) url.searchParams.set("landscape", "true");
    if (options.margin)    url.searchParams.set("margin", options.margin);

    const res = await fetch(url.toString(), { headers: this.headers });
    if (!res.ok) {
      const body = await res.json() as { error: string };
      throw new Error(body.error);
    }
    return Buffer.from(await res.arrayBuffer());
  }

  /** Save a PDF directly to a file */
  async pdfToFile(options: PdfOptions, outPath: string): Promise<void> {
    const buf = await this.pdf(options);
    fs.writeFileSync(outPath, buf);
  }
}

4. Using the Client

// main.ts
import { URLSnap } from "./urlsnap";

const snap = new URLSnap(process.env.URLSNAP_API_KEY!);

async function main() {
  // Full-page screenshot as PNG
  await snap.screenshotToFile(
    { url: "https://github.com", full_page: true },
    "github.png"
  );
  console.log("Saved github.png");

  // A4 PDF of a documentation page
  await snap.pdfToFile(
    { url: "https://www.typescriptlang.org/docs/", format: "A4" },
    "typescript-docs.pdf"
  );
  console.log("Saved typescript-docs.pdf");

  // JPEG thumbnail at custom viewport
  await snap.screenshotToFile(
    { url: "https://stripe.com", width: 1440, height: 900, format: "jpeg" },
    "stripe-thumb.jpg"
  );
  console.log("Saved stripe-thumb.jpg");
}

main().catch(console.error);
URLSNAP_API_KEY=your_key_here npx tsx main.ts

5. Using with Bun

Bun has native TypeScript support — no compilation step needed:

bun run main.ts

Bun also has a built-in Bun.write() for file I/O, so you can simplify the save step:

// bun-screenshot.ts
const snap = new URLSnap(process.env.URLSNAP_API_KEY!);
const buf = await snap.screenshot({ url: "https://example.com" });
await Bun.write("output.png", buf);
console.log("Done!");

6. Using with Deno

Deno requires explicit permission flags and uses its own file API:

// deno-screenshot.ts
import { URLSnap } from "./urlsnap.ts";

const snap = new URLSnap(Deno.env.get("URLSNAP_API_KEY")!);
const buf = await snap.screenshot({ url: "https://example.com" });
await Deno.writeFile("output.png", buf);
console.log("Saved output.png");
deno run --allow-net --allow-env --allow-write deno-screenshot.ts

7. Express Endpoint Example

Serve screenshots on-demand from an Express API:

// server.ts
import express, { Request, Response } from "express";
import { URLSnap } from "./urlsnap";

const app = express();
const snap = new URLSnap(process.env.URLSNAP_API_KEY!);

app.get("/screenshot", async (req: Request, res: Response) => {
  const { url } = req.query;
  if (typeof url !== "string") {
    return res.status(400).json({ error: "url query param required" });
  }
  try {
    const buf = await snap.screenshot({ url, full_page: true });
    res.set("Content-Type", "image/png");
    res.send(buf);
  } catch (err: unknown) {
    const msg = err instanceof Error ? err.message : "Unknown error";
    res.status(500).json({ error: msg });
  }
});

app.listen(3000, () => console.log("Server running on http://localhost:3000"));
Rate limit headers: The API returns X-Rate-Limit-Remaining and X-Daily-Limit on every response so you can track usage programmatically.

8. Handling Errors Gracefully

// With discriminated union for typed error handling
type SnapResult<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };

async function safeScreenshot(url: string): Promise<SnapResult<Buffer>> {
  try {
    const data = await snap.screenshot({ url });
    return { ok: true, data };
  } catch (err) {
    return { ok: false, error: err instanceof Error ? err.message : "Unknown" };
  }
}

const result = await safeScreenshot("https://example.com");
if (result.ok) {
  fs.writeFileSync("output.png", result.data);
} else {
  console.error("Failed:", result.error);
}

Why use an API instead of Puppeteer?

Ready to start?

Free tier includes 20 screenshots/day. Upgrade anytime for more.

Get Your Free API Key →

Summary

You've learned how to:

Check out the other language tutorials: Node.js, Python, Go, PHP, Ruby.