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:

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 manage.py startapp ogsig
Add your new app to INSTALLED_APPS
in core/settings.py
INSTALLED_APPS = [...'ogsig',]
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. Use whichever provider you'd like. I'm using imgBB: it's simple and free for our use case.
- Create an account at https://api.imgbb.com/
- Click on the "Get API key" button and copy your API key from https://api.imgbb.com/
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
services.py
in theogsig
app. - Add the below code to fetch your page's data and generate an image
import iofrom PIL import Image, ImageDraw, ImageFontimport requestsfrom bs4 import BeautifulSoupfrom typing import Tupleimport emojidef 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, descriptiondef calculate_text_height(draw, text, font, max_width, padding):"""Calculate the height needed for a block of text."""y = 0for word in text.split():if draw.textlength(word, font=font) > max_width - 2 * padding:y += font.sizereturn y + font.sizedef 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.sizedef 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 * paddingdef 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):continueif 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, 20img = Image.new("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 + paddingstart_y = (og_height - total_height - url_font.size) // 2next_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))img.show()# Convert the image to bytesimg_bytes_io = io.BytesIO()img.save(img_bytes_io, 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 = "https://api.imgbb.com/1/upload"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 = requests.post(url, 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
views.py
in your 'ogsig' app, and add:
from django.shortcuts import renderfrom .services import generate_og_imagedef 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 calledindex.html
:
<!doctype html><html><head><title>Generate OG Image</title></head><body><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></form></body></html>
- Inside
ogsig/templates
, create a new file calledpreview.html
:
<!doctype html><html><head><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><style>/* 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;}</style></head><body><div class="container"><h1>Your OG social image</h1><section><p>Your OG Image url is:<a href="{{ og_image.image_path }}">{{ og_image.image_path }}</a></p><p>To add the image to your website, add the following HTML into the<code><head> </code> section of your HTML page:</p><code><meta name="twitter:image" content="{{ og_image.image_path }}"/> <meta property="og:image" content="{{ og_image.image_path }}"/></code></section><section>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" /></a></section></div></body></html>
4. Update your URLs
- Create a file named
urls.py
in yourogsig
app and add:
from django.urls import pathfrom . import viewsurlpatterns = [path('', views.index, name='index'),]
- Update your
core/urls.py
from django.contrib import adminfrom django.urls import path, includeurlpatterns = [path('admin/', admin.site.urls),path('', include('ogsig.urls')),]
Visit your app
- Run your server from the terminal
python manage.py 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:
- Convert your app into a serverless function that you can call without a frontend. To do this, you could use my article here: How to add serverless functions to Django in 6 minutes (with HTMX and AWS Lambda) π§ .
- Add a screenshot of your page, perhaps by creating an automated screenshot function using AWS lambda and a headless browser (Or use https://screenshotlayer.com/)
- Change the above to write to HTML, which you then convert to an image. (Or use HTML to Image)