GUIDE

How to Set a Custom OG Image on Every Page (Without Editing Code Each Time)

Setting a custom og:image per page usually means editing a template or hardcoding a tag per URL. Here's how to give every page its own preview image without a code change for each one.

August 16, 20266 min read

Most sites start with one og:image for the whole site — a logo card set in a layout file, applied everywhere by default. That's fine until it isn't: a blog post about a specific topic sharing the same generic brand image as the pricing page, a product listing sharing the same card as the homepage. Setting a custom og:image per page fixes that, but the naive way to do it — hardcoding a different meta tag value into each page's template, or asking a developer to add an image path every time new content ships — turns a content problem into an engineering bottleneck.

Why the code-per-page approach breaks down

If setting a custom og:image requires a code change, the constraint isn't the image, it's the deploy. A marketing team wanting a page-specific card for a new landing page now needs an engineer to open the codebase, add or update a meta tag, and ship a deploy — for what is, functionally, a content decision. That dependency doesn't scale past a handful of pages, and it especially doesn't scale for content types that get created outside the main codebase entirely, like blog posts published through a CMS or product listings synced from a catalog.

The other failure mode is worse: teams that hit this friction once often give up and go back to one shared image for everything, accepting generic previews across the whole site rather than fighting the deploy cycle for every page that wants something specific. That's a worse outcome than the original problem — it trades a maintenance annoyance for permanently flat, undifferentiated previews.

What per-page actually requires

A genuinely per-page og:image setup needs three things working together: a way to generate or select an image without touching code, a way to attach that image's URL to a specific page's meta tags, and a way for that attachment to happen automatically as part of publishing rather than as a manual follow-up step. Miss any one of the three and you're back to a bottleneck — generation without automatic attachment just moves the manual work to a different step.

  • Generation: producing an image specific to the page's own content (title, category, featured image) rather than reusing a static default
  • Attachment: getting that image's URL into the page's og:image tag without a developer manually editing the tag per URL
  • Automation: tying generation and attachment to the publish action itself, so a new page is correct from the moment it goes live

Template-driven generation solves the generation half

The way to produce a genuinely custom image per page without a design step per page is a template that takes the page's own data as input variables — its title, a category tag, a featured photo if it has one — and renders an image from that. This isn't the same as a single static fallback image; the template is reused across pages, but its output differs per page because the variables differ. A blog post titled 'Q3 Product Roadmap' and one titled 'How We Cut Latency in Half' render different images from the same template, because the title variable feeding into each is different.

This is also where consistency and customization stop being in tension. A hand-designed one-off image per page gets you customization at the cost of consistency (every designer makes slightly different layout choices). A single shared static image gets you consistency at the cost of customization. A template with variables gets you both — every page follows the same visual system, but the content inside it is genuinely page-specific.

Getting the URL into the page without a code deploy

The attachment half depends on how a given page gets built. For a CMS-driven site, this usually means the CMS field for og:image gets populated with the rendered template URL at save or publish time, rather than a developer setting it in a layout file. For catalog or listing pages generated from structured data — a product feed, for instance — the same principle applies: the render URL becomes another field alongside the product's existing data, generated as part of the sync rather than added by hand afterward.

Handling pages that shouldn't get a fully custom image

Not every page benefits from a fully dynamic image. A pricing page or an about page often works better with a simpler, more static brand card — there isn't much page-specific content to render into a template meaningfully, and forcing dynamic variables onto a page that doesn't have natural inputs for them (no title worth highlighting beyond 'Pricing') can produce a weaker result than a well-designed static default. A practical setup usually mixes both: dynamic, templated images for content types that generate a lot of pages with genuinely different content (blog, products, listings), and a small number of hand-set static images for the handful of pages that don't change often and don't have obvious per-page variables to render.

What to do about pages that already went live with a generic image

Rolling out per-page images rarely means starting from a blank site — it usually means retrofitting a per-page system onto a site that's already been running with one shared static image for months or years. That's a backfill problem, not a fresh-start problem, and it's worth treating it as one: prioritize the highest-traffic or most-shared pages first (a popular blog post, a top-selling product) rather than trying to regenerate every URL on day one. A partial rollout that covers the pages actually getting shared is more valuable in the near term than a complete rollout that takes weeks to finish before any page benefits.

It's also worth deciding upfront whether the backfill should happen automatically (a batch job that regenerates every existing page's image against the new template) or gradually (each page gets its image regenerated the next time it's edited or republished). Automatic backfill gets full coverage faster but requires more upfront engineering to run safely against however many pages already exist; gradual backfill is simpler to build but means some low-traffic pages might keep the old generic image indefinitely if they're rarely touched again.

Verifying the rollout actually reached every page type

A per-page image system rolled out incrementally is easy to lose track of — it's straightforward to confirm the blog got its custom images and forget that a secondary content type, like a help center or a landing page section added later, was never wired into the same system and is quietly still serving the old shared default. This is worth checking directly rather than assuming coverage is complete once the main content type looks right, because the pages most likely to be missed are exactly the ones nobody's actively working on day to day, which also tends to mean nobody's likely to notice the gap without deliberately checking for it.

How useopengraph handles per-page images without a deploy

useopengraph generates images from templates with text, image, and color variables, and each render is served from a stable, versioned URL under /api/render/{templateId}/v{version}/.... That URL is just a normal image URL — it drops directly into an og:image tag through whatever field your CMS, static site generator, or catalog sync already uses to set that tag, with no code change required to give a new page its own image. Publishing an update to the template creates a new version rather than mutating existing render URLs, so pages that already went live keep exactly the image they shipped with. The REST API and MCP server also expose template rendering programmatically, which covers the automation piece directly for catalogs or pipelines that generate pages outside a traditional CMS.

Stop paying per seat
for a usage-shaped problem.

Unlimited teammates, one usage pool. Start free with the scanner — no card required.