Generate Social Media Preview Images from HTML with Playwright
Build a fixed-size social card in HTML, capture it with Playwright, and publish it with Open Graph metadata. Includes runnable Node.js code and troubleshooting.
To generate a social media preview image from HTML with Playwright, create a dedicated card component, render it at a fixed viewport, capture the card as an image, publish that image at a stable public URL, and point your page’s Open Graph metadata to it. Playwright creates the image pixels; Open Graph metadata tells sharing crawlers which page and image belong together.
The example below uses Node.js and Chromium. It captures a 1200 × 627 CSS-pixel card as a PNG. That shape meets LinkedIn’s documented 1200 × 627 minimum for its sharing module, but it is not a universal requirement for every platform. Check the destination platform’s current documentation before choosing dimensions. [LinkedIn sharing module image requirements]
1. Create a fixed-size HTML social card
Make the card its own composition instead of screenshotting your entire site. A dedicated element is easier to size, test, and capture consistently. Keep the text and imagery inside a fixed canvas, and make sure the layout still works with the longest title or largest author name you expect.
For example, create social-card.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Social card</title>
<style>
* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; height: 100%; }
body {
display: grid;
place-items: center;
background: #eef2ff;
font-family: Inter, Arial, sans-serif;
}
[data-social-card] {
width: 1200px;
height: 627px;
padding: 64px 72px;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
color: #fff;
background: linear-gradient(135deg, #172554, #4338ca 65%, #7c3aed);
}
.eyebrow { font-size: 22px; letter-spacing: .08em; text-transform: uppercase; }
h1 { max-width: 1000px; margin: 24px 0; font-size: 68px; line-height: 1.05; }
.site { font-size: 25px; opacity: .85; }
</style>
</head>
<body>
<article data-social-card aria-label="Article social preview">
<div class="eyebrow">Engineering guide</div>
<h1>Generate social preview images from HTML</h1>
<div class="site">example.com</div>
</article>
</body>
</html>
In a real app, render the title, category, brand colors, and any image from the same data source as the page. Escape or safely encode user-provided values when inserting them into HTML. If the image includes a remote font or other external asset, ensure it can be loaded in the rendering environment.
2. Install Playwright and capture the card
Install Playwright in your project and install its Chromium browser. Commands can vary slightly by package manager and project setup; the following uses npm:
npm install -D playwright
npx playwright install chromium
Save this as generate-social-card.js. It opens the local HTML file, waits for fonts, checks that the target exists, and saves the element screenshot:
const { chromium } = require('playwright');
const path = require('node:path');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage({
viewport: { width: 1200, height: 627 },
deviceScaleFactor: 1,
});
const fileUrl = `file://${path.resolve('social-card.html')}`;
await page.goto(fileUrl, { waitUntil: 'load' });
await page.evaluate(() => document.fonts.ready);
const card = page.locator('[data-social-card]');
await card.waitFor({ state: 'visible' });
await card.screenshot({
path: 'social-preview.png',
type: 'png',
scale: 'css',
animations: 'disabled',
});
} finally {
await browser.close();
}
})();
Run it with node generate-social-card.js. The output path is relative to the process’s current working directory. Create the destination directory first if you change the path to a nested location. Playwright’s screenshot methods return image bytes; supplying path writes them directly to a file. See the Playwright screenshot API for options supported by your installed version.
Wait for the state that matters
A successful navigation does not guarantee that every asynchronous application request or remote image has finished. If the card is rendered by an app route, wait for a page-specific ready condition, such as a selector or a data attribute set after content loads. For local static HTML, waiting for load and fonts is often sufficient, provided all assets are available. A network-idle wait can help for some pages, but pages with ongoing requests may never become idle, so prefer an explicit readiness signal when you control the page.
3. Choose the capture target and image settings
| Choice | Use it when | Notes |
|---|---|---|
| Element screenshot | The card is a dedicated DOM element. | Usually the clearest choice: the element defines the composition and bounds. |
| Viewport screenshot | The page itself is exactly the card canvas. | Use page.screenshot(); keep the viewport at the intended dimensions. |
| Clip rectangle | The card occupies a known rectangle in a larger page. | Set clip: { x, y, width, height } on a page screenshot. Coordinates are in CSS pixels. |
| Full page | You need an image of the entire long page. | Usually wrong for a share card: it captures a tall page rather than a fixed composition. |
Playwright supports screenshot type, clipping, full-page capture, CSS or device scale, masking, style overrides, and animation control. Review the API documentation for exact option behavior and version compatibility. [Playwright page screenshot options]
- Scale:
scale: 'css'produces one output pixel per CSS pixel.scale: 'device'uses device scale and creates more pixels when the device scale factor is above one. That may help detail, but increases file size and does not change the CSS layout dimensions. - Format: PNG is the default and preserves sharp text without lossy compression; it can also preserve transparency where the page background permits it. JPEG and WebP are options for smaller compressed files. Quality applies to JPEG and WebP, not PNG. Confirm that the platform accepts the chosen format.
- Animation and dynamic elements:
animations: 'disabled'helps avoid capturing a transition partway through. A screenshot stylesheet can hide timestamps, cursors, or other unstable details. Avoid masking content that should appear in the shared image. - Exact dimensions: Set the CSS card width and height explicitly, and use CSS scale if the intended output dimensions are CSS pixels. Check the resulting file dimensions as part of your publishing pipeline.
4. Publish the image and add Open Graph metadata
Put the generated image somewhere the sharing crawler can request directly, such as a publicly accessible asset path on your site. Use an absolute HTTPS image URL in the page head. The URL must remain valid when the page is shared; a local file path or private development URL is not suitable.
<meta property="og:title" content="Generate social preview images from HTML" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://example.com/guides/social-preview" />
<meta property="og:image" content="https://example.com/social/example.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="627" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:alt" content="A purple article card about generating social preview images" />
The Open Graph Protocol defines og:title, og:type, og:image, and og:url as the basic properties. It also defines structured image properties, including width, height, type, secure URL, and alt text. The alt value describes the image; it is not a caption. If a page sets og:image, the protocol recommends specifying og:image:alt. Put the intended og:image first if listing multiple candidates: when properties have multiple values, the first in document order is preferred in a conflict. [Open Graph Protocol]
Metadata and pixels are separate jobs: changing the card image does not automatically update the HTML metadata, and setting og:image does not generate the image. Regenerate and publish the asset, then update the page head when the card changes. Platform-specific preview refresh behavior is not uniform, so consult that platform’s documentation if a shared preview appears stale.
5. Automate generation for many pages
For a site with many pages, build one card route that accepts known page data and capture each route as part of content publishing. Derive both the page’s Open Graph tags and card content from the same record to avoid title or URL mismatches.
- Load a card route with a stable content identifier.
- Wait for a ready marker that appears only after the card data and fonts are available.
- Capture the card element to a deterministic file path, such as
public/social/<slug>.png. - Publish the file and the page metadata together, or publish the image before updating metadata so the referenced asset is available.
- Keep a fallback card for pages whose image generation fails, and log the page identifier and failure reason.
Do not allow arbitrary user-supplied URLs or HTML in a renderer without appropriate controls. If your card loads remote content, restrict what the rendering process can access and make input handling part of the design. Avoid relying on current time, random values, or live data unless those are intentionally part of the image.
6. Troubleshoot common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot is blank or missing the card | The selector does not match, content has not rendered, or navigation reached an unexpected page. | Check the URL and selector, wait for the card to be visible, and wait for the app’s explicit ready state. |
| Text wraps differently between runs | A font is late, unavailable, or substituted; the title length varies. | Wait for document.fonts.ready, ensure font assets are reachable, and design for the longest expected title. |
| Images are absent | Remote resources are slow, blocked, or not awaited. | Wait for a specific image to load or for the card’s ready marker. Check the browser console and network response for the asset. |
| Output is the wrong size | Device scale was used, the element dimensions differ from the target, or a clip is offset. | Use explicit CSS dimensions and scale: 'css'; inspect the saved image dimensions and clip coordinates. |
| Capture hangs on navigation | The page keeps connections open, making a broad network-idle condition unsuitable. | Use a navigation condition such as domcontentloaded or load, then wait for a specific card readiness signal. |
| Browser launch fails in CI | Chromium or required runtime dependencies are not installed in the environment. | Install Playwright’s Chromium browser in the build image and follow Playwright’s platform setup guidance. |
| Shared preview shows an old image | The platform may retain a prior fetch, or the page metadata still points at the old asset. | Verify the live HTML and image URL first, then use the platform’s documented refresh process if one exists. |
| Some page titles render incorrectly | Unescaped HTML, unsupported characters, or fixed layout constraints cause clipping or markup changes. | Escape dynamic values, test long and multilingual strings, and define sensible truncation or line limits. |
7. Performance, reliability, and cost
A local Playwright capture uses browser compute and time for each render. Reuse a browser process for a batch rather than launching Chromium for every card, while creating an isolated page per capture as appropriate for your app. Limit parallel work to what your machine or build workers can handle; too many concurrent pages can increase memory use and make rendering less predictable.
For reliable output, pin the Playwright version in your project, install the matching browser in the runtime image, use a known viewport, and keep card inputs stable. Capture failures should not silently publish broken images: record failures, preserve a fallback asset, and make publishing depend on the required image being available. The exact runtime and storage cost depends on your infrastructure, image format, page assets, and volume; the cited sources do not establish a universal benchmark or price.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. It can return an image or PDF from one GET request. For a page you want to capture, a basic call looks like this:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
See the ScreenshotNeo API documentation for request options. Cookie and consent banners are accepted and removed before capture, along with more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers say the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
FAQ
How do I create an Open Graph image with Playwright?
Render a fixed-size HTML card, capture its element with Playwright, publish the resulting image at an absolute public URL, and set that URL in the page’s og:image tag.
How do I screenshot an HTML page with Playwright?
Navigate to the page, wait until its content is ready, then call page.screenshot({ path: 'page.png' }). For a share card, capture the card element or a matching viewport instead of the full page.
Does a 1200 × 627 image work everywhere?
It meets the LinkedIn minimum described in its current help page. The research here does not establish one size or format that works across all platforms.


