Goal
Auto-generate per-page social cards (1200×630, OG-format) from gallery cover images and ship them with the right OpenGraph meta tags so links shared on Discord, Slack, Mastodon, Bluesky, LinkedIn, iMessage, WhatsApp, Facebook, etc. show a real preview — without the user having to know what OpenGraph is.
Why a plugin (not part of Lumina or core)
We tried the theme-only path first (commit 2470279, reverted). It revealed:
- The hard part isn't the meta tags — it's producing a usable image.
- Photographer galleries use 16:9 hero / portrait covers. OG wants 1.91:1 (1200×630). Just pointing
og:image at an existing variant produces ugly previews on every platform.
- Asking the user to provide a hand-crafted card image (
og_image URL in site.json) defeats the "out of the box" benefit and isn't realistic for the target audience.
- Theme-only meta tags without a real image source had no value — and would have required template logic users couldn't reasonably understand.
A plugin can do what's needed: read the cover image, smart-crop to 1200×630 (NetVips supports saliency/face detection), write output/og/<slug>.jpg, and emit the right <meta> tags. Zero user configuration.
Plugin Design
Package: Spectara.Revela.Plugins.OpenGraph
Pipeline integration:
- Reads manifest after
generate scan
- Picks an image per page (cover → first gallery image → site-wide fallback)
- Smart-crops to 1200×630 with NetVips
- Writes
output/og/<gallery-slug>.jpg
- Hooks into the render pipeline to inject
<meta property="og:*"> tags into the rendered HTML
Generated tags (per page):
<meta property="og:type" content="article"> <!-- "website" for home -->
<meta property="og:title" content="Page or site title">
<meta property="og:description" content="Page or site description">
<meta property="og:image" content="https://example.com/og/vacation.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:url" content="https://example.com/vacation/">
<meta property="og:site_name" content="Site name">
<meta property="og:locale" content="en_US">
Behaviour gates (mirrors sitemap pattern):
baseUrl not set in project.json → plugin emits info log, generates nothing (absolute URLs are required by every consumer)
- No image candidate (no cover, no first image, no site fallback) → no
og:image, other tags still emitted
- HTML hook only injects when no
<meta property="og: already exists in <head> (lets advanced users override)
Twitter Cards (twitter:*) intentionally out of scope — every modern platform reads OpenGraph, the twitter:* spec is effectively legacy since X stopped maintaining its public dev resources. Users who want them can ship a tiny override theme partial.
Optional later (separate issue): Branded card variants with text overlay (gallery title + site name) à la Vercel/GitHub OG cards — needs font handling, layout templating.
Pipeline Hook Question (open)
The plugin needs to inject tags into rendered HTML. Two approaches:
- Post-process HTML — read each
index.html after generate pages, inject before </head>. Simple, no template engine coupling. Robust against any theme.
- Inject template variables — add
og_* variables to the layout model so themes can {{ include 'opengraph' }}. Couples plugin to theme convention.
Lean toward (1) — it works for any theme without theme cooperation. Plugin stays self-contained.
Lessons Learned (from the reverted attempt)
current_url / base_url template variables: useful concept, but only added when a real consumer needs them. The plugin will be that consumer if it goes route (2).
BaseUrl was string with fallback "https://example.com" — the if (config.Project.BaseUrl is not null) gate at the sitemap site was dead code. Will be fixed in a separate, isolated commit so sitemap correctly skips when baseUrl is missing (matches the docs).
- Per-page is the right model (not site-wide) — different shares show different previews. This is what every modern SSG does (Hugo
.Permalink, Zola current_url, Jekyll absolute_url).
- Image-format mismatch is the real problem to solve; everything else is plumbing.
Implementation Tasks
Out of Scope (for this issue)
- Twitter Cards
- Branded text-overlay cards
- Per-page
og_image frontmatter override (can add later when needed)
- AI-generated card layouts
Goal
Auto-generate per-page social cards (1200×630, OG-format) from gallery cover images and ship them with the right OpenGraph meta tags so links shared on Discord, Slack, Mastodon, Bluesky, LinkedIn, iMessage, WhatsApp, Facebook, etc. show a real preview — without the user having to know what OpenGraph is.
Why a plugin (not part of Lumina or core)
We tried the theme-only path first (commit 2470279, reverted). It revealed:
og:imageat an existing variant produces ugly previews on every platform.og_imageURL insite.json) defeats the "out of the box" benefit and isn't realistic for the target audience.A plugin can do what's needed: read the cover image, smart-crop to 1200×630 (NetVips supports saliency/face detection), write
output/og/<slug>.jpg, and emit the right<meta>tags. Zero user configuration.Plugin Design
Package:
Spectara.Revela.Plugins.OpenGraphPipeline integration:
generate scanoutput/og/<gallery-slug>.jpg<meta property="og:*">tags into the rendered HTMLGenerated tags (per page):
Behaviour gates (mirrors sitemap pattern):
baseUrlnot set inproject.json→ plugin emits info log, generates nothing (absolute URLs are required by every consumer)og:image, other tags still emitted<meta property="og:already exists in<head>(lets advanced users override)Twitter Cards (
twitter:*) intentionally out of scope — every modern platform reads OpenGraph, thetwitter:*spec is effectively legacy since X stopped maintaining its public dev resources. Users who want them can ship a tiny override theme partial.Optional later (separate issue): Branded card variants with text overlay (gallery title + site name) à la Vercel/GitHub OG cards — needs font handling, layout templating.
Pipeline Hook Question (open)
The plugin needs to inject tags into rendered HTML. Two approaches:
index.htmlaftergenerate pages, inject before</head>. Simple, no template engine coupling. Robust against any theme.og_*variables to the layout model so themes can{{ include 'opengraph' }}. Couples plugin to theme convention.Lean toward (1) — it works for any theme without theme cooperation. Plugin stays self-contained.
Lessons Learned (from the reverted attempt)
current_url/base_urltemplate variables: useful concept, but only added when a real consumer needs them. The plugin will be that consumer if it goes route (2).BaseUrlwasstringwith fallback"https://example.com"— theif (config.Project.BaseUrl is not null)gate at the sitemap site was dead code. Will be fixed in a separate, isolated commit so sitemap correctly skips whenbaseUrlis missing (matches the docs)..Permalink, Zolacurrent_url, Jekyllabsolute_url).Implementation Tasks
docs/plugins/opengraph.mdsrc/Plugins/OpenGraph/Image.Smartcrop()with attention strategy)Out of Scope (for this issue)
og_imagefrontmatter override (can add later when needed)