How to Take Screenshots in TypeScript with the PageBolt API
You're building a TypeScript app that needs screenshots. You have a few options:
- Self-host Puppeteer — manage browser processes, handle crashes, scale in production
- Use a screenshot API — one endpoint, type-safe, no infrastructure
If you've chosen option 2, here's how to integrate PageBolt with full TypeScript support.
The Basic Pattern: Type-Safe API Calls
import * as fs from 'fs';
// Define your request shape
interface ScreenshotRequest {
url: string;
format: 'png' | 'jpeg' | 'webp';
width?: number;
height?: number;
fullPage?: boolean;
blockBanners?: boolean;
}
// Define the API response
interface ScreenshotResponse {
success: boolean;
message?: string;
}
// Define possible errors
type ScreenshotError =
| { code: 'INVALID_URL'; message: string }
| { code: 'API_ERROR'; status: number; message: string }
| { code: 'FILE_WRITE_ERROR'; message: string };
async function takeScreenshot(
request: ScreenshotRequest,
outputPath: string,
apiKey: string
): Promise<ScreenshotResponse | ScreenshotError> {
try {
const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: request.url,
format: request.format,
width: request.width || 1280,
height: request.height || 720,
fullPage: request.fullPage ?? false,
blockBanners: request.blockBanners ?? true
})
});
if (!response.ok) {
return {
code: 'API_ERROR',
status: response.status,
message: `Screenshot API returned ${response.status}`
};
}
// Get binary response as ArrayBuffer
const buffer = await response.arrayBuffer();
// Write to disk using Buffer
fs.writeFileSync(outputPath, Buffer.from(buffer));
return { success: true };
} catch (error) {
if (error instanceof Error) {
return {
code: 'FILE_WRITE_ERROR',
message: error.message
};
}
throw error;
}
}
// Usage
const result = await takeScreenshot(
{
url: 'https://example.com',
format: 'png',
fullPage: true,
blockBanners: true
},
'./screenshot.png',
'YOUR_API_KEY'
);
if (result.success) {
console.log('Screenshot saved');
} else {
console.error(`Error: ${result.message}`);
}
What this does:
- Defines strict types for request, response, and errors
- Uses
response.arrayBuffer()to handle binary PNG data - Writes to disk using
Buffer.from() - Returns typed error objects instead of throwing
Production Pattern: Class-Based API Client
For larger projects, wrap the API in a client class:
import * as fs from 'fs';
import * as path from 'path';
interface ScreenshotOptions {
url: string;
format?: 'png' | 'jpeg' | 'webp';
width?: number;
height?: number;
fullPage?: boolean;
blockBanners?: boolean;
blockAds?: boolean;
darkMode?: boolean;
}
class PageBoltClient {
private apiKey: string;
private apiBase = 'https://api.pagebolt.dev/v1';
constructor(apiKey: string) {
this.apiKey = apiKey;
}
async screenshot(
options: ScreenshotOptions,
outputPath?: string
): Promise<{ data: Buffer; path?: string }> {
const payload = {
url: options.url,
format: options.format || 'png',
width: options.width || 1280,
height: options.height || 720,
fullPage: options.fullPage ?? false,
blockBanners: options.blockBanners ?? true,
blockAds: options.blockAds ?? false,
darkMode: options.darkMode ?? false
};
const response = await fetch(`${this.apiBase}/screenshot`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(
`PageBolt API error: ${response.status} ${response.statusText}`
);
}
const buffer = Buffer.from(await response.arrayBuffer());
if (outputPath) {
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, buffer);
return { data: buffer, path: outputPath };
}
return { data: buffer };
}
async batch(
urls: string[],
outputDir: string,
options?: Partial<ScreenshotOptions>
): Promise<{ path: string; url: string }[]> {
const results: { path: string; url: string }[] = [];
for (const url of urls) {
const filename = `${Date.now()}-${Math.random().toString(36).slice(2)}.png`;
const filepath = path.join(outputDir, filename);
const result = await this.screenshot(
{ ...options, url },
filepath
);
results.push({
url,
path: result.path || filepath
});
}
return results;
}
}
// Usage
const client = new PageBoltClient(process.env.PAGEBOLT_API_KEY!);
// Single screenshot
const { data, path: filePath } = await client.screenshot(
{
url: 'https://example.com',
fullPage: true
},
'./screenshots/homepage.png'
);
console.log(`Screenshot saved to ${filePath}`);
// Batch screenshots
const urls = [
'https://example.com',
'https://example.com/about',
'https://example.com/pricing'
];
const results = await client.batch(urls, './screenshots');
console.log(`Batch complete: ${results.length} screenshots`);
Node.js Integration: Screenshot on Route
Use PageBolt in an Express server:
import express, { Request, Response } from 'express';
import * as fs from 'fs';
import * as path from 'path';
const app = express();
const apiKey = process.env.PAGEBOLT_API_KEY!;
interface ScreenshotBody {
url: string;
format?: 'png' | 'jpeg' | 'webp';
fullPage?: boolean;
}
app.post('/api/screenshot', async (req: Request<{}, {}, ScreenshotBody>, res: Response) => {
const { url, format = 'png', fullPage = true } = req.body;
if (!url) {
return res.status(400).json({ error: 'url is required' });
}
try {
const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url,
format,
fullPage,
width: 1280,
height: 720,
blockBanners: true
})
});
if (!response.ok) {
return res.status(response.status).json({
error: `Screenshot API error: ${response.statusText}`
});
}
const buffer = Buffer.from(await response.arrayBuffer());
// Return image directly
res.setHeader('Content-Type', `image/${format}`);
res.setHeader('Content-Length', buffer.length);
res.send(buffer);
} catch (error) {
console.error('Screenshot error:', error);
res.status(500).json({
error: error instanceof Error ? error.message : 'Unknown error'
});
}
});
app.listen(3000, () => {
console.log('Server listening on :3000');
console.log('POST /api/screenshot with { url, format?, fullPage? }');
});
Test it:
curl -X POST http://localhost:3000/api/screenshot \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","fullPage":true}' \
> example.png
Deno Integration: Modern Async TypeScript
Deno handles typed fetch natively. No Buffer needed — Deno has Uint8Array:
// deno.json
{
"imports": {
"std/": "https://deno.land/std@0.208.0/"
}
}
// screenshot.ts (Deno)
interface ScreenshotOptions {
url: string;
format?: 'png' | 'jpeg' | 'webp';
width?: number;
height?: number;
fullPage?: boolean;
blockBanners?: boolean;
}
async function takeScreenshot(
options: ScreenshotOptions,
outputPath: string,
apiKey: string
): Promise<void> {
const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: options.url,
format: options.format || 'png',
width: options.width || 1280,
height: options.height || 720,
fullPage: options.fullPage ?? false,
blockBanners: options.blockBanners ?? true
})
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
// Deno: use arrayBuffer() then write
const buffer = await response.arrayBuffer();
await Deno.writeFile(outputPath, new Uint8Array(buffer));
console.log(`Screenshot saved to ${outputPath}`);
}
// Usage
const apiKey = Deno.env.get('PAGEBOLT_API_KEY') || '';
await takeScreenshot(
{
url: 'https://example.com',
fullPage: true,
blockBanners: true
},
'./screenshot.png',
apiKey
);
Run it:
export PAGEBOLT_API_KEY=your_key_here
deno run --allow-net --allow-write screenshot.ts
Error Handling Patterns
Distinguish between recoverable and fatal errors:
type ApiErrorCode =
| 'INVALID_URL'
| 'TIMEOUT'
| 'INVALID_FORMAT'
| 'RATE_LIMIT'
| 'API_ERROR';
class ApiError extends Error {
constructor(
public code: ApiErrorCode,
public statusCode?: number,
message?: string
) {
super(message);
this.name = 'ApiError';
}
}
async function takeScreenshotWithRetry(
url: string,
apiKey: string,
maxRetries = 3
): Promise<Buffer> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ url, format: 'png' })
});
if (response.status === 429) {
// Rate limit: wait and retry
if (attempt < maxRetries) {
const waitMs = 1000 * Math.pow(2, attempt - 1);
console.log(`Rate limited. Retrying in ${waitMs}ms...`);
await new Promise(resolve => setTimeout(resolve, waitMs));
continue;
}
throw new ApiError('RATE_LIMIT', 429);
}
if (!response.ok) {
throw new ApiError('API_ERROR', response.status);
}
return Buffer.from(await response.arrayBuffer());
} catch (error) {
if (error instanceof ApiError && error.code === 'RATE_LIMIT') {
if (attempt === maxRetries) throw error;
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
Real Use Case: Marketing Email Screenshots
Generate screenshots for email campaigns:
import { PageBoltClient } from './pagebolt-client';
interface EmailVariant {
name: string;
url: string;
}
async function generateEmailProofs(
variants: EmailVariant[],
outputDir: string
): Promise<void> {
const client = new PageBoltClient(process.env.PAGEBOLT_API_KEY!);
console.log(`Generating ${variants.length} screenshots...`);
for (const variant of variants) {
const outputPath = `${outputDir}/${variant.name}.png`;
await client.screenshot(
{
url: variant.url,
width: 600, // Email width
fullPage: true,
blockBanners: true,
blockAds: true
},
outputPath
);
console.log(`✓ ${variant.name}`);
}
console.log(`All screenshots saved to ${outputDir}`);
}
// Usage
await generateEmailProofs(
[
{ name: 'variant-a', url: 'https://example.com?variant=a' },
{ name: 'variant-b', url: 'https://example.com?variant=b' },
{ name: 'variant-c', url: 'https://example.com?variant=c' }
],
'./email-proofs'
);
Pricing
| Plan | Requests/Month | Cost | Best For |
|---|---|---|---|
| Free | 100 | $0 | Learning, low-volume projects |
| Starter | 5,000 | $29 | Small teams, moderate use |
| Growth | 25,000 | $79 | Production apps, frequent calls |
| Scale | 100,000 | $199 | High-volume automation |
Summary
- ✅ Full TypeScript support with proper types
- ✅
response.arrayBuffer()for binary PNG handling - ✅
Buffer.from()for Node.js,Uint8Arrayfor Deno - ✅ Error handling with typed error codes
- ✅ Works in Node.js, Deno, and Express
- ✅ Batch processing for multiple URLs
- ✅ No browser management overhead
Get started free: pagebolt.dev — 100 requests/month, no credit card required.
Top comments (0)