How to add Open Graph tags in Astro
Astro gives you full control over the <head> — which also means nothing is set up for you. The clean pattern: one base layout that renders all social tags from props, so every page only has to pass a title, description and image.
The free checker shows your previews for nine platforms — from X and WhatsApp to iMessage, Discord and Bluesky.
Put the tags in your base layout
Create (or extend) src/layouts/Layout.astro and render the og: and twitter: tags in its <head> from props. Every page that uses the layout passes its own values — one source of truth, no copy-paste per page.
Make URLs absolute with Astro.url and the site config
Scrapers ignore relative image URLs. Set "site" in astro.config.mjs and build absolute URLs with new URL(image, Astro.site) — that works in dev, previews and production without hardcoding the domain.
Pass per-page values
In each page: <Layout title="…" description="…" image="/og/blog-post.png">. For Markdown/MDX content collections, map frontmatter fields to the layout props so authors set the preview from the frontmatter.
Verify the built output
Astro renders at build time, so what you ship is what scrapers see. Paste a deployed URL into the MetaPeek checker — or paste the built HTML (dist/…/index.html) into the paste-HTML mode before you deploy.
Code example
---
// src/layouts/Layout.astro
const { title, description, image = '/og-default.png' } = Astro.props;
const canonical = new URL(Astro.url.pathname, Astro.site);
const ogImage = new URL(image, Astro.site);
---
<html lang="en">
<head>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:url" content={canonical} />
<meta property="og:image" content={ogImage} />
<meta name="twitter:card" content="summary_large_image" />
<slot name="head" />
</head>
<body><slot /></body>
</html>Common mistakes
- No "site" in astro.config.mjsWithout it, new URL(image, Astro.site) throws and canonical URLs cannot be built. Set site: "https://yoursite.com" first.
- Relative og:image in frontmatterFrontmatter like image: "/og/post.png" must still be resolved against your site URL in the layout — pass it through new URL() instead of printing it as-is.
- Different tags in dev vs buildIf you compute tags from request data in SSR mode, verify the deployed page — the scraper sees the server output, not your dev server.
Frequently asked questions
Does Astro generate og:image files for me?
Not out of the box. Community integrations (like astro-og-canvas or satori-based generators) can render 1200×630 images at build time; otherwise ship a static image per page or a good default.
Where do the tags go with content collections?
Keep rendering them in the layout. The page pulls title/description/image from the collection entry (entry.data) and passes them down as props.
How do I test before deploying?
Run astro build and paste the generated HTML file into the MetaPeek paste-HTML mode — no tunnel or deploy needed.
Paste your URL and see within two seconds whether everything is right — on every platform.