Open Graph images from web pages

Make 1200 x 630 social preview images of web pages with the Screenshot API: viewport, JPEG quality, page cleanup, storing your own copy, and a short checklist.

Updated

A link preview on a social network or in a chat app shows the page’s Open Graph image. This guide shows how to make such an image from a live page with one Screenshot API call, and how to keep it.

Capture at 1200 × 630

Set the viewport to 1200 × 630 and turn off the full-page capture. The image is then exactly one viewport.

curl -X POST https://api.wayfern.com/api/v1/screenshot \
  -H "Authorization: Bearer $WAYFERN_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/blog/launch",
    "width": 1200,
    "height": 630,
    "capture": {
      "fullPage": false,
      "format": "jpeg",
      "quality": 85,
      "blockAds": true,
      "hideSelectors": ["#chat-launcher"],
      "failIfContains": ["Page not found"]
    }
  }'

One capture costs 5 credits. The image is in data.screenshot, and the page’s own Open Graph tags are in data.metadata.openGraph, with keys such as title, description and image (without the og: prefix). Read data.metadata.openGraph.image to see whether the page already has a preview image.

The width and height are CSS pixels. Both can be from 320 to 3840.

Format and quality

  • jpeg makes much smaller files than png for photos and gradients. quality is 1 to 100, 85 by default.
  • png keeps sharp text and flat colors without artifacts. quality does not apply to it.

Check data.screenshot.bytes to see the size of the result.

Make the page look right

A preview image shows the top of the page, so remove what covers it:

  • Cookie banners are dismissed by default (blockCookieBanners).
  • blockAds removes ad slots and blocks ad requests.
  • hideSelectors hides chat buttons, promo bars and popups.
  • styles can enlarge the title or hide the navigation, only for the capture.
  • darkMode asks the page for its dark color scheme.
  • settleMs waits for web fonts and animations, 250 ms by default.

failIfContains stops a capture of an error page. The request then fails with content_matched, is not charged, and you keep your old image. The content check guide explains it.

Store your own copy

Do not put the returned url in an og:image tag. It is a signed link that stops working at urlExpiresAt, and the image itself is deleted 7 days after the capture.

Download the image while the link works, and serve it from your own storage:

import os

import requests

res = requests.post(
    "https://api.wayfern.com/api/v1/screenshot",
    headers={"Authorization": f"Bearer {os.environ['WAYFERN_API_TOKEN']}"},
    json={
        "url": "https://example.com/blog/launch",
        "width": 1200,
        "height": 630,
        "capture": {"fullPage": False, "format": "jpeg", "quality": 85},
    },
    timeout=180,
)
res.raise_for_status()
shot = res.json()["data"]["screenshot"]

with open("og-launch.jpg", "wb") as handle:
    handle.write(requests.get(shot["url"], timeout=60).content)

If the link expired before you downloaded it, send shot["key"] to POST /api/v1/artifacts/link for a new one. That call is free.

Caching

The Screenshot API has no cache. Every call loads the live page and costs 5 credits. The maxAge option of the Scrape API does not apply to renders either: a scrape with the screenshot format always loads the page.

Cache on your side. Keep the image you stored, and capture again only when the page changes, for example when you publish an update.

Checklist

  • width: 1200, height: 630, capture.fullPage: false.
  • format: "jpeg" for photos, png for text and flat colors.
  • Cookie banners, ads and overlays removed.
  • failIfContains with the text of your error page.
  • Image downloaded and served from your own storage, not from the signed link.
  • og:image points to that stored copy, with og:image:width 1200 and og:image:height 630.

To make images for many pages, see bulk screenshots. Every field is in the Screenshot API reference.