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 →