Create a open graph social image generator with Django 🌐

Want more shares and clicks online? Open Graph tags determine how your link appears when anyone shares it, including on social media sites.

We'll make a Django app in 4 steps that will:

  • fetch data about any page
  • draw a basic image with the title, description, and url
  • add with a permanent link to that image
  • show you the html to drop into your page to add your OG social image

Our final product will look like: 2023-10-06-19-26-46

Here's an optional video walkthrough (featuring me 🙂):

Let’s go 🚀

1. Setup

Install the following Python packages:

pip install django pillow requests beautifulsoup4 emoji

Create the Django project and a new app:

django-admin startproject core .
python startapp ogsig

Add your new app to INSTALLED_APPS in core/


2. Generate your social image from a URL

Add a image hosting service

We want to create a link to our generated image that is permanent. So we neeUse whichever provider you'd like. I'm using imgBB: it's simple and free for our simple use case.

Download a font for your image

We will download the Roboto font to use in our generated image.

  • Visit Google fonts - Roboto and click to 'Download family'
  • Unzip the downloaded file
  • Create a folder at ogsig/media and add the downloaded font to it (e.g. ogsig/media/Roboto-Regular.ttf)

Generate your image

  • Create in the ogsig app.
  • Add the below code to fetch your page's data and generate an image
import io
from PIL import Image, ImageDraw, ImageFont
import requests
from bs4 import BeautifulSoup
from typing import Tuple
import emoji

def fetch_page_info(url: str) -> Tuple[str, str]:
    Fetch title and description from a URL.
    response = requests.get(url)
    if response.status_code != 200:
        return "", ""
    soup = BeautifulSoup(response.content, "html.parser")
    title = soup.title.string if soup.title else ""
    description = soup.find("meta", {"name": "description"})["content"] if soup.find("meta", {"name": "description"}) else ""
    return title, description

def calculate_text_height(draw, text, font, max_width, padding):
    Calculate the height needed for a block of text.
    y = 0
    for word in text.split():
        if draw.textlength(word, font=font) > max_width - 2 * padding:
            y += font.size
    return y + font.size

def draw_line(draw, x, y, line, font, color):
    Draw a line of text at a specific position.
    draw.text((x, y), line.strip(), font=font, fill=color)
    return y + font.size

def get_words(text):
    return text.split()

def is_line_too_long(line, word, draw, font, max_width, padding):
    Check if adding a word to a line makes it too long.
    return draw.textlength(line + word, font=font) > max_width - 2 * padding

def draw_text(draw, x, y, text, font, max_width, padding, color=(0, 0, 0)):
    Draw text onto an image, avoiding emojis.
    line = ""
    for word in get_words(text):
        if emoji.is_emoji(word):
        if is_line_too_long(line, word, draw, font, max_width, padding):
            y = draw_line(draw, x + padding, y, line, font, color)
            line = ""
        line += word + " "
    return draw_line(draw, x + padding, y, line, font, color)

def create_og_image(title: str, description: str, url: str) -> str:
    Create an OpenGraph image with a title, description, and URL.
    og_width, og_height, padding = 1200, 630, 20
    img ="RGB", (og_width, og_height), (255, 255, 255))
    draw = ImageDraw.Draw(img)
    title_font = ImageFont.truetype("ogsig/media/Roboto-Regular.ttf", 60)
    desc_font = ImageFont.truetype("ogsig/media/Roboto-Regular.ttf", 45)
    url_font = ImageFont.truetype("ogsig/media/Roboto-Regular.ttf", 30)

    title_height = calculate_text_height(draw, title, title_font, og_width, padding)
    desc_height = calculate_text_height(draw, description, desc_font, og_width, padding)
    total_height = title_height + desc_height + padding
    start_y = (og_height - total_height - url_font.size) // 2

    next_y = draw_text(draw, 0, start_y, title, title_font, og_width, padding)
    draw_text(draw, 0, next_y, description, desc_font, og_width, padding)
    draw.text((padding, og_height - url_font.size - padding), url, font=url_font, fill=(0, 0, 255))

    # Convert the image to bytes
    img_bytes_io = io.BytesIO(), format='PNG')
    img_bytes = img_bytes_io.getvalue()

    uploaded_image_info = upload_image(img_bytes)
    return uploaded_image_info["data"]["url"]

