astro og image
Astro OG image: static, build-time, or on-demand API
An Astro OG image is the preview file a crawler reads from og:image. This page compares three ways to ship an Astro open graph image: a static file in public/, a build-time endpoint in the Satori or astro-og-canvas style, and a dynamic OG image from a hosted API. A dynamic OG image in Astro follows the page title. The right option depends on whether that title is known when you build. Figures below come from the Astro docs, the astro-og-canvas README, the Satori README, and this app. This page does not quote a host build-minute price or an API latency.
Get an API key for the OG Pilot API, or install the MCP server so an agent can call generate_og_image. The shorter Astro layout sample lives on the Astro OG image generator. Related guides: the API docs, the og:image size cheat sheet, the OG image API comparison, the Next.js OG image guide, the Open Graph debugger, and generate one image in the browser.
Static, build-time, and on-demand
A prerendered endpoint and a hosted API called from prerendered frontmatter both run during astro build. They differ in who draws the card. Request-time rendering is a separate switch: export const prerender = false, plus an adapter.
| Approach | Build time impact | Freshness for dynamic / CMS content | SSR / adapter | Fonts / emoji | Caching | Cost / maintenance |
|---|---|---|---|---|---|---|
| Static file in public/ | Astro copies public/ into the build untouched. The project-structure docs describe these files as unprocessed. There is no image render step for that file. | The bytes change when you replace the file and deploy. A CMS edit does not update the file. | No adapter. The default static output is enough. | Whatever you exported from the design tool. Astro does not read or subset the file. | The host serves the copied file. Social platforms cache the URL they fetched. This page does not publish their TTL. Change the file or the URL, then recheck the preview. | No image API. You maintain the artwork. One shared card is a poor fit when every post needs its own title. |
| Build-time endpoint (Satori or astro-og-canvas) | On a static site, Astro calls the endpoint at build time and writes a file. astro-og-canvas OGImageRoute exports getStaticPaths and GET, one image per key in pages. More entries means more images during astro build. The package caches renders in ./node_modules/.astro-og-canvas by default. Set cacheDir to false to disable that. No duration is published here. | The image matches the pages object at build: getCollection(), a markdown glob, or any object you pass. A remote CMS edit shows up after a rebuild that includes that entry. prerender = false runs the endpoint on request instead, and that requires an adapter. | A prerendered dynamic route uses getStaticPaths and does not need an adapter. An on-demand route skips getStaticPaths and needs an adapter. Official adapters include Node, Netlify, Vercel, and Cloudflare. | Satori requires font data for any text. Formats are TTF, OTF, and WOFF. WOFF2 is not supported. Emoji and missing glyphs go through loadAdditionalAsset. For emoji, return an image data URL when the code is emoji. Satori returns SVG, so a PNG response rasterizes that SVG. astro-og-canvas takes font paths or URLs and a families stack so a later family covers a missing glyph. Its README does not document an emoji image callback. pnpm also installs canvaskit-wasm. | astro-og-canvas cacheDir is a build cache, not a social-crawler cache. The emitted file stays static until the next build. | You run the library. npm lists astro-og-canvas as MIT. This page does not quote a host's build-minute price. You maintain the endpoint and the font files. |
| On-demand API (OG Pilot) | createImage in prerendered frontmatter runs during astro build and returns a CDN URL. The Astro process does not rasterize the card. createImage on an on-demand route runs when that route is requested, so the build does not call the API for that page. | The card matches the signed claims: template, title, description, path, and the other documented fields. A URL baked into static HTML stays there until the next build. An on-demand page can sign again when CMS data changes. If iat is set, the image cache is valid for 24 hours from that timestamp. If iat is omitted, the cache does not expire on its own. A newer iat can serve the previous image with stale: true while a new render is queued. | No adapter when the call runs at build time. An adapter is required when the layout must run on each request. Keep the API key off the client: Astro exposes only PUBLIC_ variables to client code. Do not prefix OG_PILOT_API_KEY with PUBLIC_. | The documented JWT claims do not include a font file or an emoji parameter. You pass title, description, colors, logo_url, and image_url. Named templates render the card. image_type may be jpeg, png, webp, or gif. The JSON response reports width 1200 and height 630. | Pass iat as Date.now() in the JavaScript SDK. The SDK converts milliseconds to seconds. Omit iat for a cache that does not expire on its own. Social crawlers still cache the URL they fetched. Recheck the public URL after the artwork changes. | 7-day trial, no card, 100 completed images in this app. The API accepts a trial after DNS TXT verification. A trial is not enough for MCP generate_og_image. API: 600 requests per minute and 60,000 per hour per key. MCP generation: 10 per hour. The plan catalog lists Starter at $5/month ($50/year, 10,000-image quota), Builder at $49/month ($99/year, 100,000-image quota), and Growth at $99/month ($199/year, unlimited image quota). A Stripe image_quota feature can replace the quota. The catalog has no per-image price. |
Checked 2026-10-09. Sources: Astro project structure, Astro endpoints, Astro on-demand rendering, Astro environment variables, the astro-og-canvas README, and the Satori README. OG Pilot figures are from this app's plan catalog, API docs, and MCP guide.
Which one to ship
Use public/ when one designed card covers the site and you will replace the file when the artwork changes. A blog where every post title should appear on the card outgrows a single file.
Use a build-time endpoint when every entry is known at build, from a content collection or markdown, and you want the PNG in the build output. You own the font files. Satori is JSX-to-SVG. astro-og-canvas is a CanvasKit route helper. OGImageRoute builds one image per key, including keys loaded with getCollection().
Use the hosted API when the same templates should work from Astro, Rails, or WordPress, or an agent should call generate_og_image. The Astro build stores a CDN URL. It does not store a font pipeline. A remote CMS that edits a title between deploys needs the HTML to be generated again. Rebuild the static site, or render that route on demand. Calling the API from prerendered frontmatter does not watch the CMS after the build finishes.
BaseHead meta tags with the OG Pilot API
Install og-pilot-js at ^0.4.2, the JavaScript SDK version in the API docs. configure reads OG_PILOT_API_KEY and OG_PILOT_DOMAIN. createImage signs the JWT and returns the image URL. template defaults to page. Use blog_post on a post layout when you have an author and a date. Pass iat when you want the documented 24-hour cache window. The frontmatter runs on the server during the build, or on each request when the page sets prerender to false. Put BaseHead in the layout head. The documented JSON response includes width 1200 and height 630. There is no width or height request field.
npm install og-pilot-js@^0.4.2
---
// src/components/BaseHead.astro
import { configure, createImage } from "og-pilot-js"
const { title, description } = Astro.props
configure((config) => {
config.apiKey = import.meta.env.OG_PILOT_API_KEY
config.domain = import.meta.env.OG_PILOT_DOMAIN
})
const ogImage = await createImage(
{
template: "page",
title,
description,
path: Astro.url.pathname,
},
{ iat: Date.now() },
)
---
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={Astro.url.href} />
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={ogImage} />
POST https://ogpilot.com/api/v1/images
Content-Type: application/json
Accept: application/json
{"token":"YOUR_JWT_TOKEN"}
Required JWT claims: iss (verified domain), sub (API key prefix).
Optional: iat, template, title, description, path.
The JavaScript SDK signs this token.
MCP install for an agent
The remote server is https://ogpilot.com/mcp. Transport is Streamable HTTP. Auth is OAuth 2.1 with PKCE. The config file does not contain an API key. generate_og_image takes a public url and an optional style, and it needs an active plan and one credit. debug_open_graph, compress_image, and extract_color_palette work on a guest connector session. Claude and ChatGPT do not read the mcpServers file. Follow the connector steps on /docs/mcp.
{
"mcpServers": {
"og-pilot": {
"type": "streamable-http",
"url": "https://ogpilot.com/mcp"
}
}
}
generate_og_image
{ "url": "https://example.com/blog/launch" }
How to choose an Astro OG image
- Use a file in public/ when one designed card covers the site.
- Prerender an endpoint with getStaticPaths when each content entry needs its own image at build time.
- Call the OG Pilot API from server-side layout frontmatter when the card should come from a hosted template.
- Render that route on demand, with an adapter, when CMS edits must change og:image without a full-site rebuild.
- Install the MCP server when an agent should call generate_og_image with the page URL.
What is an Astro OG image? The file your layout puts in og:image. Does a static file in public/ add a render step? Astro copies that directory untouched. Do you need an adapter for a build-time endpoint? Only when the endpoint runs on request. Can an agent generate the card without the Astro build? Yes, with generate_og_image, after an account with an active plan. Will this page publish latency or price benchmarks? No.