Open Graph (OG) protocol meta tags — defined by Facebook in 2010 and now consumed by every major social platform, Slack, Discord, WhatsApp, iMessage, and LinkedIn — turn ordinary web pages into structured social-share objects by providing <meta property="og:title">, <meta property="og:description">, <meta property="og:image">, <meta property="og:url">, and <meta property="og:type"> tags in the document <head>. Twitter (X) Cards are a parallel system — Twitter scrapers read OG tags first but prefer Twitter-specific <meta name="twitter:card"> and <meta name="twitter:image"> tags when both are present. The most impactful card type is summary_large_image, which renders a full-width image with title and description below — it produces significantly higher click-through rates in social feeds than summary (small thumbnail). WordPress does not output any OG or Twitter meta tags by default — every plugin-free WordPress theme serves pages without social metadata, meaning every share shows only the URL with no image, title, or description preview. The correct hook for injecting <meta> tags is wp_head — output buffering is not needed; echo each <meta> tag directly. Image requirements: Facebook recommends 1200×630px, minimum 600×315px — images below 300px in either dimension are not shown; Twitter summary_large_image requires minimum 300×157px and recommends 1200×600px with a 2:1 ratio; maximum file size for both is 5MB for the scraper to process. The canonical URL in og:url should always be the full absolute URL including protocol and trailing slash if that is the canonical form — WordPress’s get_permalink() returns the correct canonical URL for posts and pages, but home_url( ‘/’ ) should be used for the front page and get_term_link() for taxonomy archives. The Structured Data JSON-LD post covered machine-readable content for search engines; Open Graph meta covers machine-readable content for social network scrapers — both use wp_head and are complementary without overlap.
Problem: A WordPress recipe blog’s posts share as bare URLs on Facebook, Pinterest, and Slack with no image, no recipe name, and no description — the social platforms cannot determine which image to use because the page has no OG meta tags and multiple images in the post content. Click-through rate from social shares is low.
Solution: Add a wp_head hook that outputs complete OG and Twitter Card meta tags using the post featured image as og:image, the post excerpt or auto-generated description as og:description, and falls back gracefully for archives, the home page, and custom post types.
// functions.php (or a plugin file)
add_action( 'wp_head', 'theme_output_social_meta_tags', 5 );
function theme_output_social_meta_tags(): void {
global $post;
// ── Determine title, description, URL, image ──────────────────────────
$title = '';
$description = '';
$url = '';
$image_url = '';
$image_w = 0;
$image_h = 0;
$og_type = 'website';
if ( is_singular() && isset( $post ) ) {
$og_type = 'article';
$title = wp_strip_all_tags( get_the_title( $post ) );
$url = get_permalink( $post );
// Prefer manually-written excerpt; fallback: first 160 chars of content
if ( $post->post_excerpt ) {
$description = wp_strip_all_tags( $post->post_excerpt );
} else {
$description = wp_trim_words( wp_strip_all_tags( $post->post_content ), 30, '' );
}
// Featured image at 1200×630 if it exists
if ( has_post_thumbnail( $post ) ) {
$thumb = wp_get_attachment_image_src( get_post_thumbnail_id( $post ), 'og-image' );
if ( $thumb ) {
[ $image_url, $image_w, $image_h ] = $thumb;
}
}
// Fallback to first image attached to the post
if ( ! $image_url ) {
$attachments = get_posts( [
'post_type' => 'attachment',
'post_mime_type' => 'image',
'post_parent' => $post->ID,
'numberposts' => 1,
'fields' => 'ids',
] );
if ( $attachments ) {
$src = wp_get_attachment_image_src( $attachments[0], 'large' );
if ( $src ) [ $image_url, $image_w, $image_h ] = $src;
}
}
} elseif ( is_front_page() ) {
$title = wp_strip_all_tags( get_bloginfo( 'name' ) );
$description = wp_strip_all_tags( get_bloginfo( 'description' ) );
$url = home_url( '/' );
} elseif ( is_category() || is_tag() || is_tax() ) {
$term = get_queried_object();
$title = wp_strip_all_tags( single_term_title( '', false ) );
$description = wp_strip_all_tags( term_description( $term ) );
$url = get_term_link( $term );
} elseif ( is_author() ) {
$author = get_queried_object();
$title = esc_html( $author->display_name );
$description = wp_strip_all_tags( get_the_author_meta( 'description', $author->ID ) );
$url = get_author_posts_url( $author->ID );
}
// Fallback: site default OG image (set in Customizer or hardcoded)
if ( ! $image_url ) {
$default = get_theme_mod( 'og_default_image' );
if ( $default ) {
$image_url = esc_url( $default );
$image_w = 1200;
$image_h = 630;
}
}
// Truncate description to 160 characters
if ( strlen( $description ) > 160 ) {
$description = substr( $description, 0, 157 ) . '...';
}
// ── Output tags ───────────────────────────────────────────────────────
if ( ! $title || ! $url ) return;
echo "
";
printf( '' . "
", esc_attr( $og_type ) );
printf( '' . "
", esc_attr( $title ) );
printf( '' . "
", esc_url( $url ) );
printf( '' . "
", esc_attr( get_bloginfo( 'name' ) ) );
if ( $description ) {
printf( '' . "
", esc_attr( $description ) );
}
if ( $image_url ) {
printf( '' . "
", esc_url( $image_url ) );
if ( $image_w ) printf( '' . "
", (int) $image_w );
if ( $image_h ) printf( '' . "
", (int) $image_h );
}
echo "
";
$card_type = $image_url ? 'summary_large_image' : 'summary';
printf( '' . "
", esc_attr( $card_type ) );
printf( '' . "
", esc_attr( $title ) );
if ( $description ) {
printf( '' . "
", esc_attr( $description ) );
}
if ( $image_url ) {
printf( '' . "
", esc_url( $image_url ) );
}
// Optional: add your Twitter/X handle
$twitter_handle = get_theme_mod( 'twitter_handle' );
if ( $twitter_handle ) {
printf( '' . "
", esc_attr( '@' . ltrim( $twitter_handle, '@' ) ) );
}
echo "
";
}
// ── Register a custom image size for OG images (1200×630, hard-cropped) ───────
add_action( 'after_setup_theme', function(): void {
add_image_size( 'og-image', 1200, 630, true );
} );
NOTE: Facebook and LinkedIn cache OG meta tag snapshots aggressively — when you update a post’s featured image or title, the old preview continues showing in social shares until the cache expires (typically 24–72 hours) or is manually cleared. Facebook’s cache can be cleared at developers.facebook.com/tools/debug by entering the URL and clicking “Scrape Again”. LinkedIn’s Post Inspector at linkedin.com/post-inspector provides the same functionality. Also, the custom image size og-image (1200×630) is only generated for images uploaded after you register the size with add_image_size() — existing featured images need to be regenerated with WP-CLI: wp media regenerate --only-missing to create the 1200×630 crop for all existing attachments.