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 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):
continue
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 = 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 + 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))
img.show()
# Convert the image to bytes
img_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 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 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 }}" /><br>
<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 path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
- Update your
core/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
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)
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.