Social share images must be JPEG or PNG

Pitfall By design
og:image and twitter:image must point at a .jpg or .png, even when every visible image on the page is WebP or AVIF. A meta tag carries exactly one URL: there is no <picture>, no srcset and no Accept negotiation, so the single format you name has to satisfy every scraper that will ever read it - and they disagree with each other, change without notice, and fail silently, so the page looks perfect while the shared card comes back blank. Nothing is gained by risking it, because a share image is fetched once by a crawler and cached on their side, never on a visitor’s critical path. Docly makes the mistake easy to reach twice: the display-name extension picks the output format, so linkImage(…, ‘name.webp’) serves WebP, and images.defaultFormat: webp in #/site.json turns an extension-less share URL into WebP without you writing .webp anywhere. Fixing the tag is not enough on its own - scrapers cache the failed result, so re-scrape the URL afterwards.
Applies to: Hash templatesImagesSEO

What you'll see

You share a page on Facebook, LinkedIn, Slack or Teams and the preview card comes back with no image — just the title, the description and a blank rectangle. The page itself is fine. The markup looks right, and pasting the og:image URL straight into the address bar renders the image perfectly:

<meta property="og:image" content="https://example.com/images/e/abc123/1200x630x0/article.webp">

Nothing is broken on your side, so the natural next step is to hunt for a cache, a redirect, a Content-Type or a missing dimension tag. The format is the problem. Under a site-wide images.defaultFormat: "webp" it is worse, because the tag does not even have to mention WebP — the URL below serves WebP too, and reads as if it could not possibly be a format issue:

<meta property="og:image" content="https://example.com/images/e/abc123/1200x630x0/article">

What's actually happening

A meta tag has one URL and no fallback. This is the whole of it. On a page you can serve a modern format safely because the markup carries alternatives — <picture> with <source type> lets the browser pick, and a browser that cannot decode AVIF falls back to WebP or JPEG. A <meta> tag has none of that. There is no <picture>, no srcset, and Docly does not negotiate format from the Accept header, so the one format you name is the one every consumer gets. If it cannot read it, there is nothing to fall back to and you get no image at all.

The consumers are dozens of independent scrapers, and they do not agree. Facebook, LinkedIn, X, Slack, Discord, WhatsApp, Signal, Teams, iMessage, Xing, Quora and every link-preview library in between each fetch your og:image with their own code. Published reports on which of them handle WebP contradict each other and reverse over time: testing from 2023 had Facebook rendering WebP previews despite its own documentation listing only JPEG, PNG and GIF, while Slack, Signal, Skype and Quora failed; guidance written in 2026 has LinkedIn’s scraper skipping .webp and showing a no-image card. That instability is the argument, not a footnote to it. You cannot verify this once and be done, because you do not control any of these crawlers and none of them will tell you when they change. JPEG and PNG are the formats every one of them has always accepted.

There is no upside to trade against that risk. The reason to serve WebP on a page is weight on the critical path: a real visitor waits for it, and LCP moves. A share image is not on anybody’s critical path. It is fetched once, by a crawler, out of band, and cached on the platform’s own infrastructure before any human sees the card. Saving 40 KB on a request no visitor makes buys nothing, and the downside is the entire preview.

In Docly the format is chosen by the display-name extension, which is what makes this easy to do by accident. Docly converts a scaled copy on serving, and it takes the output format solely from the extension on the display name in the image URL — see Use AVIF and WebP image formats. So a share tag built by copying the pattern used everywhere else on the page inherits WebP silently:

<!-- on the page: correct, WebP has universal browser support -->
<img src="#linkImage(f.Image1, 1200, 800, 0, 'article.webp')#" …>

<!-- in the head: the same call, now broken for a scraper that cannot read WebP -->
<meta property="og:image" content="#linkImage(f.Image1, 1200, 630, 0, 'article.webp')#">

And images.defaultFormat in #/site.json catches the second case: it applies whenever the URL carries no format-steering extension, so a display name with no extension at all becomes WebP site-wide. webp is a good default for page images and you should keep it — but it means a share URL must name .jpg or .png explicitly rather than relying on the absence of an extension.

The failure is silent in both directions. Nothing errors, nothing is logged, and your own browser renders the URL fine because it supports WebP. The only place the fault exists is inside another company’s scraper. Worse, the platforms cache what they scraped: once a URL has been fetched and the image rejected, correcting the tag changes nothing until the cached scrape is invalidated, which is why this often reads as “the fix did not work”.

What to do

The rule

Every image referenced from a <meta> tag is .jpg or .png. Nothing else. Keep WebP and AVIF for everything a browser renders; the head is the one place where the oldest, dullest format is the correct engineering choice. Use .png only when the image genuinely needs transparency or is flat graphics with text, and .jpg otherwise — a photographic PNG at share dimensions is needlessly large.

<meta property="og:image" content="#linkImage(f.Image1, 1200, 630, 0, 'article-title.jpg')#">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="#linkImage(f.Image1, 1200, 630, 0, 'article-title.jpg')#">

Build the URL with linkImage rather than assembling it by hand — see Do not build file and image URLs by hand. Give the display name a real, descriptive filename while you are there, per Give linked images SEO-friendly filenames: it is the filename a person sees when they save the card image.

Three things that break this even after the extension is right

  • og:image must be an absolute URL, scheme and host included. A scraper does not resolve relative paths the way a browser does. If your helper returns a site-relative path, prefix the site origin; settle what it actually returns rather than assuming, per Use a scratch hash file to test Docly functions.
  • The share size must be registered. Under images.sizePolicy of auto or strict, 1200x630x0 has to be in allowedSizes or the request is refused — and a share URL that 400s looks exactly like a format failure from the outside. Add it once, in #/site.json, per Restrict and register image sizes. Note that the policy checks the dimensions, not the extension, so the JPEG share copy reuses the size you may already have registered for a WebP page image.
  • The image must be publicly reachable with no login. A scraper arrives with no session. Anything under #/ is never served at all — see Keep private data under the # folder.

Re-scrape after you fix it

The platforms cache the scrape, not just the image, so the corrected tag will not show up on its own. Force a re-fetch and read what the tool reports — this is also the fastest way to confirm the format was the fault:

  • Facebook: Sharing DebuggerScrape Again (developers.facebook.com/tools/debug/)
  • LinkedIn: Post Inspector (linkedin.com/post-inspector/)
  • X: Card Validator
  • Anything else: post the link into a private channel and look at it.

Do not hand this check back to whoever asked for the change — the debugger is a URL and a button, and the result is the verification.

Checklist

  • og:image and twitter:image end in .jpg or .png — never .webp, .avif, .svg or no extension at all.
  • The URL is absolute, and reachable while logged out.
  • 1200x630x0 (or whatever you chose) is in allowedSizes.
  • og:image:width and og:image:height are set — some scrapers skip an image whose dimensions they cannot determine cheaply.
  • Page images are untouched: still WebP, still AVIF-in-<picture>.
  • Re-scraped in at least the Facebook debugger and the LinkedIn inspector after the change.