The Complete Guide to OG Image Sizes and Meta Tags
1200x630, absolute URLs, summary_large_image — the exact dimensions and tags every platform expects, plus how to generate a correctly-sized image automatically instead of by hand.
Every social platform crops, resizes, or rejects your og:image differently. Get the dimensions wrong and your carefully designed card shows up cropped on Twitter, stretched on LinkedIn, or doesn't show up at all on Discord. This is the reference for getting it right the first time, plus how to generate the image itself instead of hand-designing one in Figma per page.
The size to default to: 1200×630
If you only remember one number, make it this one. 1200×630px (a 1.91:1 aspect ratio) is the closest thing to a universal standard — it's what Facebook recommends, what Twitter/X's summary_large_image card expects, and what LinkedIn, Slack, and Discord all render acceptably without cropping into anything important.
| Platform | Recommended size | Aspect ratio | Notes |
|---|---|---|---|
| Facebook / Open Graph | 1200×630 | 1.91:1 | Minimum 200×200, but under 600px wide gets a small card, not a large one |
| Twitter / X | 1200×628 | ~1.91:1 | 1200×630 works fine — the 2px difference isn't visually meaningful |
| 1200×627 | ~1.91:1 | Same story — 1200×630 renders correctly in practice | |
| Discord / Slack | 1200×630 | 1.91:1 | Both read standard og:image tags, no platform-specific tag needed |
In practice: design once at 1200×630, use it everywhere, and stop worrying about the platform-by-platform table above. The exceptions (Pinterest, which prefers taller 2:3 images, and square app icons) are rare enough to handle case by case.
The meta tags that actually matter
The image alone does nothing without the tags pointing at it. This is the minimum set:
<meta property="og:image" content="https://example.com/og/post-1.png" /> <meta property="og:image:width" content="1200" /> <meta property="og:image:height" content="630" /> <meta property="og:image:alt" content="A short description of the image" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:image" content="https://example.com/og/post-1.png" />
Three details that cause the most real-world failures:
- The URL must be absolute.
/og/post-1.pngwill silently fail on most crawlers. Alwayshttps://yourdomain.com/og/post-1.png. - Always set
og:image:widthandog:image:height. Without them, some crawlers have to fetch the image before deciding how to lay out the card, which measurably slows down the first preview render. twitter:cardmust besummary_large_image, notsummary—summaryrenders a small square thumbnail instead of the full-width card, even if your image is the right dimensions.
The safe zone
Different platforms crop slightly differently around the edges, and Twitter in particular rounds the corners on the large-image card. Keep any text, logos, or faces inside the center ~85% of the canvas — roughly a 60px margin on a 1200×630 image — so nothing important lands in the part that gets clipped or rounded off.
File format and size
PNG or JPEG for og:image, not WebP. Support for WebP in link-preview crawlers is inconsistent — some older Twitter/X and LinkedIn crawler versions don't render it at all, which is a much worse failure mode than a slightly larger file. Use WebP for images served directly to users in your own pages; keep PNG or JPEG for anything a social crawler is going to fetch. (More on that tradeoff in our WebP generation guide.)
Keep the file under 1MB — Facebook's crawler will reject images larger than 8MB outright, but anything over ~1MB risks a slow or timed-out fetch on a flaky mobile connection, which some crawlers treat as a failed image. 100–300KB is a realistic target for a 1200×630 card with a solid background and a few lines of text.
Generating the image dynamically
Hand-designing one static image works for a homepage. It doesn't scale to a blog, a product catalog, or anything with more than a handful of pages — you need a per-page image generated automatically, with the right dimensions baked in.
In the Next.js App Router, the convention is an opengraph-image.tsx file that returns a JSX tree, and Next.js handles the sizing and content-type for you:
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export default async function Image({ params }: { params: { slug: string } }) {
return new ImageResponse(
(
<div style={{
width: '100%', height: '100%', display: 'flex',
flexDirection: 'column', justifyContent: 'center',
background: '#0a0a0a', color: 'white', padding: 80,
}}>
<h1 style={{ fontSize: 64, fontWeight: 700 }}>{params.slug}</h1>
</div>
),
size,
);
}This works well if you're already on Next.js and your card design is simple flexbox. It runs on the same Satori-based engine we use under the hood, so the same constraints apply — no CSS grid, no gap-*, color shades cap at 900. For anything more complex, or if you're not on Next.js at all, generating the image via an API at build or publish time is more portable:
const res = await fetch('https://renderfy.io/api/v1/render', {
method: 'POST',
headers: {
'Authorization': 'Bearer rfy_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'tailwind',
content: `
<div class="flex h-full w-full flex-col justify-between bg-zinc-900 p-16 text-white">
<span class="text-sm font-medium text-emerald-400">yourdomain.com</span>
<h1 class="text-6xl font-bold leading-tight">${postTitle}</h1>
<span class="text-sm text-zinc-500">${publishedDate}</span>
</div>
`,
options: { format: 'png', dimensions: 'og-image' },
}),
});
const png = Buffer.from(await res.arrayBuffer());dimensions: "og-image" resolves to 1200×630 automatically, so there's no dimension math to get wrong. Run this at publish time and save the result, or behind a route handler if you want it generated on first request and cached after.
Testing before you ship
Meta tags are invisible until you check them, and every major platform caches the first result it fetches — so test before publishing, not after:
- Facebook Sharing Debugger (developers.facebook.com/tools/debug) — also the tool to use when a page shows a stale image, since it has a "Scrape Again" button that forces Facebook to refetch.
- LinkedIn Post Inspector (linkedin.com/post-inspector) — same idea, and LinkedIn's cache is notoriously sticky without it.
- Twitter/X Card Validator — coverage has been inconsistent since X's API changes; posting a test tweet to yourself is the more reliable check now.
If an old image keeps showing up after you've fixed it, it's almost always the platform's cache, not your tags — re-run the debugger for that URL rather than re-checking your HTML.
Checklist
- Image is 1200×630, PNG or JPEG
- File size under ~300KB
og:imageURL is absolute, not relativeog:image:widthandog:image:heightare settwitter:cardissummary_large_image, notsummary- Important text/logo sits inside the center ~85% of the frame
- Tested in the Facebook Sharing Debugger and LinkedIn Post Inspector before publishing
Getting the tags right is a one-time fix. Generating a correctly-sized image for every page you publish, automatically, is the part worth not doing by hand — that's the actual recurring cost as a site grows past a handful of pages.
Further reading
- Open Graph Image API — full reference for generating dynamic OG images with the right dimensions for every platform
- Converting HTML to Images in JavaScript: The Complete Guide — every server-side approach compared
- How to Generate WebP Images in Node.js — when WebP is the right call, and when it isn't