Why PyAutoGUI Images Won’t Save and How to Fix It
PyAutoGUI screenshots save only when you provide a path or save the returned image. Fix Pillow, Linux dependencies, paths, permissions, and display issues.

Direct fix: pyautogui.screenshot() returns a Pillow image in memory. It does not choose a file destination unless you pass a filename. Save it in one step:
import pyautogui
pyautogui.screenshot("screenshot.png")
Or capture first, then save the returned image:
import pyautogui
image = pyautogui.screenshot()
image.save("screenshot.png")
If neither version produces a file, check the Python environment, Pillow, Linux capture prerequisites, the process working directory, and write permissions. Then verify whether a later exception is being mistaken for a save failure.
How PyAutoGUI screenshot saving works
The screenshot function has two useful forms:
| Pattern | Use it when | Result |
|---|---|---|
pyautogui.screenshot("path.png") |
You only need a file | Captures and saves directly |
image = pyautogui.screenshot() followed by image.save("path.png") |
You need to inspect, crop, annotate, or transform the image | Returns a Pillow image, then saves it explicitly |
A call with no filename has no destination. The returned object exists in memory until your code saves it or the process exits.
A complete, defensive Python example
from pathlib import Path
import os
import pyautogui
output = Path.cwd() / "screenshots" / "desktop.png"
output.parent.mkdir(parents=True, exist_ok=True)
print("Working directory:", Path.cwd())
print("Output path:", output)
image = pyautogui.screenshot()
image.save(output)
if not output.is_file():
raise RuntimeError(f"Screenshot was not created: {output}")
print("Saved", output, "size:", output.stat().st_size, "bytes")
This version creates the directory, prints the actual destination, and checks the file after saving. Path objects are accepted by Pillow on current Python versions; use str(output) if an older environment requires a string.

