Flask Open Graph Meta Tags: Add OG Tags to Your Python App

Set og:title, og:image, og:description and Twitter card tags in Flask using Jinja2 templates, dynamic routes, and server-side OG image generation.

How OG tags work in Flask

Flask renders HTML server-side via Jinja2 templates. Open Graph tags go in the <head> section of your base layout. You pass OG values as template variables from your route handler — no special library required.

Step 1: Base layout with OG tag block

{# templates/base.html #}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>{{ og_title | default(site_name) }}</title>

  {# Open Graph #}
  <meta property="og:type"        content="{{ og_type | default('website') }}" />
  <meta property="og:title"       content="{{ og_title | default(site_name) }}" />
  <meta property="og:description" content="{{ og_description | default('') }}" />
  <meta property="og:image"       content="{{ og_image }}" />
  <meta property="og:url"         content="{{ og_url | default(request.url) }}" />

  {# Twitter Card #}
  <meta name="twitter:card"        content="summary_large_image" />
  <meta name="twitter:title"       content="{{ og_title | default(site_name) }}" />
  <meta name="twitter:description" content="{{ og_description | default('') }}" />
  <meta name="twitter:image"       content="{{ og_image }}" />
</head>
<body>{% block content %}{% endblock %}</body>
</html>

Step 2: Pass OG values from your route

# app.py
from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/post/<slug>')
def post(slug):
    post = get_post_from_db(slug)
    return render_template('post.html',
        og_title=post.title,
        og_description=post.excerpt,
        og_image=f'https://yourdomain.com/og/{slug}.png',
        og_url=request.url,
        og_type='article',
    )

@app.route('/og/<slug>.png')
def og_image(slug):
    # Generate dynamic OG image with Pillow
    from PIL import Image, ImageDraw, ImageFont
    import io

    post = get_post_from_db(slug)
    img = Image.new('RGB', (1200, 630), color=(10, 10, 10))
    draw = ImageDraw.Draw(img)
    font = ImageFont.truetype('fonts/Inter-Bold.ttf', 56)
    draw.text((60, 240), post.title, fill='white', font=font)

    buf = io.BytesIO()
    img.save(buf, format='PNG')
    buf.seek(0)
    from flask import send_file
    return send_file(buf, mimetype='image/png',
                     max_age=3600)

Using Flask-Meld or blueprints for larger apps

In larger Flask apps using blueprints, define a g.og object in a before-request hook and read it in your base template:

# In a blueprint before_request or app-level context processor
@app.context_processor
def inject_og_defaults():
    return dict(
        og_title='My App',
        og_description='Default site description',
        og_image='https://yourdomain.com/og/default.png',
        og_url=request.url,
    )

# Then in a specific route, just override the keys you need:
return render_template('post.html',
    og_title=post.title,
    og_description=post.excerpt,
    og_image=f'https://yourdomain.com/og/{post.slug}.png',
)

Common Flask OG mistakes

  • Relative og:image URLs: Always use absolute URLs. url_for('static', filename='og.png', _external=True) is the Flask-idiomatic way.
  • Missing og:type: Without it, scrapers may misclassify your content. Always set it explicitly.
  • Jinja2 auto-escaping: Flask auto-escapes HTML in templates — if your titles contain & they'll be double-escaped. Use the |e filter only when needed, not on values that are already escaped.

Verify your Flask OG tags

After deploying, paste your URL into OGFixer to preview exactly how Twitter, Slack, Discord, and LinkedIn will render your links.

Related guides