How link previews work
When you paste a URL into Slack, Discord, or Twitter, the platform fetches the page and reads its Open Graph meta tags (og:title, og:description, og:image) to build the preview card. We will build the same thing as a reusable API service.
Extract meta tags
Use /extract to pull og:title, og:description, og:image, and favicon from the rendered page.
Screenshot fallback
If no og:image exists, capture a screenshot of the page with /screenshot as the preview image.
Cache and serve
Store results in a cache. Serve previews from a simple API endpoint with sub-100ms response times.
Step 1: Extract meta tags (Python)
Use SnapRender's /extract endpoint to pull Open Graph tags from any URL. The key trick: use extract_attributes to get the content attribute from meta tags instead of the element's text content.
#E8A0BF">import requests
API_KEY = #A8D4A0">"sr_live_YOUR_KEY"
#E8A0BF">def get_link_preview(url):
# Step 1: Extract meta tags #E8A0BF">with /extract
resp = requests.#87CEEB">post(
#A8D4A0">"https://api.snaprender.dev/v1/extract",
headers={#A8D4A0">"x-api-key": API_KEY},
json={
#A8D4A0">"url": url,
#A8D4A0">"selectors": {
#A8D4A0">"title": #A8D4A0">"title",
#A8D4A0">"og_title": #A8D4A0">'meta[property=#A8D4A0">"og:title"]',
#A8D4A0">"og_desc": #A8D4A0">'meta[property=#A8D4A0">"og:description"]',
#A8D4A0">"og_image": #A8D4A0">'meta[property=#A8D4A0">"og:image"]',
#A8D4A0">"description": #A8D4A0">'meta[name=#A8D4A0">"description"]',
#A8D4A0">"favicon": #A8D4A0">'link[rel=#A8D4A0">"icon"]',
},
#A8D4A0">"extract_attributes": {
#A8D4A0">"og_title": #A8D4A0">"content",
#A8D4A0">"og_desc": #A8D4A0">"content",
#A8D4A0">"og_image": #A8D4A0">"content",
#A8D4A0">"description": #A8D4A0">"content",
#A8D4A0">"favicon": #A8D4A0">"href",
}
}
)
data = resp.#87CEEB">json().#E8A0BF">get(#A8D4A0">"data", {})
#E8A0BF">return {
#A8D4A0">"title": data.#E8A0BF">get(#A8D4A0">"og_title") #E8A0BF">or data.#E8A0BF">get(#A8D4A0">"title") #E8A0BF">or url,
#A8D4A0">"description": data.#E8A0BF">get(#A8D4A0">"og_desc") #E8A0BF">or data.#E8A0BF">get(#A8D4A0">"description") #E8A0BF">or #A8D4A0">"",
#A8D4A0">"image": data.#E8A0BF">get(#A8D4A0">"og_image") #E8A0BF">or #E8A0BF">None,
#A8D4A0">"favicon": data.#E8A0BF">get(#A8D4A0">"favicon") #E8A0BF">or #E8A0BF">None,
#A8D4A0">"url": url,
}
# Example
preview = get_link_preview(#A8D4A0">"https://github.com")
#E8A0BF">print(preview)Step 2: Screenshot fallback
Many pages lack an og:image. Generate a real screenshot as a fallback preview image. Use 1200x630 — the standard Open Graph image dimension.
#E8A0BF">def get_preview_with_screenshot(url):
# Get meta tags
preview = get_link_preview(url)
# If no og:image, generate a screenshot #E8A0BF">as fallback
#E8A0BF">if #E8A0BF">not preview[#A8D4A0">"image"]:
resp = requests.#87CEEB">post(
#A8D4A0">"https://api.snaprender.dev/v1/screenshot",
headers={#A8D4A0">"x-api-key": API_KEY},
json={
#A8D4A0">"url": url,
#A8D4A0">"width": 1200,
#A8D4A0">"height": 630,
#A8D4A0">"format": #A8D4A0">"jpeg",
#A8D4A0">"quality": 80,
}
)
#E8A0BF">if resp.#87CEEB">status_code == 200:
# Save screenshot #E8A0BF">and use #E8A0BF">as preview image
filename = url.split(#A8D4A0">"//")[1].replace(#A8D4A0">"/", #A8D4A0">"_") + #A8D4A0">".jpg"
#E8A0BF">with #E8A0BF">open(#A8D4A0">"previews/" + filename, #A8D4A0">"wb") #E8A0BF">as f:
f.#87CEEB">write(resp.#87CEEB">content)
preview[#A8D4A0">"image"] = #A8D4A0">"/previews/" + filename
#E8A0BF">return previewStep 3: Serve as an API (Python)
Wrap the preview logic in a simple Flask endpoint with in-memory caching.
#E8A0BF">from flask #E8A0BF">import Flask, jsonify, request
#E8A0BF">app = Flask(__name__)
# In-memory cache (use Redis #E8A0BF">in production)
cache = {}
@#E8A0BF">app.#E8A0BF">route(#A8D4A0">"/api/preview")
#E8A0BF">def preview():
url = request.args.#E8A0BF">get(#A8D4A0">"url")
#E8A0BF">if #E8A0BF">not url:
#E8A0BF">return jsonify({#A8D4A0">"error": #A8D4A0">"url parameter required"}), 400
# Check cache
#E8A0BF">if url #E8A0BF">in cache:
#E8A0BF">return jsonify(cache[url])
# Generate preview
data = get_preview_with_screenshot(url)
cache[url] = data
#E8A0BF">return jsonify(data)
# GET /api/preview?url=https://example.com
# Returns: { title, description, image, favicon, url }Node.js version
The same service in Node.js with Express. Same logic, same SnapRender endpoints.
#E8A0BF">const express = #E8A0BF">require(#A8D4A0">'express');
#E8A0BF">const #E8A0BF">app = express();
#E8A0BF">const cache = new Map();
#E8A0BF">async #E8A0BF">function getPreview(url) {
// Extract meta tags
#E8A0BF">const resp = #E8A0BF">await fetch(#A8D4A0">'https://api.snaprender.dev/v1/extract', {
method: #A8D4A0">'POST',
headers: {
#A8D4A0">'x-api-key': #A8D4A0">'sr_live_YOUR_KEY',
#A8D4A0">'Content-Type': #A8D4A0">'application/json',
},
body: JSON.#87CEEB">stringify({
url,
selectors: {
title: #A8D4A0">'title',
og_title: #A8D4A0">'meta[property=#A8D4A0">"og:title"]',
og_desc: #A8D4A0">'meta[property=#A8D4A0">"og:description"]',
og_image: #A8D4A0">'meta[property=#A8D4A0">"og:image"]',
},
extract_attributes: {
og_title: #A8D4A0">'content',
og_desc: #A8D4A0">'content',
og_image: #A8D4A0">'content',
},
}),
});
#E8A0BF">const { data } = #E8A0BF">await resp.#87CEEB">json();
#E8A0BF">return {
title: data.og_title || data.title || url,
description: data.og_desc || #A8D4A0">'',
image: data.og_image || #E8A0BF">null,
url,
};
}
#E8A0BF">app.#E8A0BF">get(#A8D4A0">'/api/preview', #E8A0BF">async (req, res) => {
#E8A0BF">const { url } = req.query;
#E8A0BF">if (!url) #E8A0BF">return res.status(400).#87CEEB">json({ error: #A8D4A0">'url required' });
#E8A0BF">if (cache.has(url)) #E8A0BF">return res.#87CEEB">json(cache.#E8A0BF">get(url));
#E8A0BF">const preview = #E8A0BF">await getPreview(url);
cache.set(url, preview);
res.#87CEEB">json(preview);
});
#E8A0BF">app.listen(3000);Build your link preview service
Get your API key in 30 seconds. 100 free requests/month to start building. No credit card required.
Get Your API KeyFrequently asked questions
A link preview is the rich card that appears when you paste a URL into Slack, Discord, Twitter, or iMessage. It typically shows the page title, description, and an image (og:image). These are generated from Open Graph and Twitter Card meta tags in the page's HTML.
Many modern sites render meta tags with JavaScript (React/Next.js apps, SPAs). A simple HTTP request only gets the raw HTML before JavaScript runs, which may not contain the og:title or og:image tags. SnapRender renders the full page first, ensuring you get the actual meta tags.
Use SnapRender's /screenshot endpoint to capture a screenshot of the page. This gives you a real visual of the page content, which is more accurate and up-to-date than relying on the site's og:image (which may be a generic logo or missing entirely).
Yes, and you should. Link preview data rarely changes, so cache the result for 24-48 hours. This saves API requests and speeds up your service. Store the title, description, image URL, and timestamp in your database.