How to Save a Matplotlib Graph as PNG in Python

A chart stuck in a pop-up window can’t go in a report. To save a Matplotlib graph as PNG, call savefig() with a .png filename before plt.show().

In this tutorial, we’ll cover the matplotlib save graph as png workflow from one file to a whole folder, including resolution, clipped legends and saving without a window.

Quick answer: the file extension picks the format, and dpi sets the sharpness:

fig.savefig("revenue.png", dpi=300, bbox_inches="tight")

I ran every script here on Matplotlib 3.11.2 on Python 3.12.5, so each output block is real.

What You Need Before You Start

You’ll need Python and Matplotlib, and our guide to installing Matplotlib covers setup. We’ll reuse one dataset, six months of made-up 2026 revenue for a Chicago coffee shop. Save it as shop_sales.py:

months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
revenue = [18450, 17920, 21300, 22875, 24610, 26140]
target = [18000, 18500, 20000, 22000, 24000, 25000]

Step 1: Save a Matplotlib Graph with savefig

We use fig.savefig() because it always targets that figure, while plt.savefig() uses whichever figure is current.

import matplotlib.pyplot as plt
from PIL import Image
from shop_sales import months, revenue

fig, ax = plt.subplots(figsize=(7, 4))
ax.bar(months, revenue, color="#2a6f97")
ax.set_title("Chicago coffee shop revenue, 2026 (USD)")

fig.savefig("revenue.png")
print("Saved:", Image.open("revenue.png").size)
plt.show()

Output:

Saved: (700, 400)
Matplotlib plot window showing a bar chart of monthly 2026 revenue for a Chicago coffee shop
The window shows the same chart that revenue.png now holds.

A 7 x 4 inch figure at the default 100 dpi gives a 700 x 400 pixel file. Change the extension and the same call writes other formats, as our post on how to save a Matplotlib graph as PDF shows.

Step 2: Set the PNG Resolution with dpi

Same chart, four resolutions. Pixel size is simply inches multiplied by dpi:

import matplotlib.pyplot as plt
from PIL import Image
from shop_sales import months, revenue

fig, ax = plt.subplots(figsize=(7, 4))
ax.bar(months, revenue, color="#2a6f97")

for dpi in (72, 100, 150, 300):
    name = f"revenue_{dpi}.png"
    fig.savefig(name, dpi=dpi)
    print(dpi, "dpi ->", Image.open(name).size)

Output:

72 dpi -> (504, 288)
100 dpi -> (700, 400)
150 dpi -> (1050, 600)
300 dpi -> (2100, 1200)
Command Prompt output listing PNG pixel sizes at 72, 100, 150 and 300 dpi
At 300 dpi the file is 2100 x 1200 pixels.

We’d use 150 for web pages and 300 for print. If your data is already an image, skip the figure and save the NumPy array as a PNG.

Step 3: Keep Legends Inside the PNG with bbox_inches

Now we add a target line and move the legend outside the axes. A default save cuts it off, while bbox_inches="tight" resizes the output to fit everything:

import matplotlib.pyplot as plt
from PIL import Image
from shop_sales import months, revenue, target

fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(months, revenue, marker="o", label="Revenue")
ax.plot(months, target, "--", label="Target")
ax.set_title("Revenue vs. target, 2026 (USD)")
ax.legend(bbox_to_anchor=(1.02, 1), loc="upper left")

fig.savefig("default.png")
fig.savefig("tight.png", bbox_inches="tight", pad_inches=0.2)
print("default:", Image.open("default.png").size)
print("tight:  ", Image.open("tight.png").size)

Output:

default: (700, 400)
tight:   (767, 393)
Before and after comparison of saved PNG files, with the legend cut off by default and fully visible using bbox_inches tight
The same figure, saved both ways.

Only the tight file, at 767 x 393 pixels, holds the whole legend. pad_inches controls the remaining margin, and there’s more on removing it in our guide to a PNG without a border.

Step 4: Save Multiple Charts to a Folder Without a Window

Same revenue, one PNG per month. The Agg backend draws to files only, so this runs on servers and in scheduled jobs:

from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from shop_sales import months, revenue

out = Path("monthly_charts")
out.mkdir(exist_ok=True)

for month, value in zip(months, revenue):
    fig, ax = plt.subplots(figsize=(4, 3))
    ax.bar([month], [value], color="#2a6f97")
    fig.savefig(out / f"revenue_{month.lower()}.png", dpi=150)
    plt.close(fig)

files = sorted(p.name for p in out.glob("*.png"))
print(len(files), "PNG files:", files[0], "...", files[-1])
print("Open figures:", len(plt.get_fignums()))

Output:

6 PNG files: revenue_apr.png ... revenue_may.png
Open figures: 0
Command Prompt output showing six PNG files saved to the folder and zero open figures
Six files saved and no figures left open.

plt.close(fig) matters in loops, because each open figure holds memory. To export one panel of a grid instead, see how to save a single axis as PNG.

Matplotlib savefig Options Cheat Sheet

GoalArgument
Sharper filedpi=300
Fit labels and legendsbbox_inches="tight"
See-through backgroundtransparent=True
Write to memoryformat="png"

Transparency has its own catches, covered in saving a PNG with a transparent background.

Can You Save a Matplotlib Figure Without Creating a File?

Yes. Pass a BytesIO buffer and set format, since there’s no extension to read. That’s handy for web apps and email attachments:

from io import BytesIO
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([1, 2, 3], [18450, 21300, 26140])

buffer = BytesIO()
fig.savefig(buffer, format="png")
print("PNG header:", buffer.getvalue()[1:4].decode())

Output:

PNG header: PNG

The buffer starts with the real PNG header, so it’s a complete image in memory.

Common Mistakes to Avoid When Saving PNG Files

Saving after show: once the window closes, the figure is gone. We reproduced it with the same sales data:

import matplotlib.pyplot as plt
from PIL import Image
from shop_sales import months, revenue

plt.bar(months, revenue)
plt.show()
plt.savefig("late.png")     # too late: this is a new, empty figure

colors = Image.open("late.png").convert("RGB").getcolors()
print("Distinct colors:", len(colors))

Output:

Distinct colors: 1

One distinct color means a blank image. Our savefig blank image fix walks through the other causes.

A missing folder: savefig() won’t create directories for you:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
try:
    fig.savefig("reports/q2/revenue.png")
except FileNotFoundError as err:
    print(type(err).__name__, "-", err.strerror)

Output:

FileNotFoundError - No such file or directory
Command Prompt output showing FileNotFoundError when saving a PNG into a folder that does not exist
Create the folder first, as Step 4 does with mkdir.

Next Steps After Exporting Your Charts

Next, bundle your charts into one report: learn to save multiple pages to a PDF.

We also recommend these export guides:

Frequently asked questions

How do I save a Matplotlib plot as a PNG?

Call fig.savefig("chart.png") or plt.savefig("chart.png") before plt.show().

Why is my saved PNG blank?

It’s usually because savefig() ran after plt.show(). Save first, then show.

What dpi should I use?

Use 150 for the web and 300 for print. Higher values make larger files.

How do I stop labels from being cut off?

Pass bbox_inches="tight" to savefig().

Can I save a chart without displaying it?

Yes. Skip plt.show(), and use the Agg backend on headless machines.

Where is savefig documented?

See the savefig reference in the official docs.

Leave a Comment