Skip to content

OGP (Open Graph Protocol) Basics — How Social Media Decides What a Shared Link Looks Like

Paste a URL into X (formerly Twitter) or Facebook, and a card appears: a title, a description, and an image, neatly assembled. That is not automatic guesswork — the page itself publishes metadata called OGP (Open Graph Protocol), and the platform’s crawler reads it to build the card. Following the previous post on JSON-LD, this one covers OGP, another piece of metadata that lives in <head>, using this blog’s implementation (wpmm.jp/blog and en.wpmm.jp/blog) as the example.

Note: OGP was proposed by Facebook in 2010. The name reflects the idea of treating a page as part of a graph — a network of connected nodes. Today it has become a de facto standard: many platforms beyond Facebook (X, LINE, Slack, and others) read the same meta tags to build their own preview cards.

The Card Is Just <meta> Tags

OGP is not exotic technology — it is a set of <meta property="og:..."> tags in <head>. Four of them are enough to form a basic card:

Property Role
og:title The title shown on the card
og:description The description shown on the card
og:image The URL of the image shown on the card
og:url The page’s canonical URL, recorded as the share source

When a URL is posted or sent, the platform’s crawler fetches that page and reads these tags to assemble the card. This is entirely separate from how the page renders in a browser — it is preview data built specifically for sharing.

og:type — What Kind of Page Is This

og:type declares what the page represents. A blog article uses article; a general page such as a homepage uses website.

<meta property="og:type" content="article" />

Setting article also enables article-specific properties such as article:published_time and article:author. This blog outputs og:type="article" on individual post pages, and og:type="website" everywhere else (homepage, archives, search results, and so on).

Image Dimensions Matter More Than They Look

Declaring og:image:width and og:image:height alongside og:image lets a crawler settle the layout before it even fetches the image, which tends to make card assembly more reliable. A widely used size across platforms is roughly 1200×630 (landscape). This blog’s generated OGP images use that same size.

<meta property="og:image"        content="https://wpmm.jp/blog/wp-content/uploads/ogp/post-52-ja-20260922010000.png" />
<meta property="og:image:width"  content="1200" />
<meta property="og:image:height" content="630" />

For X specifically, it’s also worth adding twitter:card (set to summary_large_image for a large-image card), plus twitter:title / twitter:description / twitter:image. X prioritizes its own twitter: prefixed tags, so relying on OGP alone can result in a smaller card or unexpected content.

Why the Image URL Isn’t Always the Same — This Blog’s Four-Step Fallback

This blog automatically generates an image with the article’s title baked in, for posts that have no featured image set. What og:image returns follows this priority order:

  1. If a featured image is explicitly set on the post, use it — highest priority
  2. For an individual post, if a pre-generated OGP image already exists on disk (cached), return that cached file’s static URL
  3. For an individual post with no cache yet, point to the on-the-fly generation engine with a query-string URL (fallback)
  4. For anything else (homepage, archives, 404, etc.), use the site’s shared default image
// Simplified pseudocode
function wpmm_blog_ogp_image_url() {
    if ( is_singular() && has_post_thumbnail() ) {
        return get_the_post_thumbnail_url(); // 1) featured image first
    }
    if ( is_singular( 'post' ) ) {
        $cached = find_cached_ogp_png( get_the_ID() );
        if ( $cached ) {
            return $cached;               // 2) static cached URL
        }
        return generator_url_with_query(); // 3) fall back to dynamic generation
    }
    return $default_ogp_image_url;         // 4) site-wide default
}

The reason steps 2 and 3 are kept separate is that a query-string URL (a dynamic URL) is not always handled correctly as an image by every social platform’s crawler. This blog actually hit that problem once: an X post went out pointing at a dynamic URL, and the crawler failed to load the image, leaving a small text-only card that never updated afterward. The fix was to have the server call the image-generation engine internally, once, at publish or update time, and save the result to disk as a file. By the time the post is shared, a static PNG with no query string already exists, and og:image can return that static URL directly. The dynamic URL only comes into play as a safety net, when cache generation has somehow failed.

Caching and Content-Type Are Part of This Too

The engine that serves the OGP image sets Content-Type: image/png explicitly and applies a fairly long Cache-Control duration. Social platforms’ own crawlers often cache what they fetch for a while too, which means a single failed fetch (a cardless card) can linger even after the underlying problem is fixed. The HTTP cache header concepts covered a few posts back apply just as directly to serving OGP images.

og:url Pins Down the “Share Source”

og:url should hold the canonical URL — no trailing slash inconsistencies, no stray query parameters. If that value drifts, the same article can end up split across multiple URLs for share-count or like-count purposes, as far as the platform is concerned. Using the same value as the canonical URL is the safe default.

// Strip the query string before using it for archives, 404s, etc.
$ogp_url = strtok( $_SERVER['REQUEST_URI'], '?' );

Verifying After Publishing

Like hreflang and JSON-LD, OGP tags are invisible on the rendered page, so gaps are easy to miss. A few ways to check:

  • View the page source and confirm the og: tags carry the expected values
  • Fetch just the og:image URL with curl and check whether it still carries a query string (a sign it fell back to the dynamic URL)
  • Use a platform’s own share-preview tool to see the actual card

On this blog, the SEO check script audit.py mechanically verifies that og:type / og:title / og:description / og:url / og:image are all present after each publish.

Common Pitfalls

  • og:image stays a dynamic URL (with a query string): more prone to failed fetches by a platform’s crawler, which tends to produce cardless cards
  • og:image:width / height are missing: not strictly required, but layout tends to settle later, making the card less predictable
  • A query string leaks into og:url: the same article gets tracked as several different URLs
  • A platform caches a failed card for a long time: a fix does not always show up right away. Without an official way to force a recrawl, a workaround is to re-share a different URL (for example, one with an added query string)

Summary

OGP is share-specific summary data embedded in a page as <meta property="og:..."> tags. og:title, og:description, og:image, and og:url form the baseline; og:type declares what kind of page it is, and explicit image dimensions help the layout settle predictably. Because a static image URL and a dynamic one are not treated the same way by every platform’s crawler, favoring a pre-generated static URL — and keeping dynamic generation as a fallback for failure cases only — goes a long way toward avoiding broken share cards.