matplotlib legend not showing: No artists with labels (tested)

An empty legend means no artist had a label= when legend() ran (warning: No artists with labels found to put in legend). Add label=, call legend() after plotting, and use fig.legend() or ax.legend() per subplot.

When ax.legend() draws nothing, matplotlib usually says why in a warning that is easy to miss. Measured with matplotlib 3.11.2 on Python 3.13.5 (Agg backend); every output below is the unedited result of running the code shown.

1. No artists with labels found to put in legend

Code:

import warnings, matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
warnings.simplefilter("always")
fig, ax = plt.subplots()
ax.plot([1, 2, 3])                      # no label=
with warnings.catch_warnings(record=True) as w:
    leg = ax.legend()
print("warning:", w[0].message if w else None)
print("legend entries:", len(leg.get_texts()))
fig, ax = plt.subplots()
ax.plot([1, 2, 3], label="_hidden")     # leading underscore is ignored
with warnings.catch_warnings(record=True) as w:
    ax.legend()
print("underscore label warning:", bool(w))
fig, ax = plt.subplots()
ax.plot([1, 2, 3], label="a")
ax.plot([3, 2, 1], label="b")
leg = ax.legend()
print("labels:", [t.get_text() for t in leg.get_texts()])

Output:

warning: No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
legend entries: 0
underscore label warning: True
labels: ['a', 'b']

legend() with no arguments collects only artists that have a label=. Without one you get UserWarning: No artists with labels found to put in legend and an empty legend (0 entries). Labels starting with an underscore, such as "_hidden", are deliberately skipped. Fix: pass label="..." to each plot/scatter/bar call.

2. legend() before plotting, subplots, explicit handles

Code:

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.legend()                              # called BEFORE plotting
ax.plot([1, 2, 3], label="late")
print("legend before plot:", [t.get_text() for t in ax.get_legend().get_texts()] if ax.get_legend() else None)
fig, (a1, a2) = plt.subplots(1, 2)
a1.plot([1, 2], label="left")
a2.plot([2, 1], label="right")
plt.legend()                             # only the current Axes (a2)
print("plt.legend on subplots:", [t.get_text() for t in a2.get_legend().get_texts()], "a1 legend:", a1.get_legend())
fig.legend()
print("fig.legend:", [t.get_text() for t in fig.legends[0].get_texts()])
fig, ax = plt.subplots()
lines = ax.plot([1, 2], [3, 4], [1, 2], [4, 3])
ax.legend(lines, ["up", "down"])
print("explicit handles:", [t.get_text() for t in ax.get_legend().get_texts()])
fig, ax = plt.subplots()
ax.plot([1, 2], label="x")
ax.legend("ab")                          # a string, not a list
print("legend('ab'):", [t.get_text() for t in ax.get_legend().get_texts()])

Output:

/tmp/lg399/b2.py:5: UserWarning: No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
  ax.legend()                              # called BEFORE plotting
legend before plot: []
plt.legend on subplots: ['right'] a1 legend: None
fig.legend: ['left', 'right']
explicit handles: ['up', 'down']
legend('ab'): ['a']

The legend is built from the artists that exist at the moment you call it: calling it before plot() warns and stays empty even after a labelled line is added, so call legend() last. plt.legend() only targets the current Axes (the last subplot); the left subplot got no legend. Use ax.legend() per subplot or fig.legend() for one legend covering all of them. You can also pass handles and labels explicitly. Watch out for ax.legend("ab"): a string is treated as a list of labels, one character each, so the line was labelled just a, with no warning.

3. Legend outside the plot is cut off in savefig

Code:

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(4, 3))
for i in range(3):
    ax.plot([0, 1], [i, i + 1], label=f"series {i}")
ax.legend(loc="center left", bbox_to_anchor=(1, 0.5))
fig.savefig("clipped.png")
fig.savefig("tight.png", bbox_inches="tight")
from PIL import Image
print("default:", Image.open("clipped.png").size, "tight:", Image.open("tight.png").size)

Output:

default: (400, 300) tight: (473, 274)

A legend placed outside the Axes with bbox_to_anchor=(1, 0.5) falls outside a 4x3-inch figure saved at 100 dpi (400x300): it is cropped. bbox_inches="tight" grew the image to 473 px wide to include it. See matplotlib savefig dpi, size, transparent and blank images for the other savefig surprises.

Related: matplotlib not showing plot, matplotlib suppress warnings, matplotlib errorbar.

Frequently asked questions

Why is my matplotlib legend empty?

No plotted artist had a label= (or labels start with an underscore) when legend() was called. Add label= and call legend() last.

Why does plt.legend() only show one subplot?

plt.legend() targets the current Axes only. Use ax.legend() on each subplot or fig.legend() for one combined legend.

Why is the legend cut off when saving?

A legend outside the Axes is cropped by savefig; use bbox_inches="tight".

Written with AI assistance. Measured on 2026-10-23 with matplotlib 3.11.2 and CPython 3.13.5 on Linux (Agg backend). Every output is the verbatim result of running the code shown. PlainTables is our extension; matplotlib is free.