OG Images for Events: Drive Registrations With Better Link Previews
How to add compelling OG images to event pages — conferences, webinars, meetups, and online workshops — so every share drives more registrations.
Event OG images are conversion assets
When someone shares your event link — a conference, webinar, hackathon, or community meetup — the OG preview card is your billboard. It needs to communicate date, time, topic, and why to attend in under 2 seconds.
Event links are shared in high-noise environments (Discord channels, Twitter threads, Slack groups) where dozens of other links compete for attention. A strong OG image stops the scroll. A weak one gets ignored.
Required fields for event OG images
- Event title: The primary hook — should convey the topic clearly
- Date and time: Critical — people decide immediately if they're free
- Format: Online/in-person/hybrid — context for attendance
- Host/brand: Logo or organizer name builds trust
- Speaker(s): If notable, include name and avatar — credibility driver
- Free/paid + price: Removes a click needed to decide
Dynamic event OG image generator
// app/api/og/event/route.tsx
import { ImageResponse } from "next/og";
export const runtime = "edge";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get("title") || "Event";
const date = searchParams.get("date") || "";
const time = searchParams.get("time") || "";
const format = searchParams.get("format") || "Online"; // Online | In-Person | Hybrid
const organizer = searchParams.get("organizer") || "";
const price = searchParams.get("price") || "Free";
const speakerName = searchParams.get("speaker") || "";
const speakerAvatar = searchParams.get("avatar") || "";
// Format the date nicely
let formattedDate = date;
if (date) {
try {
formattedDate = new Date(date).toLocaleDateString("en-US", {
weekday: "short",
month: "short",
day: "numeric",
year: "numeric",
});
} catch (_) {}
}
const isOnline = format === "Online";
const locationColor = isOnline ? "#22d3ee" : "#fb923c"; // cyan : orange
return new ImageResponse(
(
<div
style={{
display: "flex",
flexDirection: "column",
background: "linear-gradient(145deg, #0f0f23 0%, #1a0a2e 100%)",
width: "100%",
height: "100%",
padding: "60px",
justifyContent: "space-between",
border: "1px solid #ffffff0d",
}}
>
{/* Top: organizer */}
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
{organizer && (
<div style={{ fontSize: 22, color: "#a1a1aa", fontWeight: 600 }}>
{organizer}
</div>
)}
<div
style={{
fontSize: 15,
color: locationColor,
background: `${locationColor}20`,
padding: "3px 12px",
borderRadius: 20,
border: `1px solid ${locationColor}44`,
}}
>
{format}
</div>
</div>
{/* Middle: title + speaker */}
<div style={{ display: "flex", flexDirection: "column", gap: 24 }}>
<div
style={{
fontSize: title.length > 55 ? 42 : 52,
fontWeight: 900,
color: "#ffffff",
lineHeight: 1.1,
}}
>
{title}
</div>
{speakerName && (
<div style={{ display: "flex", alignItems: "center", gap: 14 }}>
{speakerAvatar && (
<img
src={speakerAvatar}
style={{ width: 44, height: 44, borderRadius: "50%", objectFit: "cover" }}
alt={speakerName}
/>
)}
<div style={{ fontSize: 20, color: "#d4d4d8" }}>
with {speakerName}
</div>
</div>
)}
</div>
{/* Bottom: date + price */}
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
{formattedDate && (
<div
style={{
fontSize: 20,
color: "#e4e4e7",
background: "#1c1c2e",
padding: "8px 16px",
borderRadius: 8,
fontWeight: 600,
}}
>
📅 {formattedDate}{time ? ` · ${time}` : ""}
</div>
)}
<div
style={{
fontSize: 20,
color: price === "Free" ? "#34d399" : "#fbbf24",
background: price === "Free" ? "#064e3b" : "#451a03",
padding: "8px 16px",
borderRadius: 8,
fontWeight: 700,
}}
>
{price === "Free" ? "🎉 Free" : `🎟 ${price}`}
</div>
</div>
</div>
),
{ width: 1200, height: 630 }
);
}Event metadata and JSON-LD
// app/events/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
const event = await getEvent(params.slug);
const ogTitle = event.title;
const ogDescription = `${new Date(event.date).toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric",
})} · ${event.format} · ${event.price === 0 ? "Free" : `$${event.price}`}`;
const ogImageParams = new URLSearchParams({
title: event.title,
date: event.date,
time: event.time,
format: event.format,
organizer: event.organizer,
price: event.price === 0 ? "Free" : `$${event.price}`,
...(event.speaker && { speaker: event.speaker.name }),
...(event.speaker?.avatar && { avatar: event.speaker.avatar }),
});
return {
title: ogTitle,
description: ogDescription,
openGraph: {
title: ogTitle,
description: ogDescription,
type: "website",
images: [
{
url: `/api/og/event?${ogImageParams}`,
width: 1200,
height: 630,
},
],
},
twitter: {
card: "summary_large_image",
title: ogTitle,
description: ogDescription,
},
};
}
// Add Event JSON-LD for Google Events rich results
export default async function EventPage({ params }) {
const event = await getEvent(params.slug);
const eventJsonLd = {
"@context": "https://schema.org",
"@type": "Event",
name: event.title,
startDate: event.startDateTime,
endDate: event.endDateTime,
eventStatus: "https://schema.org/EventScheduled",
eventAttendanceMode: event.format === "Online"
? "https://schema.org/OnlineEventAttendanceMode"
: "https://schema.org/OfflineEventAttendanceMode",
location: event.format === "Online"
? { "@type": "VirtualLocation", url: event.url }
: { "@type": "Place", name: event.venue, address: event.address },
offers: {
"@type": "Offer",
price: event.price,
priceCurrency: "USD",
availability: "https://schema.org/InStock",
},
organizer: {
"@type": "Organization",
name: event.organizer,
},
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(eventJsonLd) }}
/>
{/* event content */}
</>
);
}Common event OG mistakes
- No date visible: The most important info for events — always surface it in the OG image
- Generic speaker headshot: Speaker photos at 1200×630 look too personal — use them small with text, not as the full background
- Missing timezone: For virtual events, include timezone in the og:description even if not in the image
- Post-event stale OG: Update your OG image/description after the event ends to redirect to recording
- No Eventbrite OG refresh: If using Eventbrite, verify the OG tags are correct — it generates its own which can differ from your event page
Preview your event OG cards
Before promoting your next event, paste the URL into OGFixer to verify how the preview card looks on Twitter, Discord, Slack, and LinkedIn.
Check your event OG free →