Need to turn a URL into a PNG or JPEG? Maybe you're generating thumbnails for a link directory, creating previews for a CMS, or building a portfolio of website designs. Whatever the use case, converting a website to an image programmatically used to mean spinning up a headless browser, managing dependencies, and debugging rendering issues.
Not anymore. A website-to-image API handles the hard parts: browser rendering, JavaScript execution, font loading, cookie banners, and output optimization. You send a URL, you get an image back.
This guide covers how to convert any website to an image using GrabShot's API, with working code in curl, Node.js, and Python.
Running Puppeteer or Playwright yourself works, but comes with overhead:
An API abstracts all of this. One HTTP request, one image back. The API provider handles the browsers, the rendering quirks, and the scaling.
The simplest possible call. No authentication needed for the free tier:
curl "https://grabshot.dev/api/screenshot?url=https://example.com&format=png" \
-o example.png
That's it. You now have a PNG of example.com saved locally. Let's look at what you can customize.
const fs = require('fs');
async function websiteToImage(url, outputPath) {
const params = new URLSearchParams({
url,
format: 'png',
width: '1280',
height: '800'
});
const response = await fetch(
`https://grabshot.dev/api/screenshot?${params}`,
{
headers: { 'x-api-key': process.env.GRABSHOT_API_KEY }
}
);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync(outputPath, buffer);
console.log(`Saved ${outputPath} (${buffer.length} bytes)`);
}
websiteToImage('https://github.com', 'github.png');
import requests
import os
def website_to_image(url: str, output_path: str, **options):
params = {
"url": url,
"format": options.get("format", "png"),
"width": options.get("width", 1280),
"height": options.get("height", 800),
}
headers = {"x-api-key": os.environ.get("GRABSHOT_API_KEY", "")}
resp = requests.get(
"https://grabshot.dev/api/screenshot",
params=params,
headers=headers,
)
resp.raise_for_status()
with open(output_path, "wb") as f:
f.write(resp.content)
print(f"Saved {output_path} ({len(resp.content):,} bytes)")
website_to_image("https://github.com", "github.png")
The format parameter controls the output type. Each has trade-offs:
| Format | Best For | File Size | Quality |
|---|---|---|---|
| PNG | UI screenshots, text-heavy pages, transparency | Larger | Lossless |
| JPEG | Photo-heavy pages, thumbnails, previews | Smaller | Adjustable (1-100) |
| WebP | Web display, modern browsers | Smallest | Adjustable |
For thumbnails and previews where file size matters, JPEG at quality 80 is a good default. For pixel-perfect captures where you need crisp text, use PNG.
# JPEG at 85% quality - good balance of size and clarity
curl "https://grabshot.dev/api/screenshot?url=https://example.com&format=jpeg&quality=85" \
-o example.jpg
# WebP for smallest file size
curl "https://grabshot.dev/api/screenshot?url=https://example.com&format=webp&quality=80" \
-o example.webp
By default, the API captures the visible viewport. To capture the entire scrollable page, set fullPage=true:
curl "https://grabshot.dev/api/screenshot?url=https://example.com&fullPage=true" \
-o full-page.png
Full-page captures are useful for archiving, documentation, and design reviews. Be aware that very long pages (10,000px+) will produce large files. Combine with JPEG format and a reasonable quality setting to keep file sizes manageable.
Control the browser viewport size and pixel density:
# Mobile viewport
curl "https://grabshot.dev/api/screenshot?url=https://example.com&width=390&height=844" \
-o mobile.png
# Tablet viewport
curl "https://grabshot.dev/api/screenshot?url=https://example.com&width=768&height=1024" \
-o tablet.png
# Retina (2x pixel density)
curl "https://grabshot.dev/api/screenshot?url=https://example.com&width=1280&height=800&deviceScaleFactor=2" \
-o retina.png
The deviceScaleFactor=2 parameter renders at 2x resolution, giving you a 2560x1600 image from a 1280x800 viewport. This is essential for retina displays and high-DPI marketing materials.
Modern websites load content asynchronously. SPAs, lazy-loaded images, and animations need time to render. The API provides several strategies:
# Wait for a specific element to appear
curl "https://grabshot.dev/api/screenshot?url=https://example.com&waitForSelector=.main-content" \
-o waited.png
# Wait a fixed delay (milliseconds)
curl "https://grabshot.dev/api/screenshot?url=https://example.com&delay=3000" \
-o delayed.png
The waitForSelector approach is more reliable than a fixed delay because it adapts to actual page load speed. Use CSS selectors that target the content you care about.
Build a bookmark manager or link aggregator that shows visual previews of saved URLs. Capture at a small viewport, compress as JPEG:
async function generateThumbnail(url) {
const params = new URLSearchParams({
url,
width: '640',
height: '400',
format: 'jpeg',
quality: '75'
});
const res = await fetch(
`https://grabshot.dev/api/screenshot?${params}`,
{ headers: { 'x-api-key': process.env.GRABSHOT_API_KEY } }
);
return Buffer.from(await res.arrayBuffer());
}
Capture dashboards or analytics pages on a schedule and email them to stakeholders:
import requests
import smtplib
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
def capture_dashboard():
resp = requests.get("https://grabshot.dev/api/screenshot", params={
"url": "https://your-app.com/dashboard",
"width": 1440,
"height": 900,
"format": "png",
"delay": 5000, # wait for charts to render
}, headers={
"x-api-key": "your-api-key"
})
return resp.content
# Capture and attach to email
img_data = capture_dashboard()
msg = MIMEMultipart()
msg.attach(MIMEImage(img_data, name="dashboard.png"))
If you run a directory of websites, tools, or templates, you need screenshots of every listed site. Batch processing makes this straightforward:
const urls = [
'https://stripe.com',
'https://linear.app',
'https://vercel.com',
'https://notion.so'
];
async function batchCapture(urls) {
const results = await Promise.allSettled(
urls.map(async (url) => {
const slug = new URL(url).hostname.replace(/\./g, '-');
const params = new URLSearchParams({
url,
width: '1280',
height: '800',
format: 'webp',
quality: '80'
});
const res = await fetch(
`https://grabshot.dev/api/screenshot?${params}`,
{ headers: { 'x-api-key': process.env.GRABSHOT_API_KEY } }
);
if (!res.ok) throw new Error(`${url}: ${res.status}`);
const buf = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync(`screenshots/${slug}.webp`, buf);
return { url, size: buf.length };
})
);
results.forEach((r, i) => {
if (r.status === 'fulfilled') {
console.log(`OK: ${r.value.url} (${r.value.size} bytes)`);
} else {
console.error(`FAIL: ${urls[i]} - ${r.reason.message}`);
}
});
}
batchCapture(urls);
25 free screenshots per month. No credit card required. API key in 30 seconds.
Try It Free →Some pages are trickier than others. Here are common issues and how to handle them:
| Parameter | Default | Description |
|---|---|---|
| url | required | The URL to capture |
| format | png | Output format: png, jpeg, webp |
| width | 1280 | Viewport width in pixels |
| height | 800 | Viewport height in pixels |
| quality | 80 | JPEG/WebP quality (1-100) |
| fullPage | false | Capture entire scrollable page |
| deviceScaleFactor | 1 | Pixel density (1, 2, or 3) |
| delay | 0 | Wait time in ms before capture |
| waitForSelector | - | CSS selector to wait for |
| colorScheme | light | light or dark |
For the full API reference with all parameters, check the documentation.
GrabShot offers a free tier with 25 screenshots per month, which is enough to test and prototype. Paid plans start at $9/month for 2,000 captures, with higher tiers for production workloads. See the pricing page for details.
Converting a website to an image is a solved problem. You don't need to manage Chromium instances, fight rendering bugs, or scale browser pools. A single API call gives you a high-quality image of any URL, in the format and resolution you need.
The code examples above are copy-paste ready. Grab a free API key and start capturing.