How to Create a Social Card Image from a Webpage with Puppeteer
Render a webpage as a social card with Puppeteer: set the viewport, wait for the design to render, choose an output format, and save the image.
Use Puppeteer’s Page.screenshot() API to render a webpage at your target social-card dimensions and save the result. Set the viewport before navigation, wait for the content your design depends on, then capture the viewport or a specific region. The example below uses 1200 × 630 pixels as a starting point; verify the current dimensions and other requirements for the platform where you plan to publish.
1. Install Puppeteer and render a card
In a new project, install Puppeteer:
npm install puppeteer
Save this as social-card.mjs and run it with node social-card.mjs. Replace the example URL with a page you control or have permission to capture.
import puppeteer from 'puppeteer';
const url = 'https://example.com/article';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
// Set the layout dimensions before navigation so responsive styles
// are evaluated for the intended card size.
await page.setViewport({
width: 1200,
height: 630,
deviceScaleFactor: 1,
});
await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 60_000,
});
// Optional: wait for a design-specific element or app readiness signal.
// await page.waitForSelector('[data-card-ready="true"]');
await page.screenshot({
path: 'social-card.png',
type: 'png',
});
} finally {
await browser.close();
}
Puppeteer’s screenshots guide demonstrates launching a browser, navigating with a wait condition, saving a screenshot, and closing the browser. Its viewport API documents setting the viewport dimensions. See the Puppeteer screenshots guide, ScreenshotOptions API, and Page.setViewport API.
2. Decide what part of the page to capture
A social card is usually a composed landscape image, not a screenshot of the entire document. Pick a capture method that matches the page you have:
| Method | Use it when | Example |
|---|---|---|
| Viewport screenshot | The page or a dedicated route is designed to fit the card dimensions. | page.screenshot({ path: 'card.png' }) |
| Clip | The intended card occupies a known rectangle in the rendered page. | page.screenshot({ path: 'card.png', clip: { x: 0, y: 0, width: 1200, height: 630 } }) |
| Full page | You explicitly need the whole document, such as a long-page preview. | page.screenshot({ path: 'page.png', fullPage: true }) |
fullPage: true captures the complete document and can produce a very tall image. For a predictable card, prefer a dedicated route or a deliberately sized element, and check the final crop at the target dimensions. Puppeteer documents the clip and fullPage screenshot options in its ScreenshotOptions API.
Capturing a clipped region
The clip rectangle is measured in CSS pixels. Make sure the chosen rectangle is within the rendered page and includes the complete composition:
await page.screenshot({
path: 'social-card.png',
type: 'png',
clip: { x: 0, y: 0, width: 1200, height: 630 },
});
If the card is an element that might move or resize, wait for it and read its bounds rather than assuming its coordinates:
const card = await page.waitForSelector('[data-social-card]');
if (!card) throw new Error('Social card element was not found');
const bounds = await card.boundingBox();
if (!bounds || bounds.width === 0 || bounds.height === 0) {
throw new Error('Social card element has no visible bounds');
}
await page.screenshot({
path: 'social-card.png',
type: 'png',
clip: bounds,
});
3. Wait for visual readiness
Navigation completion and visual readiness are different. The example uses networkidle2, a wait condition shown in Puppeteer’s guide, but no generic network wait proves that every font, image, animation, or client-rendered component has settled.
- For client-rendered content: wait for a selector that only appears when the card is ready, or for an application-owned readiness signal.
- For important images: wait for them to load and decode before capture.
- For web fonts: wait for
document.fonts.readywhen font appearance matters. - For animated designs: make the capture state deterministic, for example by disabling animation in the page’s capture mode.
Example readiness checks:
await page.waitForSelector('[data-card-ready="true"]', { timeout: 15_000 });
await page.evaluate(async () => {
await document.fonts.ready;
const images = Array.from(document.images);
await Promise.all(images.map(async (image) => {
if (!image.complete) {
await new Promise((resolve, reject) => {
image.addEventListener('load', resolve, { once: true });
image.addEventListener('error', reject, { once: true });
});
}
if (image.decode) await image.decode().catch(() => {});
}));
});
Use checks that reflect the page’s actual design. A page with analytics polling or a persistent connection may never become network-idle, while a page can become network-idle before a delayed component is ready. Puppeteer’s guide uses networkidle2 as an example, not as a guarantee for all assets or applications. See the screenshots guide and Page.screenshot API.
4. Pick dimensions, scale, and output format
The dimensions should come from the platform and the design. A secondary guide recommends 1200 × 630 pixels for an X summary_large_image card, but platform requirements can change and should be checked before publishing. Do not treat that size as a universal social-card rule. See the secondary X card guide and verify current requirements with your target platform.
- Viewport: Set width and height before navigating so responsive layout uses the intended dimensions. Puppeteer notes that changing viewport characteristics can trigger a reload. See Page.setViewport.
- Device scale factor: Use
1for output matching the CSS-pixel dimensions. A larger factor creates more physical pixels and a larger image; check the actual saved dimensions if you change it. - PNG: Lossless output, useful when crisp text and graphics matter.
- JPEG: Often a smaller option for photographic designs, with a quality tradeoff. Puppeteer supports a
qualitysetting for JPEG output. - Transparency: Use
omitBackground: truewhen a transparent background is wanted and the output format supports it.
JPEG example:
await page.screenshot({
path: 'social-card.jpg',
type: 'jpeg',
quality: 85,
});
Transparent PNG example:
await page.screenshot({
path: 'social-card.png',
type: 'png',
omitBackground: true,
});
Do not pass JPEG quality expecting it to change PNG output. The available format, quality, clipping, full-page, and background options are described in the ScreenshotOptions API.
5. Validate the generated card
Before adding the image to a publishing pipeline, inspect the artifact. Confirm its pixel dimensions, crop, readable text, loaded imagery, background, and file size. A screenshot call succeeding only means Puppeteer produced an image; it does not verify that the result meets a platform’s current file-size, format, metadata, or crawler-access rules.
- Open the saved file and confirm the composition is not clipped unexpectedly.
- Check that headings and small details remain legible at the size the platform displays.
- Verify that the image dimensions match your intended card design.
- Check that the file format and size meet the target platform’s current guidance.
- Validate the page metadata and crawler accessibility separately; those requirements are platform-specific.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Image shows a loading state or missing content | The screenshot ran after navigation but before the app or assets were ready. | Wait for an app-specific selector/readiness signal; explicitly wait for important images and fonts. |
networkidle2 times out |
The page keeps requests active, such as analytics, polling, or a long-lived connection. | Use a navigation condition suited to the page, then wait for the card’s actual ready selector instead. |
| The card has the wrong responsive layout | The viewport was set after navigation or does not match the intended design size. | Set width, height, and device scale factor before page.goto(). |
| The output is very tall | fullPage: true captured the full document. |
Capture the viewport, use a clip rectangle, or capture a known card element. |
| The clip is misplaced or incomplete | Coordinates were guessed, the element moved, or the clip falls outside the rendered area. | Wait for the target element, obtain its bounding box, and inspect the crop. |
| Fonts look different from the page in a normal browser | Font files were not ready or failed to load before capture. | Wait for document.fonts.ready and check font requests and page rendering. |
| Image file is larger than expected | Lossless PNG, a high device scale factor, or a large full-page capture can increase output size. | Use the intended card dimensions; consider JPEG for photo-heavy cards and inspect its quality. |
| Browser process remains after an error | Cleanup did not run after navigation or capture failed. | Put browser.close() in a finally block, as in the runnable example. |
7. Performance, reliability, and cost
For a repeatable job, use a dedicated card route or deterministic card element, a fixed viewport, explicit readiness checks, and guaranteed browser cleanup. Avoid capturing the entire page when the deliverable is a fixed-size card. Reusing a browser process for multiple captures can reduce repeated launch work, but isolate page state between jobs and close the browser when the worker shuts down.
Capture time depends on page load behavior, assets, and readiness conditions. A long timeout does not make a design more reliable if the page is waiting on unrelated network activity; wait for the content that matters. Local Puppeteer costs include the compute and browser runtime you operate, plus the work of maintaining the capture environment. This research does not establish a benchmark or a fixed per-image cost.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. A single request returns an image or PDF, and its parameters also accept the names used by other screenshot APIs. Here is a direct image request:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/article -o shot.webp
See the ScreenshotNeo API documentation for parameters, including viewport and output options.
- Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers say the page verdict and whether the request was billed.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdffor AI agents and MCP clients. - There are 1,000 screenshots a month free with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo and get 1,000 free screenshots a month, no card required.
FAQ
Should I capture the webpage’s Open Graph image instead?
If the page already publishes a suitable social image, using that asset can avoid rendering the live page. Use Puppeteer when you need to generate a card from rendered page content or a dedicated card layout.
Does a successful screenshot guarantee a valid social card?
No. It confirms an image was produced. Check the target platform’s current image dimensions, file limits, metadata, crawler access, and cache behavior separately.
Can I use this approach in a build pipeline?
Yes. Make the capture deterministic by controlling the route, viewport, readiness signal, output path, and browser cleanup, then validate the saved artifact before publication.


