How to Build Link Previews with a Screenshot API
When you paste a URL into Slack, Discord, or Twitter, it automatically generates a rich preview with a title, description, and thumbnail image. This feature -- called link unfurling -- is one of the most common use cases for screenshot APIs. This guide shows you how to build your own link preview service from scratch.
What Is a Link Preview?
A link preview is a rich card that shows a visual representation of a URL. It typically includes:
- Title: The page's title or Open Graph title
- Description: A summary from the page's meta description
- Image: Either the page's Open Graph image or a screenshot thumbnail
- Domain: The website's domain name
The challenge is that many websites do not have Open Graph images, or their OG images are generic logos that do not represent the actual page content. A screenshot-based approach gives you a real visual of the page, which is more useful and engaging.
Architecture Overview
Here is the flow for a link preview service:
- User pastes a URL in your app
- Your backend receives the URL
- You fetch the page metadata (title, description) with a simple HTTP request
- You capture a screenshot thumbnail via a Screenshot API
- You cache both the metadata and thumbnail
- You return the assembled preview card to the frontend
Step 1: Capture the Screenshot Thumbnail
The core of link previews is the thumbnail image. Using our API, you can capture a screenshot of any URL with a single HTTP request:
Node.js (using fetch)
const API_KEY = 'sk_live_your_api_key';
const API_URL = 'https://screenshotapi-api-production.up.railway.app';
async function capturePreview(url) {
const params = new URLSearchParams({
url,
width: '1200',
height: '630', // OG image ratio (1.91:1)
format: 'jpeg',
quality: '80',
wait: '2000', // Wait for page to fully load
});
const response = await fetch(
`${API_URL}/v1/screenshot?${params}`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);
if (!response.ok) {
throw new Error(`Screenshot failed: ${response.status}`);
}
return response.arrayBuffer();
}Python (using requests)
import requests
API_KEY = 'sk_live_your_api_key'
API_URL = 'https://screenshotapi-api-production.up.railway.app'
def capture_preview(url):
response = requests.get(
f'{API_URL}/v1/screenshot',
params={
'url': url,
'width': 1200,
'height': 630,
'format': 'jpeg',
'quality': 80,
'wait': 2000,
},
headers={'Authorization': f'Bearer {API_KEY}'},
)
response.raise_for_status()
return response.contentStep 2: Fetch Page Metadata
To get the title and description, you can parse the HTML's meta tags. Here is a lightweight approach in Node.js:
async function fetchMetadata(url) {
const response = await fetch(url, {
headers: { 'User-Agent': 'LinkPreviewBot/1.0' },
signal: AbortSignal.timeout(5000),
});
const html = await response.text();
// Extract Open Graph or standard meta tags
const title = extractMeta(html, 'og:title')
|| extractTag(html, 'title');
const description = extractMeta(html, 'og:description')
|| extractMeta(html, 'description');
const domain = new URL(url).hostname;
return { title, description, domain };
}
function extractMeta(html, property) {
const match = html.match(
new RegExp(
`<meta[^>]*(?:property|name)=["']${property}["'][^>]*content=["']([^"']*)["']`,
'i'
)
);
return match ? match[1] : null;
}
function extractTag(html, tag) {
const match = html.match(
new RegExp(`<${tag}[^>]*>([^<]*)<\/${tag}>`, 'i')
);
return match ? match[1].trim() : null;
}Step 3: Build the Preview API Endpoint
Combine screenshot capture and metadata fetching into a single Express endpoint:
import express from 'express';
const app = express();
const cache = new Map(); // Simple in-memory cache
app.get('/api/preview', async (req, res) => {
const { url } = req.query;
if (!url) return res.status(400).json({ error: 'URL required' });
// Check cache first (1-hour TTL)
const cached = cache.get(url);
if (cached && Date.now() - cached.timestamp < 3600000) {
return res.json(cached.data);
}
try {
// Fetch metadata and screenshot in parallel
const [metadata, screenshot] = await Promise.all([
fetchMetadata(url),
capturePreview(url),
]);
// Convert screenshot to base64 for easy embedding
const thumbnailBase64 = Buffer.from(screenshot)
.toString('base64');
const preview = {
url,
title: metadata.title,
description: metadata.description,
domain: metadata.domain,
thumbnail: `data:image/jpeg;base64,${thumbnailBase64}`,
};
// Cache the result
cache.set(url, { data: preview, timestamp: Date.now() });
res.json(preview);
} catch (error) {
res.status(500).json({ error: 'Failed to generate preview' });
}
});
app.listen(3000);Step 4: Display the Preview Card
On the frontend, render the preview data as a card. Here is a simple React component:
function LinkPreview({ url, title, description, domain, thumbnail }) {
return (
<a href={url} className="link-preview-card">
<img src={thumbnail} alt={title} />
<div className="link-preview-content">
<span className="link-preview-domain">{domain}</span>
<h3>{title}</h3>
<p>{description}</p>
</div>
</a>
);
}Production Considerations
Caching Strategy
In production, replace the in-memory cache with Redis or a CDN. Screenshot thumbnails are static assets that rarely change -- cache them for 24 hours or more. This reduces API calls and speeds up repeat requests.
Image Storage
Instead of returning base64, store screenshots in S3, R2, or a CDN and return a URL. This reduces payload size from megabytes to bytes and allows the browser to cache the image separately.
Async Processing
For real-time chat applications, generate previews asynchronously. When a user pastes a URL, immediately show a loading skeleton. Trigger the preview generation in the background and push the result via WebSocket when ready.
Rate Limiting and Abuse Prevention
Validate URLs before capturing screenshots. Block requests to private/internal IPs (SSRF protection), limit URL length, and rate-limit requests per user. Our API includes built-in SSRF protection, so you do not need to implement it yourself.
Use Cases for Link Preview Screenshots
- Chat and messaging apps: Slack-style link unfurling for shared URLs
- Social media tools: Preview cards for scheduled posts (what the link will look like)
- Content aggregators: Visual directory of bookmarked links
- SEO tools: Visual audit of competitor landing pages
- Documentation: Embed live previews of referenced websites
- Email newsletters: Visual thumbnails for curated link roundups
Why Use a Screenshot API Instead of Open Graph Images?
| Feature | OG Images | Screenshot API |
|---|---|---|
| Shows actual page content | No | Yes |
| Works when OG tags are missing | No | Yes |
| Always up to date | Cached | Fresh |
| Consistent dimensions | Varies | Controlled |
| No infrastructure needed | Yes | Yes (API) |
Getting Started
Ready to build link previews? You can start with our free tier -- 100 screenshots per month, no credit card required.
- Create a free account
- Get your API key from the dashboard
- Try the interactive playground to test screenshots
- Copy the code examples above into your project
For production link preview services handling thousands of requests, our Pro plan at $29/month gives you 10,000 screenshots -- enough for most applications. See our pricing page for details.
Related Posts
Generate Website Thumbnails at Scale
Batch thumbnail generation for directories and catalogs.
Automate Screenshots with Node.js
Compare Puppeteer, Playwright, and API approaches.
Website Screenshot API Guide
Complete guide with code examples in Node.js, Python, and cURL.
Best Screenshot APIs Compared (2026)
Feature and pricing comparison of top screenshot APIs.
Build link previews in minutes
100 free screenshots per month. No credit card required.
Get Your Free API Key