Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

  • 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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<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

  1. 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.
  2. Check the page source or rendered head. Confirm that the canonical page has one intended set of Open Graph tags and that og:image points to the deployed file, not a local build path.
  3. Check consistency. Make sure og:image:type agrees with the encoded file, dimensions match the generated image, and the alt text describes what the image actually shows.
  4. 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:image URL. 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 .png does 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 with python -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 --font to 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.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Or 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.