FastAPI Open Graph Meta Tags: Add OG to Your FastAPI App

How to add og:title, og:image, og:description, and Twitter card meta tags to a FastAPI application using Jinja2 templates or as a headless API with a separate frontend.

FastAPI with Jinja2 templates

If your FastAPI app renders HTML using Jinja2, inject OG meta tags in the template and pass values from your route handler:

# main.py
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/blog/{slug}", response_class=HTMLResponse)
async def read_post(request: Request, slug: str):
    post = await get_post(slug)  # your data source
    return templates.TemplateResponse("post.html", {
        "request": request,
        "title": post["title"],
        "description": post["excerpt"],
        "og_image": f"https://yourdomain.com/og/{slug}.png",
        "canonical_url": f"https://yourdomain.com/blog/{slug}",
    })
{# templates/post.html #}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>{{ title }}</title>
  <meta name="description" content="{{ description }}" />

  <meta property="og:title"        content="{{ title }}" />
  <meta property="og:description"  content="{{ description }}" />
  <meta property="og:image"        content="{{ og_image }}" />
  <meta property="og:url"          content="{{ canonical_url }}" />
  <meta property="og:type"         content="article" />

  <meta name="twitter:card"        content="summary_large_image" />
  <meta name="twitter:title"       content="{{ title }}" />
  <meta name="twitter:description" content="{{ description }}" />
  <meta name="twitter:image"       content="{{ og_image }}" />

  <link rel="canonical" href="{{ canonical_url }}" />
</head>
<body>
  <h1>{{ title }}</h1>
</body>
</html>

FastAPI as a headless API

If FastAPI is your backend API and you use a separate frontend (React, Vue, Next.js), OG meta tags must live in the frontend's server-side rendering layer — not in FastAPI itself. FastAPI returns JSON; social scrapers cannot read OG tags from JSON responses.

If you must add OG tags from FastAPI (e.g., for a specific shareable URL), return an HTML response with meta tags and a JavaScript redirect:

# Minimal OG "meta page" that redirects after scrapers read the tags
from fastapi.responses import HTMLResponse

@app.get("/share/{slug}", response_class=HTMLResponse)
async def share_redirect(slug: str):
    post = await get_post(slug)
    html = f"""<!DOCTYPE html>
<html>
<head>
  <meta property="og:title" content="{post['title']}" />
  <meta property="og:description" content="{post['excerpt']}" />
  <meta property="og:image" content="https://yourdomain.com/og/{slug}.png" />
  <meta property="og:url" content="https://yourdomain.com/blog/{slug}" />
  <meta http-equiv="refresh" content="0;url=https://yourdomain.com/blog/{slug}" />
</head>
<body>Redirecting...</body>
</html>"""
    return HTMLResponse(content=html)

Dynamic OG image generation in FastAPI

Generate OG images on-demand using Pillow or a headless browser library:

# og_image.py
from fastapi import APIRouter
from fastapi.responses import StreamingResponse
from PIL import Image, ImageDraw, ImageFont
import io

router = APIRouter()

@router.get("/og/image")
async def og_image(title: str = "My Page", description: str = ""):
    img = Image.new("RGB", (1200, 630), color=(17, 17, 17))
    draw = ImageDraw.Draw(img)

    # Use a basic font (replace with your font path in production)
    font_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 56)
    font_desc  = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 28)

    draw.text((60, 220), title, font=font_title, fill=(255, 255, 255))
    draw.text((60, 320), description, font=font_desc, fill=(156, 163, 175))

    buf = io.BytesIO()
    img.save(buf, format="PNG")
    buf.seek(0)

    return StreamingResponse(buf, media_type="image/png", headers={
        "Cache-Control": "public, max-age=86400",
    })

Then reference the endpoint as your og:image: https://yourdomain.com/og/image?title=My+Post&description=...

Common FastAPI OG mistakes

  • Returning JSON from API endpoints — social scrapers can't read OG tags from JSON. Use a Jinja2 template or a dedicated HTML meta page.
  • Relative image URLs — always use absolute URLs for og:image.
  • Missing CORS or auth on the OG image endpoint — scrapers won't follow authenticated requests. Ensure the OG image URL is publicly accessible.

Verify your FastAPI OG tags

After deploying, paste your URL into OGFixer to preview how your FastAPI app's links appear on Twitter, LinkedIn, Slack, and Discord — and catch any OG tag issues before they go live.