def upload_image(image_bytes: bytes) -> dict:
    Upload an image to get a permanent URL for our OpenGraph image.
    url = ""
    params = {
        "key": "ADD_YOUR_API_KEY"
    }  # Move this key to an environment variable if uploading the code or sharing it with others.
    files = {"image": image_bytes}
    response =, params=params, files=files)

    if response.status_code != 200:
        raise ValueError(f"Failed to upload image: {response.content}")
    return response.json()

def generate_og_image(url: str) -> dict:
    Generate OpenGraph image and return details.
    title, description = fetch_page_info(url)
    img_path = create_og_image(title, description, url)
    return {"title": title, "description": description, "image_path": img_path}

3. Create a frontend

  • Open or create in your 'ogsig' app, and add:
from django.shortcuts import render
from .services import generate_og_image

def index(request):
    if request.method == 'POST':
        url = request.POST.get('url')
        if url:
            og_image_data = generate_og_image(url)
            print(f'{og_image_data = }')
            return render(request, 'preview.html', {
                'og_image': og_image_data, 'page_url': url

    return render(request, 'index.html')
  • Create a folder called templates inside your "ogsig" app if it doesn't already exist.
  • Inside that ogsig/templates, create a new file called index.html:
<!DOCTYPE html>
    <title>Generate OG Image</title>
    <h1>Generate Open Graph Social Image</h1>
    <form method="post" action="">
        {% csrf_token %}
        <label for="url">Enter URL:</label>
        <input type="text" id="url" name="url">
        <button type="submit">Generate</button>

  • Inside ogsig/templates, create a new file called preview.html:
<!DOCTYPE html>
    <meta property="og:title" content="{{ og_image.title }}" />
    <meta property="og:description" content="{{ og_image.description }}" />
    <meta property="og:image" content="{{ og_image.image_path }}" />
    <meta property="og:url" content="{{ og_image.url }}" />
    <meta name="twitter:card" content="summary_large_image">
    <title>Generated preview</title>
        /* Reset some default browser styles */
        body, h1, p, a, code {
            margin: 0;
            padding: 0;
            font-family: Arial, sans-serif;

        /* Apply a background color */
        body {
            background: #f9f9f9;
            color: #333;
            line-height: 1.6;

        /* Wrap all page content */
        .container {
            max-width: 900px;
            margin: 0 auto;
            padding: 20px;

        /* Add some spacing and formatting to headers */
        h1 {
            padding: 10px;
            background-color: #333;
            color: #fff;
            text-align: center;

        /* Style the section */
        section {
            margin-bottom: 20px;
            padding: 20px;
            background-color: #fff;
            border: 1px solid #ddd;
            border-radius: 8px;

        /* Style the image */
        img {
            max-width: 100%;
            height: auto;
            border: 1px solid #ddd;
            margin: 10px 0 0 0;

        /* Style the code block */
        code {
            display: block;
            padding: 10px;
            background-color: #eee;
            border: 1px solid #ddd;
            border-radius: 4px;

        /* Style the link */
        a {
            color: #0077cc;
            text-decoration: none;

        a:hover {
            text-decoration: underline;
<div class="container">
    <h1>Your OG social image</h1>
            Your OG Image url is: <a href="{{ og_image.image_path }}">{{ og_image.image_path }}</a>
            To add the image to your website, add the following HTML into the <code>&lt;head&gt; </code> section of your HTML page:
            &lt;meta name="twitter:image" content="{{ og_image.image_path }}" /&gt;<br>
            &lt;meta property="og:image" content="{{ og_image.image_path }}" /&gt;
        Your generated OG social image for: <a href="{{ page_url }}" target="_blank">{{ page_url }}</a>
        <a href="{{ og_image.image_path }}" target="_blank">
            <img src="{{ og_image.image_path }}" alt="OG Image">

4. Update your URLs

  • Create a file named urls.pyin your ogsig app and add:
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
  • Update your core/
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('', include('ogsig.urls')),

Visit your app

  • Run your server from the terminal
python runserver
  • Submit a url to generate an OG social image. You can then insert this into your page HTML to add the OG social image.

Finished 🎉

Congrats - you’ve now created a Django app that visits a page and then creates an social sharing image.

Ideas to expand your app further:

P.S Build your Django frontend even faster

I want to release high-quality products as soon as possible. Probably like you, I want to make my Django product ideas become reality as soon as possible.

That's why I built Photon Designer - an entirely visual editor for building Django frontend at the speed that light hits your eyes. Photon Designer outputs neat, clean Django templates.

Let's get visual.

Do you want to create beautiful django frontends effortlessly?
Click below to book your spot on our early access mailing list (as well as early adopter prices).