I'm adding an icon to a QAction on my toolbar using
QIcon("bug.png"), and the image file is sitting right next to my Python script. But the icon doesn't show up in the GUI. What went wrong?
If you've ever added an icon to a toolbar action and been greeted by a blank space where your icon should be, you're not alone. This is one of the most common stumbling blocks when working with icons in PyQt6 or PySide6, and the fix is straightforward once you understand the cause.
The Problem: Relative File Paths
When you write something like this:
button_action = QAction(QIcon("bug.png"), "Your button", self)
Python looks for bug.png relative to the current working directory — the folder your script was launched from, not the folder where your script lives.
If you run your script from the terminal while sitting in the same directory as the script, everything works fine. But if you run it from an IDE like VS Code, PyCharm, or any other tool that may launch your script from a different directory, the working directory could be somewhere else entirely. Python can't find bug.png, so the icon silently fails to load and you see nothing.
There's no error message. Qt simply shows an empty icon when the image file can't be found. This makes the issue tricky to diagnose at first.
The Fix: Use an Absolute Path
The solution is to build an absolute path to the icon file based on the location of your Python script. Python provides the special variable __file__, which always holds the path to the currently running script. You can use that to construct a reliable path to any file sitting alongside your code.
There are two good ways to do this.
Using pathlib (recommended)
The pathlib module provides a clean, modern way to work with file paths:
from pathlib import Path
# Get the folder where this script lives
basedir = Path(__file__).resolve().parent
# Build the full path to the icon
icon_path = str(basedir / "bug.png")
Path(__file__).resolve().parent gives you the directory containing your script, regardless of where the script was launched from. The / operator joins path components together.
Using os.path
The os.path module is the older approach, but it works just as well:
import os
basedir = os.path.dirname(os.path.abspath(__file__))
icon_path = os.path.join(basedir, "bug.png")
os.path.abspath(__file__) converts the script's path to an absolute path, and os.path.dirname() strips off the filename to give you the containing directory.
Alternative: Change the Working Directory
Another option is to change the working directory to match your script's location at the start of your program:
import os
os.chdir(os.path.dirname(os.path.abspath(__file__)))
After this line, all relative paths in your script will resolve relative to the script's own directory. This can be convenient, but it's a global change that affects your entire program, so building absolute paths explicitly is generally a safer habit.
Complete Working Example
Here's a full example using PySide6 with the pathlib approach. If you're using PyQt6, the only changes are the import lines (shown in the comments). This example builds on concepts from the actions, toolbars and menus tutorial.
- PyQt6
- PySide6
import sys
from pathlib import Path
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtGui import QAction, QIcon
from PyQt6.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QStatusBar,
QToolBar,
)
basedir = Path(__file__).resolve().parent
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
label = QLabel("Hello!")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.setCentralWidget(label)
toolbar = QToolBar("My main toolbar")
toolbar.setIconSize(QSize(16, 16))
self.addToolBar(toolbar)
# Use an absolute path for the icon
icon_path = str(basedir / "bug.png")
button_action = QAction(QIcon(icon_path), "Your button", self)
button_action.setStatusTip("This is your button")
button_action.triggered.connect(self.toolbar_button_clicked)
button_action.setCheckable(True)
toolbar.addAction(button_action)
self.setStatusBar(QStatusBar(self))
def toolbar_button_clicked(self, s):
print("click", s)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
import sys
from pathlib import Path
from PySide6.QtCore import QSize, Qt
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QStatusBar,
QToolBar,
)
basedir = Path(__file__).resolve().parent
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
label = QLabel("Hello!")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.setCentralWidget(label)
toolbar = QToolBar("My main toolbar")
toolbar.setIconSize(QSize(16, 16))
self.addToolBar(toolbar)
# Use an absolute path for the icon
icon_path = str(basedir / "bug.png")
button_action = QAction(QIcon(icon_path), "Your button", self)
button_action.setStatusTip("This is your button")
button_action.triggered.connect(self.toolbar_button_clicked)
button_action.setCheckable(True)
toolbar.addAction(button_action)
self.setStatusBar(QStatusBar(self))
def toolbar_button_clicked(self, s):
print("click", s)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Make sure you have a file called bug.png in the same folder as this script. You can use any small PNG image to test.
Why This Happens in IDEs
Most IDEs set the working directory to the project root folder, not the folder containing the specific file you're running. For example, if your project looks like this:
my_project/
├── icons_example/
│ ├── toolbars_and_menus.py
│ └── bug.png
└── other_stuff/
And your IDE sets the working directory to my_project/, then QIcon("bug.png") will look for my_project/bug.png — which doesn't exist. The file is actually at my_project/icons_example/bug.png.
Using __file__ to build an absolute path avoids this entirely. Your code will work the same way whether you run it from the terminal, from an IDE, or from a shortcut.
A Good Habit for All Your Projects
Any time you load a file in your Qt application — icons, images, stylesheets, data files — use an absolute path built from __file__. Defining a basedir variable at the top of your script (or in a shared module) makes this easy:
from pathlib import Path
basedir = Path(__file__).resolve().parent
Then throughout your code, reference files like this:
QIcon(str(basedir / "icons" / "bug.png"))
This pattern keeps your paths reliable and your icons visible, no matter how or where your script gets launched. For more complex applications, you may also want to consider using the Qt Resource System to bundle images and other assets directly into your application.
Bring Your PyQt/PySide Application to Market
Stuck in development hell? I'll help you get your project focused, finished and released. Benefit from years of practical experience releasing software with Python.