Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsSome links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use Pillow to create an image file, publish it at a URL that social-preview crawlers can fetch, and reference that URL in the page’s og:image metadata. The tag does not generate or host the image. This guide builds a share image in Python, saves it deliberately, and adds the Open Graph tags that describe it.
What an Open Graph image is—and what you need to provide
An Open Graph image is an image identified for a page by its og:image property. The Open Graph Protocol defines the metadata that describes the page; Pillow provides Python image operations. Your site still has to serve the finished file at a publicly reachable URL. A tag pointing to a local path on your computer, or to an image that requires a private login, does not make that file available to external crawlers.
The protocol lists four required basic properties for every page: og:title, og:type, og:image, and og:url (Open Graph Protocol). For an image, it also defines optional properties for MIME type, width, height, secure URL, and alt text. It recommends og:image:alt when an image is specified. These properties describe your asset; they do not establish that every social platform will display it in the same way.
Recommended Free Tools
Choose an image design and output format
The protocol does not prescribe a universal image dimension, layout, typeface, or file-size ceiling. Choose dimensions and composition for your own design and verify platform-specific guidance separately if a particular network is a requirement. A simple title, a short supporting line, and a restrained brand mark are often easier to read in a preview than a page screenshot crowded with small text.
#1 Best Overall
- JPEG: useful for photographic or gradient-heavy artwork when you do not need transparency. It is a lossy format, so inspect the saved result for artifacts around text and edges.
- PNG: useful when you need transparency or crisp flat-color graphics and text. It can produce larger files for some designs.
- WebP: an option when your hosting and intended consumers support it. Do not assume support or a particular preview result without checking the target platform.
Pick the format intentionally and keep the file extension, actual encoded format, and declared MIME type consistent. Pillow infers the format from the filename extension when saving unless you pass a format explicitly (Pillow image file formats). Image size in Pillow is a pixel tuple in (width, height) order (Pillow Image reference).
Generate an Open Graph image with Pillow
The example below creates a 1200-by-630 pixel JPEG with a dark background, a title, and a subtitle. That size is an example canvas, not a protocol requirement or a promise of compatibility with every service. The script accepts text from the command line, wraps it to fit, checks the final image dimensions, and saves with an explicit JPEG format.
1. Install Pillow
Use a virtual environment if this is part of a project, then install Pillow:
Rank #2
python -m venv .venv
# macOS/Linux:
source .venv/bin/activate
# Windows PowerShell:
# .venvScriptsActivate.ps1
python -m pip install Pillow
Pillow’s stable documentation is versioned and its API can change over time; check the documentation corresponding to the Pillow version installed in your environment. The documented library description is that it adds image-processing capabilities to Python (Pillow documentation).
2. Save this script as make_og.py
from __future__ import annotations
import argparse
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
WIDTH, HEIGHT = 1200, 630
BACKGROUND = "#101827"
FOREGROUND = "#ffffff"
ACCENT = "#72e0c1"
def load_font(size: int, font_path: str | None) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
"""Load a chosen TrueType/OpenType font, or Pillow's built-in default."""
if font_path:
return ImageFont.truetype(font_path, size=size)
return ImageFont.load_default(size=size)
def wrap_text(draw: ImageDraw.ImageDraw, text: str, font: ImageFont.ImageFont,
max_width: int) -> list[str]:
"""Wrap words so their measured line width does not exceed max_width."""
lines: list[str] = []
current = ""
for word in text.split():
candidate = f"{current} {word}".strip()
if current and draw.textbbox((0, 0), candidate, font=font)[2] > max_width:
lines.append(current)
current = word
else:
current = candidate
if current:
lines.append(current)
return lines
def main() -> None:
parser = argparse.ArgumentParser(description="Create a simple Open Graph image.")
parser.add_argument("--title", default="A useful page title")
parser.add_argument("--subtitle", default="A concise description for the share preview")
parser.add_argument("--output", default="public/og/example.jpg")
parser.add_argument("--font", help="Path to a .ttf or .otf font file")
args = parser.parse_args()
image = Image.new("RGB", (WIDTH, HEIGHT), BACKGROUND)
draw = ImageDraw.Draw(image)
title_font = load_font(68, args.font)
subtitle_font = load_font(30, args.font)
margin = 76
text_width = WIDTH - margin * 2
# Accent bar gives the graphic a simple visual anchor.
draw.rounded_rectangle((margin, 92, margin + 110, 104), radius=6, fill=ACCENT)
title_lines = wrap_text(draw, args.title, title_font, text_width)
subtitle_lines = wrap_text(draw, args.subtitle, subtitle_font, text_width)
title_line_height = 82
title_y = 155
for line in title_lines:
draw.text((margin, title_y), line, font=title_font, fill=FOREGROUND)
title_y += title_line_height
subtitle_y = min(title_y + 28, HEIGHT - margin - 90)
subtitle_line_height = 42
for line in subtitle_lines:
if subtitle_y + subtitle_line_height > HEIGHT - margin:
break
draw.text((margin, subtitle_y), line, font=subtitle_font, fill="#c7d2e0")
subtitle_y += subtitle_line_height
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
image.save(output, format="JPEG", quality=90, optimize=True)
with Image.open(output) as saved:
if saved.size != (WIDTH, HEIGHT):
raise RuntimeError(f"Unexpected output size: {saved.size}")
print(f"Wrote {output} ({saved.format}, {saved.size[0]}x{saved.size[1]})")
if __name__ == "__main__":
main()
If you omit --font, the script uses Pillow’s built-in default font. To control the appearance, pass a path to a TrueType or OpenType font that you are licensed to use. Font paths differ by operating system, so supply a real local path rather than relying on a path copied from another machine.
3. Run it and inspect the output
python make_og.py
--title "How to Generate Open Graph Images in Python"
--subtitle "Create the artwork, publish it, and add the page metadata"
--output public/og/python-open-graph.jpg
--font ./fonts/YourLicensedFont.ttf
Open public/og/python-open-graph.jpg in an image viewer before publishing. Check that the title fits, the subtitle remains legible at a reduced preview size, and no important text is clipped. The script wraps text by measured width, but very long unbroken strings, unusually tall titles, or a font with different metrics can still need manual layout adjustments.
Publish the image and add the page metadata
Deploy the generated image to your site’s public assets. Replace the example host and page URL below with the canonical public URLs for your own page and file. Use an absolute HTTPS image URL for broad crawler accessibility, and confirm that the deployed URL returns the image rather than an HTML error page, an access-denied response, or a redirect to a login screen.
<head>
<meta property="og:title" content="How to Generate Open Graph Images in Python">
<meta property="og:type" content="article">
<meta property="og:url" content="https://www.example.com/guides/python-open-graph-images/">
<meta property="og:image" content="https://www.example.com/og/python-open-graph.jpg">
<meta property="og:image:alt" content="A dark graphic titled How to Generate Open Graph Images in Python">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
</head>
The first four properties are the protocol’s basic required set. The image type, dimensions, and alt text are descriptive image properties; set values that match the actual published file. The protocol also defines og:image:secure_url for a secure image URL where relevant. When the ordinary image URL is already HTTPS, a second secure URL is generally unnecessary.
If you provide more than one og:image, put the preferred image first. The protocol gives preference to the first value when there is a conflict. Keep an image’s structured properties—such as its alt text, MIME type, width, and height—after the corresponding image root declaration, before declaring another image root. Do not add multiple candidates unless you have a reason to offer them.
Verify the deployed page, not just the local file
- Check the image URL directly. Open the absolute URL in a private browser window or use an HTTP client. It should be reachable without your session and return the image you generated.
- Check the page source or rendered head. Confirm that the canonical page has one intended set of Open Graph tags and that
og:imagepoints to the deployed file, not a local build path. - Check consistency. Make sure
og:image:typeagrees with the encoded file, dimensions match the generated image, and the alt text describes what the image actually shows. - Use the target platform’s current preview or debugging tool. Preview behavior, crawler access, and cache-refresh procedures are platform-specific. The Open Graph Protocol and Pillow documentation do not establish a universal platform dimension, file-size limit, or cache invalidation rule.
Troubleshooting common failures
- The preview has no image: inspect the deployed page head and verify the exact
og:imageURL. Then request that image URL without a logged-in browser session. A missing file, private asset, blocked crawler request, or incorrect path can prevent retrieval. - The preview shows an older image: first verify that the new file is live at the URL in the tag. Some services may reuse fetched preview data, but the refresh mechanism and cache lifetime depend on the platform; use its current preview tool or documentation rather than assuming a universal refresh command.
- The declared format does not match the file: align the filename or explicit Pillow save format with the metadata’s MIME type. Renaming a JPEG to
.pngdoes not convert its encoding. - The text is clipped or tiny: inspect the rendered image, shorten the copy, lower the font size, or adjust line spacing and margins. Test with the longest expected title, not only a short sample.
ModuleNotFoundError: No module named 'PIL': install Pillow in the same Python environment that runs the script withpython -m pip install Pillow.- A custom font cannot be opened: check that the path exists, that the file is a supported font, and that the running process has permission to read it. Omit
--fontto use Pillow’s default font while diagnosing the rest of the pipeline. - Image decoding or loading fails: this example creates a new image and does not load a source image. If you adapt it to use an input file, check that the file exists and that the installed Pillow build supports its format.
Performance, reliability, and maintenance
For a static article graphic, generate the image during publishing or deployment and serve the resulting file as a static asset. This avoids doing image composition on every page request. If you generate images dynamically, validate user-provided text and output paths, limit resource use, and avoid allowing arbitrary input to write outside the intended asset directory.
Keep the generated artifact and metadata in sync. If the title or artwork changes, regenerate the image, deploy it, and confirm that the page points to the current URL. A versioned filename can make asset changes explicit, but any resulting cache behavior depends on your host and the platform fetching the preview. Do not promise a particular refresh time without platform-specific evidence.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Or skip the browser setup
If the image you want is a screenshot of a page rather than a designed graphic made with Pillow, ScreenshotNeo can return a website screenshot from one GET request. It is a website screenshot API and MCP server for developers, made by Yorker Media. A screenshot can be used as an Open Graph image only when it is the visual you intend to share; it does not replace the Pillow design workflow for custom artwork.
Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for API details. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Visit ScreenshotNeo for product information, or sign up free.
Frequently Asked Questions
Does an Open Graph image have to be generated in Python?
No. Python with Pillow is one way to create the image file; the Open Graph tags refer to a publicly served image regardless of how it was made.
Will every social network display the image exactly as designed?
No universal rendering result is established by the Open Graph Protocol; check the current preview tooling for the platform where you plan to share the page.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

