Backends and environment setup
What a backend actually is, how to choose between interactive and headless, and how to make charts work in notebooks, servers and CI.
What a backend is
Matplotlib separates the plot description from the thing that draws it. The backend is the renderer: an interactive one opens a window (Qt, Tk, macOS), a non-interactive one writes a file (Agg). Every "my plot does not appear" problem is really a backend question.
import matplotlib
matplotlib.use("Agg") # must run BEFORE pyplot is imported
import matplotlib.pyplot as plt
matplotlib.get_backend() # what is active right now
# inside a Jupyter notebook, instead of a backend call
# %matplotlib inline # static PNG in the output cell
# %matplotlib widget # interactive canvas, needs ipympl installed| Backend | Produces | Use for |
|---|---|---|
| Agg | PNG/PDF/SVG files | Scripts, servers, CI, batch jobs |
| QtAgg / TkAgg | A window | Local exploration and pan/zoom |
| inline (notebook) | Static image in the cell | Reports and tutorials |
| widget / ipympl | Live canvas in the cell | Interactive analysis in Jupyter |
| pdf, svg, ps | Vector files only | Print pipelines |
Choosing a backend per environment
# headless machine: force a file backend before Python starts
export MPLBACKEND=Agg
# one-off script
MPLBACKEND=Agg python make_charts.py
# a GUI toolkit must be installed for interactive backends
pip install matplotlib PyQt6 # or use tkinter from the standard library
pip install ipympl # for the widget backend in Jupyter- In a script, call
matplotlib.use("Agg")before importing pyplot and forget about it. - In a notebook, use the
%matplotlibmagic rather than a backend call. - Set
MPLBACKENDin CI so a missing display cannot fail the build. - If a window flashes and vanishes, the script ended before the GUI loop ran: call
plt.show()last. - Interactive backends are for you, not for the report you are producing.
Configuration and matplotlibrc
matplotlib.matplotlib_fname() # the config file actually in use
matplotlib.get_configdir() # where your user config lives
# a matplotlibrc file, applied to every script on this machine
# backend: Agg
# figure.figsize: 7, 4.5
# figure.dpi: 120
# savefig.dpi: 200
# font.size: 10⚠️
Importing pyplot selects a backend immediately. Calling
matplotlib.use() after that may appear to work and then fail. Put the call at the very top of the file, before any pyplot import.FAQ
Why does my script work locally but crash in CI?
CI has no display, so an interactive backend fails on start. Set
MPLBACKEND=Agg or call matplotlib.use("Agg") before importing pyplot.Should I edit matplotlibrc or set rcParams?
Use
rcParams in code so the settings travel with the script and are reviewable. Reserve matplotlibrc for machine-wide preferences such as the backend.Related
Publication quality: DPI, vector formats and layout Debugging plots: empty axes, missing data and overlap
Last refreshed 2026-09-18.