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:
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
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
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;
}
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);
}
}
// 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
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!");
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
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"));
// 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);
}
Free tier includes 20 screenshots/day. Upgrade anytime for more.
Get Your Free API Key →You've learned how to:
Check out the other language tutorials: Node.js, Python, Go, PHP, Ruby.