The State of OG Images: A 10,000-Site Crawl
We crawled the top 10,000 home pages for og:image and Twitter card tags: adoption, sizes, formats, file weight and broken image URLs, with our method.
Disclosure: RenderScreenshot sells an API that generates OG images, so we have an interest in this topic. The crawl below did not use our API: it is a plain Python script that fetches HTML and images. We report every number from the run, including the ones that do not make a case for our product.
Most advice about Open Graph images assumes that sites have one. We wanted to know how true that is, and what the images look like when they exist. On 2026-10-18 we fetched the home page of each of the top 10,000 domains in the Tranco list, read the Open Graph and Twitter card tags, then downloaded every og:image and measured it. This post is the result. For how to generate cards yourself, start with the production guide to OG images.
Key findings
- 57.9% of home pages had an
og:image(3,911 of 6,753 HTML home pages). 41.8% had neither anog:imagenor atwitter:image. - Bigger sites are more likely to have one: 65.1% in the top 1,000, 58.8% from rank 1,001 to 5,000, 56.0% from 5,001 to 10,000.
- 687 home pages set
og:titlebut noog:image. They took the time to write Open Graph tags and left out the part that most platforms show largest. - 1200x630 is the default size, but not the majority. 29.6% of measured images were exactly 1200x630; 47.1% were close to 1.91:1; 19.3% were square.
- One in five images was narrower than 600 px (20.8%), and 4.8% were narrower than 200 px. Those are usually logos and icons used as a fallback.
- 3.4% of
og:imageURLs were broken for our crawler, mostly with 404 and 403. - 304 home pages asked for a
summary_large_imagecard with no image at all: notwitter:image(or the oldertwitter:image:src) and noog:imageto fall back on. - Declared sizes are often wrong. 22.6% of pages that declared
og:image:widthandog:image:heightdeclared a size that did not match the file.
Method
| Date | 2026-10-18, one run from one machine |
| Domain list | Tranco top 1M list 467YX (generated 2026-10-18), first 10,000 domains |
| Page fetched | https://<domain>/, then https://www.<domain>/ if that failed. Redirects followed |
| User agent | Mozilla/5.0 (compatible; RenderScreenshot-OG-Study/1.0; +https://renderscreenshot.com/blog/state-of-og-images) |
| HTML read | First 3 MB; <meta> tags before <body>, first value per property |
| Image fetched | The og:image (or og:image:url / og:image:secure_url) resolved against the final page URL, first 20 MB |
| Image measured | Format and pixel size read from the file header (PNG, JPEG, GIF, WebP, AVIF); SVG and ICO detected but not measured |
| Timeouts | 15 s per network operation, 45 s per response body, 900 s for the whole crawl |
| Runs | One. No JavaScript was executed |
| Raw data | og-crawl-2026-10-18.csv, one row per domain (1.6 MB) |
| Script | og_crawl.py, Python standard library only |
What this measures and what it does not. We read the server-sent HTML of home pages only. A site that adds tags with JavaScript, or that puts its effort into article pages and not the home page, counts as "no og:image" here. Social crawlers mostly do not run JavaScript either, so the first limit matches how previews really work; the second does not, and article pages probably score higher. One run means a site that was down for a minute counts as unreachable.
Which domains we could read
Of 10,000 domains, 6,753 (67.5%) returned an HTML page with a 2xx status. The rest are mostly not websites: the Tranco list ranks domains by traffic, and many top domains serve CDNs, DNS, APIs or apps.
| Outcome | Domains |
|---|---|
| HTML page, 2xx | 6,753 |
| No connection (DNS failure, refused, no TLS) | 2,055 |
| HTTP error status | 1,032 (of which 403: 758) |
| Timeout | 94 |
| 2xx but not HTML | 54 |
| Other errors | 12 |
The 758 home pages that answered 403 matter for anyone building link previews. Our user agent said what it was, and many sites turn such clients away. Chat apps and preview services meet the same wall, which is why a preview pipeline needs a fallback; our link preview use case shows one. All percentages below use the 6,753 readable HTML pages as the base unless stated.
Adoption of Open Graph and Twitter tags
| Tag | Home pages with it | Share |
|---|---|---|
og:title |
4,399 | 65.1% |
og:description |
4,147 | 61.4% |
og:image |
3,911 | 57.9% |
og:url |
3,838 | 56.8% |
og:type |
3,814 | 56.5% |
twitter:card |
3,283 | 48.6% |
og:site_name |
3,262 | 48.3% |
twitter:image |
2,059 | 30.5% |
twitter:site |
1,892 | 28.0% |
og:image:width |
1,371 | 20.3% |
og:image:type |
647 | 9.6% |
og:image:alt |
506 | 7.5% |
xychart-beta title "og:image presence by Tranco rank band (% of HTML home pages)" x-axis ["1-1,000", "1,001-5,000", "5,001-10,000"] y-axis "Percent" 0 --> 100 bar [65.1, 58.8, 56.0]
Twitter (X) falls back to Open Graph tags, so a missing twitter:image is not a problem when og:image is set. Only 18 pages had a twitter:image or twitter:image:src without an og:image. The reverse problem is more common: of the 2,547 pages that asked for a summary_large_image card, 304 gave no image in either tag. A large card with no image is shown as a plain link, if at all.
Card types, of the 3,283 pages with a twitter:card:
twitter:card |
Pages |
|---|---|
summary_large_image |
2,547 |
summary |
696 |
app |
17 |
photo |
5 |
player |
2 |
preview |
1 |
photo and preview are not among the card types X documents today.
og:image:alt is the tag most often left out: 501 of the 3,911 pages with an image (12.8%) described it. Platforms that support it can give the preview image a text alternative for screen reader users.
The images themselves
Of the 3,911 og:image URLs, 3,778 returned an image with a 2xx status. We measured pixel size on 3,677 of them (SVG, ICO and a few unreadable files had no size from the header).
Formats
pie showData title og:image format, read from the file header (3,778 images) "PNG" : 2092 "JPEG" : 1376 "WebP" : 184 "SVG" : 73 "GIF" : 21 "Other (HTML, unknown, AVIF, ICO)" : 32
We read the format from the first bytes of each file, not from the URL or the Content-Type header. That matters: 56 images (1.5%) were served with a Content-Type that named a different format than the bytes, most often a JPEG labelled image/png or the reverse. Seventeen URLs returned an HTML page with a 2xx status instead of an image, which usually means a soft 404 or a login wall.
73 sites (1.9%) use an SVG as their og:image. The large platforms do not render SVG previews, so these sites get no image in most places. If your card exists as SVG (for example from Satori), convert it to PNG before you publish the URL.
Sizes
| Size (px) | Images |
|---|---|
| 1200x630 | 1,089 |
| 1200x628 | 136 |
| 2400x1260 | 97 |
| 200x200 | 86 |
| 1200x675 | 83 |
| 512x512 | 80 |
| 1920x1080 | 63 |
| 1200x1200 | 55 |
| 1200x627 | 53 |
| 400x400 | 42 |
| Shape or width | Share of 3,677 measured images |
|---|---|
| Exactly 1200x630 | 29.6% |
| Aspect ratio within 3% of 1.91:1 | 47.1% |
| Square | 19.3% |
| 1200 px wide or more | 63.6% |
| 2000 px wide or more | 8.6% (316) |
| Narrower than 600 px | 20.8% |
| Narrower than 200 px | 4.8% |
Three groups stand out. The 1.91:1 group follows the common guidance. The square, small group (497 images were squares of 512 px or less) is mostly logos: the site set an og:image, but it is the brand mark, which platforms show as a small thumbnail or crop into a wide frame. The 2x group (2400x1260 and larger) produces sharp cards on high-density screens at the cost of weight.
The small-image problem overlaps with card type: 215 of the 2,123 measured images on pages that asked for summary_large_image were narrower than 600 px. Those pages ask for a large card and supply an image that is too small to fill one.
File weight
| Format | Images | Median bytes |
|---|---|---|
| PNG | 2,092 | 30,187 |
| JPEG | 1,376 | 75,165 |
| WebP | 184 | 28,324 |
Across all fetched images, the median was 43,550 bytes, the 90th percentile 396,054 and the 95th 705,831. 102 images (2.7%) were over 1 MB and 3 were over 5 MB, the largest at 11.6 MB. X documents a 5 MB limit for card images, so those three are too large to show there. A large file also slows every preview fetch, and some platforms give up on slow images. The PNG median is low because many PNGs are flat logos and text cards; a photo saved as PNG is where the multi-megabyte files come from.
Broken URLs
| Cause | Images |
|---|---|
| 404 Not Found | 81 |
| 403 Forbidden | 29 |
| Other HTTP errors (400, 422, 426, 500, 530) | 5 |
| No connection | 4 |
| Raw spaces or non-ASCII characters our script did not encode | 8 |
Template bugs: an unfilled placeholder as the host (undefined and one other), two URLs joined into one, a port followed by null, a data: URI |
5 |
| Malformed HTTP response | 1 |
The 404s are the classic failure: an image that was renamed or deleted while the tag still points at it. Nothing on the page itself looks broken, so nobody notices until a link is shared. The 403s are images behind bot protection, which blocks the crawlers that need them. The five template bugs are URLs that no platform can fetch: a variable that was never filled in, or a base URL glued in front of an absolute one. 177 pages (4.5%) used a relative og:image path; we resolved them against the page URL, but not every consumer does, so use absolute URLs. 53 (1.4%) pointed at plain http:// URLs.
Declared versus actual size
og:image:width and og:image:height let a platform lay out the preview before it downloads the image. Of the 1,295 pages that declared both for an image we could measure, 293 (22.6%) declared a size that was not the size of the file. The usual cause is a template that hard-codes 1200 and 630 while the image comes from a CMS field with any size. A wrong declaration is worse than none, because the platform may crop or scale based on it.
What this means if you build a site
- Set an
og:imageon every page you expect people to share, not only the home page. 42% of the home pages we read had none. - Use 1200x630 PNG or JPEG. It is the size most sites converge on, and it is safe on every platform we know of.
- Do not use your logo as the card. A square logo becomes a small thumbnail or a cropped mess in a wide frame.
- Check the URL from outside. Fetch it without cookies and with a crawler user agent. 3.4% of images failed that test here.
- Only declare width and height if they are true, and add
og:image:alt.
If generating a card per page is the hard part, render it from HTML. The OG image API page shows how we do it with a 1200x630 preset and signed URLs.
What this means if you consume previews
If you build chat, CRM or bookmarking features that show link previews, plan for the 42% with no image and the 3.4% with a broken one. A screenshot of the page is a reasonable fallback; our link preview use case describes that pipeline.
Reproduce it
The crawler uses only the Python standard library and runs in about 15 minutes with 96 threads. This excerpt is the part that reads the tags; the rest fetches the image and reads its size from the file header.
from html.parser import HTMLParser WANTED = { 'og:image', 'og:image:url', 'og:image:secure_url', 'og:image:width', 'og:image:height', 'og:image:type', 'og:image:alt', 'og:title', 'og:description', 'og:type', 'og:url', 'og:site_name', 'twitter:card', 'twitter:image', 'twitter:image:src', 'twitter:site', } class MetaParser(HTMLParser): """Collect the first value of each wanted <meta> tag in the document head.""" def __init__(self): super().__init__(convert_charrefs=True) self.meta = {} self.done = False def handle_starttag(self, tag, attrs): if self.done: return if tag == 'body': self.done = True return if tag != 'meta': return a = {k.lower(): (v or '') for k, v in attrs} key = (a.get('property') or a.get('name') or '').strip().lower() if key in WANTED and key not in self.meta: self.meta[key] = a.get('content', '').strip()
Three lessons from running it, if you write your own:
- Socket timeouts do not bound a crawl. Our first run stalled at 9,500 domains with every thread idle. A DNS lookup is not covered by the socket timeout, and a server that sends a byte every few seconds never trips it. The second run added a 45-second deadline per response body and a 900-second limit on the whole crawl; one domain hit the overall limit.
- Read the format from the bytes. 1.5% of
Content-Typeheaders were wrong, and file extensions in URLs are worse. - Encode URLs before you request them. Eight
og:imageURLs with raw spaces or non-ASCII characters failed in our script. Browsers and most crawlers percent-encode them first, so treat those eight as a limit of our tool rather than broken images.
The full crawler script and the raw CSV are published with this post: one row per domain, with fetch status, the og:image URL, the image format, bytes and pixel size, and a 0/1 flag for each other tag. The has_twitter_image flag covers twitter:image only; the counts of 18 and 304 above also accept twitter:image:src. With twitter:image alone, they are 17 and 305. Run python3 og_crawl.py top-1m.csv 10000 out.csv against a Tranco download to repeat it. We plan to re-run the crawl each quarter so the numbers can be compared over time.
Data from the October 2026 run (2026-10-18). Python 3.15.0, standard library only. Tranco list 467YX.
Related in this topic
- Generate OG Images From HTML: A Production Guide Generate Open Graph images from an HTML template: platform size limits, escaping, signed URLs without redirects, cache-safe expiry and a Next.js example.
- Next.js OG Images: opengraph-image.tsx vs a Route Handler Dynamic OG images in Next.js 16: opengraph-image.tsx, the tags it emits, measured static vs per-request timings, fonts, and when a screenshot route wins.
- OG Image Not Showing? 12 Causes and a Checker Script OG image not showing in Slack, LinkedIn or X? Twelve causes in the order to check them, a tested Node script that finds each one, and the fix for every case.
- OG Image Size: 1200×630, Crops and File Limits The right OG image size is 1200×630 (1.91:1). Platform limits with sources, how many pixels each preview shape crops, and measured file sizes at 1x and 2x.
- Rails OG Images: meta-tags, an ERB Card and Signed URLs Dynamic Open Graph images in Rails 8: an ERB card route, the meta-tags gem, a signed og:image helper, Minitest tests and what allow_browser does to crawlers.
- Twitter Card Image Size: summary vs summary_large_image X card image sizes: 1200×600 (2:1) for summary_large_image, a square for summary. What 1200×630 and 1200×628 lose to the crop, and the tags that pick the card.