OG Tag Checker: Validate Open Graph Tags Before You Share

February 21, 2026 · 10 min read

You publish a blog post, share it on Twitter, and... the preview card is broken. No image, a truncated title, or the wrong description. Sound familiar? Open Graph tags control how your content appears when shared on social platforms, and getting them wrong means fewer clicks.

An OG tag checker validates these meta tags before your content goes live, catching issues like missing images, incorrect dimensions, or absent descriptions. In this guide, we'll build one from scratch using an API, and show you exactly which tags matter and how to validate them programmatically.

What Are Open Graph Tags?

Open Graph (OG) is a protocol created by Facebook that lets you control how URLs are previewed when shared on social media. When someone pastes your link into Twitter, Slack, LinkedIn, or iMessage, the platform reads your OG tags to build a rich preview card.

The essential tags are:

TagPurposeRequired?
og:titleThe title shown in the cardYes
og:descriptionA short summary (usually 1-2 sentences)Strongly recommended
og:imageThe preview image URLYes (for rich cards)
og:urlThe canonical URLRecommended
og:typeContent type (article, website, etc.)Recommended
og:site_nameYour site/brand nameOptional

Twitter also uses its own twitter:card tags, but falls back to OG tags when they're missing. So getting your OG tags right covers most platforms automatically.

Common OG Tag Mistakes

Before we build a checker, here are the issues you're looking for:

Extracting OG Tags with an API

The fastest way to check OG tags is to use a meta tag extraction API. MetaPeek (part of the GrabShot suite) extracts all meta tags from any URL, including Open Graph, Twitter Card, and standard HTML meta tags.

curl

curl "https://metapeek.grabshot.dev/api/extract?url=https://example.com" \
  -H "X-API-Key: YOUR_API_KEY"

The response includes all OG tags found on the page:

{
  "url": "https://example.com",
  "meta": {
    "og:title": "Example Domain",
    "og:description": "This domain is for illustrative examples.",
    "og:image": "https://example.com/og-image.png",
    "og:url": "https://example.com",
    "og:type": "website",
    "twitter:card": "summary_large_image"
  },
  "title": "Example Domain",
  "description": "This domain is for illustrative examples."
}

Building an OG Tag Validator in Node.js

Let's build a complete checker that extracts tags and validates them against best practices:

const https = require('https');

const API_KEY = 'YOUR_API_KEY';
const METAPEEK_URL = 'https://metapeek.grabshot.dev/api/extract';

async function fetchMeta(url) {
  const endpoint = `${METAPEEK_URL}?url=${encodeURIComponent(url)}`;
  const res = await fetch(endpoint, {
    headers: { 'X-API-Key': API_KEY }
  });
  return res.json();
}

function validateOgTags(data) {
  const issues = [];
  const warnings = [];
  const meta = data.meta || {};

  // Required tags
  if (!meta['og:title']) {
    issues.push('Missing og:title');
  } else if (meta['og:title'].length > 70) {
    warnings.push(`og:title is ${meta['og:title'].length} chars (recommended: under 70)`);
  }

  if (!meta['og:image']) {
    issues.push('Missing og:image - share cards will have no preview image');
  } else {
    if (!meta['og:image'].startsWith('https://')) {
      issues.push('og:image should be an absolute HTTPS URL');
    }
  }

  if (!meta['og:description']) {
    warnings.push('Missing og:description - platforms will guess from page content');
  } else if (meta['og:description'].length > 160) {
    warnings.push(`og:description is ${meta['og:description'].length} chars (recommended: under 160)`);
  }

  // Recommended tags
  if (!meta['og:url']) {
    warnings.push('Missing og:url - recommended for canonical URL');
  }

  if (!meta['og:type']) {
    warnings.push('Missing og:type - defaults to "website"');
  }

  // Twitter-specific
  if (!meta['twitter:card'] && !meta['og:image']) {
    issues.push('No twitter:card or og:image - Twitter will show a plain link');
  }

  return {
    url: data.url,
    tags: meta,
    issues,
    warnings,
    score: issues.length === 0
      ? (warnings.length === 0 ? 'perfect' : 'good')
      : 'needs-fix'
  };
}

// Usage
(async () => {
  const url = process.argv[2] || 'https://grabshot.dev';
  console.log(`Checking OG tags for: ${url}\n`);

  const data = await fetchMeta(url);
  const result = validateOgTags(data);

  console.log('Tags found:');
  for (const [key, value] of Object.entries(result.tags)) {
    if (key.startsWith('og:') || key.startsWith('twitter:')) {
      console.log(`  ${key}: ${value}`);
    }
  }

  if (result.issues.length > 0) {
    console.log('\n❌ Issues:');
    result.issues.forEach(i => console.log(`  - ${i}`));
  }

  if (result.warnings.length > 0) {
    console.log('\n⚠️  Warnings:');
    result.warnings.forEach(w => console.log(`  - ${w}`));
  }

  console.log(`\nScore: ${result.score}`);
})();

Python Version

Here's the same checker in Python:

import requests
import sys

API_KEY = "YOUR_API_KEY"
METAPEEK_URL = "https://metapeek.grabshot.dev/api/extract"

def fetch_meta(url):
    resp = requests.get(
        METAPEEK_URL,
        params={"url": url},
        headers={"X-API-Key": API_KEY}
    )
    resp.raise_for_status()
    return resp.json()