Install and verify the dependencies
PyAutoGUI uses Pillow for image data and screenshot handling. Install both packages in the same environment that runs your script:
python -m pip install --upgrade pyautogui pillow
Confirm that imports resolve to the interpreter you expect:
python -c "import sys, pyautogui, PIL; print(sys.executable); print(pyautogui.__file__); print(PIL.__file__)"
If you use a virtual environment, activate it before installing and running the program. A common failure is installing Pillow globally while executing the script with a different virtualenv or system Python.
Linux prerequisite
PyAutoGUI’s screenshot documentation names scrot as the Linux capture dependency. On Debian or Ubuntu, the older quickstart installation command is:
sudo apt-get install scrot
Package names and installation commands vary by distribution. If the import succeeds but capture raises an operating-system or backend error, install the documented capture utility for your distribution and retry from the same graphical session.
Use an explicit path you can inspect
A relative filename is resolved against the process’s current working directory, which may differ from the directory containing your script, IDE project, or notebook.
from pathlib import Path
import pyautogui
print("Current directory:", Path.cwd())
image = pyautogui.screenshot()
image.save("screenshot.png")
print("Expected file:", Path.cwd() / "screenshot.png")
For diagnosis, use an absolute path in a directory you know is writable:
from pathlib import Path
import pyautogui
output = Path.home() / "Pictures" / "pyautogui-test.png"
output.parent.mkdir(parents=True, exist_ok=True)
pyautogui.screenshot(str(output))
print(output, output.exists())
On Windows, a raw string avoids accidental escapes in paths such as C:\\temp\\shot.png. On macOS and Linux, expand the home directory with Path.home() rather than hard-coding another user’s path.
Debug the save separately from the capture
First determine whether the image object was created, then test writing a file:
import pyautogui
image = pyautogui.screenshot()
print("Captured:", image.size, image.mode)
image.save("screenshot.png", format="PNG")
print("Save completed")
If “Save completed” appears, inspect the printed working directory and file location. If an exception appears afterward, the screenshot may already exist; check the file before treating the whole script as a capture failure. A historical macOS issue, for example, reported a saved screenshot followed by a later NameError. That case illustrates why the traceback and file existence should be checked independently; it is not a universal explanation.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| No file appears | No filename was passed and the returned image was not saved | Pass a path to screenshot() or call image.save(path). |
ModuleNotFoundError: PIL |
Pillow is missing from the active interpreter | Run python -m pip install pillow with that interpreter. |
| Linux backend or capture error | scrot or the desktop capture backend is unavailable |
Install the documented Linux prerequisite and run inside an active graphical session. |
FileNotFoundError for the destination |
The parent directory does not exist | Create it with Path(path).parent.mkdir(parents=True, exist_ok=True). |
PermissionError |
The process cannot write to the selected directory | Choose a writable directory and check its permissions. |
| File is in an unexpected folder | Relative paths use Path.cwd(), not the script folder |
Print the working directory or use an absolute path. |
| Image exists but has the wrong dimensions | Display scaling, multiple monitors, or capture-backend behavior | Print image.size, test one display, and separate this from file-saving logic. |
| Unsupported or invalid output format | Extension or format is not appropriate for the image data | Use a standard extension such as .png, or specify format="PNG". |
Check the output after saving
Do not rely on an IDE’s file tree refreshing immediately. Check from Python or the shell:
from pathlib import Path
path = Path("screenshot.png").resolve()
print(path)
print("exists:", path.exists())
print("bytes:", path.stat().st_size if path.exists() else 0)
# macOS/Linux
ls -lh /absolute/path/to/screenshot.png
# Windows PowerShell
Get-Item C:\path\to\screenshot.png
A zero-byte or missing file indicates a write problem. A nonzero file that will not open points to format, corruption, or a viewer issue rather than the destination selection alone.
Capture a region, then save it
Saving works the same way when you capture a region:
import pyautogui
image = pyautogui.screenshot(region=(0, 0, 800, 600))
image.save("top-left.png")
The region tuple is (left, top, width, height). If the result is smaller than expected, print image.size and check display scaling and monitor coordinates. A historical Windows 10 report described unexpectedly small images with old PyAutoGUI and Python versions; treat that as a version and display-scaling symptom, not a current universal behavior.
Performance and reliability notes
- The PyAutoGUI documentation gives an example of about 100 milliseconds for a full screenshot on a 1920 × 1080 screen. Treat that as an estimate, not a guarantee.
- Repeated captures can be expensive. Capture only when the screen state has changed, avoid unnecessary full-screen regions, and save using a suitable format.
- PNG is lossless and useful for UI text; JPEG is smaller but introduces compression artifacts. Pillow can save other supported formats when the required encoder is available.
- For unattended jobs, log the absolute path, image dimensions, byte count, Python executable, and exception traceback.
- Desktop capture depends on an unlocked graphical session and visible content. It is not a substitute for capturing a remote web page that must render independently of a logged-in desktop.

Or skip the browser setup
If what you need is a rendered website image rather than the local desktop, ScreenshotNeo provides a website screenshot API. It accepts one GET request and returns PNG, JPEG, WebP, or PDF. Cookie and consent banners are accepted before capture, then more than 60 known consent platforms, newsletter popups, and chat widgets are removed. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
See the ScreenshotNeo API documentation for all options. A minimal call is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);
ScreenshotNeo also supports full-page captures with lazy images, CSS element capture, dark mode, device presets, custom viewports, retina scale, PDFs, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous webhooks, bulk capture, usage reporting, and an MCP server with take_screenshot, get_page_info, and capture_pdf for AI clients.
An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
FAQ
Does screenshot() return a file path?
No. With no argument it returns a Pillow image object. You must call save() or pass a filename directly.
Why does the file save beside a different directory?
Relative paths are based on the process working directory. Print Path.cwd() or use an absolute destination.
Can I save screenshots as JPEG?
Yes, pass a .jpg or .jpeg destination and use an RGB image if the source contains transparency.
Why does a saved screenshot have unexpected dimensions?
Check image.size, monitor layout, display scaling, and the capture region. This is separate from whether the file was written.
Is PyAutoGUI suitable for server-side website screenshots?
It captures the visible desktop and requires a working graphical environment. For repeatable URL-based captures, use a browser automation service or an API such as ScreenshotNeo.