def validate_og_tags(data):
    meta = data.get("meta", {})
    issues = []
    warnings = []

    # Check og:title
    title = meta.get("og:title", "")
    if not title:
        issues.append("Missing og:title")
    elif len(title) > 70:
        warnings.append(f"og:title is {len(title)} chars (recommended: under 70)")

    # Check og:image
    image = meta.get("og:image", "")
    if not image:
        issues.append("Missing og:image - no preview image on social shares")
    elif not image.startswith("https://"):
        issues.append("og:image must be an absolute HTTPS URL")

    # Check og:description
    desc = meta.get("og:description", "")
    if not desc:
        warnings.append("Missing og:description")
    elif len(desc) > 160:
        warnings.append(f"og:description is {len(desc)} chars (recommended: under 160)")

    # Check og:url
    if not meta.get("og:url"):
        warnings.append("Missing og:url")

    # Check twitter:card
    if not meta.get("twitter:card") and not image:
        issues.append("No twitter:card or og:image for Twitter previews")

    return {"issues": issues, "warnings": warnings, "meta": meta}

if __name__ == "__main__":
    url = sys.argv[1] if len(sys.argv) > 1 else "https://grabshot.dev"
    print(f"Checking OG tags for: {url}\n")

    data = fetch_meta(url)
    result = validate_og_tags(data)

    for key, val in result["meta"].items():
        if key.startswith(("og:", "twitter:")):
            print(f"  {key}: {val}")

    if result["issues"]:
        print("\n❌ Issues:")
        for i in result["issues"]:
            print(f"  - {i}")

    if result["warnings"]:
        print("\n⚠️  Warnings:")
        for w in result["warnings"]:
            print(f"  - {w}")

    status = "needs-fix" if result["issues"] else "good"
    print(f"\nStatus: {status}")

Validating the OG Image

Checking that og:image exists in the HTML is only half the battle. You also need to verify the image actually loads and meets size requirements. Here's how to add image validation:

async function validateOgImage(imageUrl) {
  try {
    const res = await fetch(imageUrl, { method: 'HEAD' });

    if (!res.ok) {
      return { valid: false, error: `Image returns ${res.status}` };
    }

    const contentType = res.headers.get('content-type');
    if (!contentType || !contentType.startsWith('image/')) {
      return { valid: false, error: `Not an image: ${contentType}` };
    }

    const size = parseInt(res.headers.get('content-length') || '0');
    if (size > 8 * 1024 * 1024) {
      return { valid: false, error: 'Image exceeds 8MB (Facebook limit)' };
    }

    return { valid: true, contentType, size };
  } catch (err) {
    return { valid: false, error: err.message };
  }
}

For full image dimension checking, you can combine this with a screenshot API to capture how the share card actually renders, giving you a pixel-perfect preview.

Generating a Visual Preview

Numbers and validation rules are useful, but what teams really want is to see how the link will look when shared. You can generate a visual preview by capturing a screenshot of a share card mockup:

# Capture how your page looks as a Twitter card preview
curl "https://grabshot.dev/api/screenshot?url=https://cards-dev.twitter.com/validator&width=600&height=400&format=png" \
  -H "X-API-Key: YOUR_API_KEY" \
  --output twitter-preview.png

Or build a simple HTML template that renders the OG data as a card, then screenshot that template with GrabShot:

# Pass your card template with dynamic OG data
curl -X POST "https://grabshot.dev/api/screenshot" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<div style=\"width:600px;height:315px;background:#fff;font-family:sans-serif;padding:20px\"><img src=\"OG_IMAGE_URL\" style=\"width:100%;height:200px;object-fit:cover\"><h3>OG_TITLE</h3><p style=\"color:#666\">example.com</p></div>",
    "width": 600,
    "height": 315,
    "format": "png"
  }' \
  --output card-preview.png

Check Your OG Tags Now

Use MetaPeek to extract and validate Open Graph tags from any URL. Free tier includes 25 requests per month.

Try MetaPeek Free

Automating OG Tag Checks in CI/CD

The best time to catch broken OG tags is before deployment. Add a check to your CI pipeline:

# .github/workflows/og-check.yml
name: OG Tag Validation
on: [pull_request]

jobs:
  check-og-tags:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Start preview server
        run: npm start &

      - name: Validate OG tags
        run: |
          URLS=$(find public -name "*.html" | sed 's|public|http://localhost:3000|')
          for url in $URLS; do
            echo "Checking: $url"
            RESULT=$(curl -s "https://metapeek.grabshot.dev/api/extract?url=$url" \
              -H "X-API-Key: ${{ secrets.METAPEEK_KEY }}")

            # Check for og:title
            TITLE=$(echo $RESULT | jq -r '.meta["og:title"] // empty')
            if [ -z "$TITLE" ]; then
              echo "❌ Missing og:title on $url"
              exit 1
            fi

            # Check for og:image
            IMAGE=$(echo $RESULT | jq -r '.meta["og:image"] // empty')
            if [ -z "$IMAGE" ]; then
              echo "❌ Missing og:image on $url"
              exit 1
            fi

            echo "✅ $url - OK"
          done

This catches missing OG tags on every pull request, so broken share cards never make it to production.

OG Tag Checklist

Use this as a quick reference when auditing any page:

  1. og:title present and under 70 characters
  2. og:description present and under 160 characters
  3. og:image is an absolute HTTPS URL
  4. og:image resolves (no 404) and is an actual image
  5. Image dimensions are at least 1200x630px (for large cards)
  6. Image file size under 8MB
  7. og:url matches the canonical URL
  8. twitter:card is set (summary_large_image for posts with images)
  9. No mixed HTTP/HTTPS content in image URLs
  10. Tags are in the <head>, not the <body>

Wrapping Up

Broken OG tags are one of those invisible problems that silently hurt your click-through rates. Every shared link without a proper preview card is a missed opportunity. Building an automated OG tag checker into your workflow, whether as a standalone script or a CI/CD step, takes minutes and saves you from discovering issues only after the link is already live.

The combination of MetaPeek for tag extraction and GrabShot for visual previews gives you both the data and the visual confirmation that your share cards look right. Start with the free tier and automate from there.