When you're developing a PyQt5 application, setting a window icon is straightforward — you point setWindowIcon at an image file and it just works. But the moment you package your app with PyInstaller and run the resulting .exe, the icon vanishes. The file path that worked during development no longer resolves to anything useful inside the packaged application.
This is a common stumbling block, and it has a clear solution. In this article, we'll walk through why icons break after packaging, how to bundle resource files with PyInstaller, and how to make sure your window icon displays correctly on Windows — including in the taskbar.
Why the Icon Disappears After Packaging
When you run your script directly with Python, the working directory is typically the folder your script lives in. A relative path like 'resource/logo.ico' resolves just fine because the resource folder is sitting right there next to your .py file.
After packaging with PyInstaller, the situation changes. Your .exe might be launched from anywhere — a user could double-click it from their desktop, or open a file by right-clicking and choosing "Open with." The working directory is no longer the folder containing your source code. The relative path 'resource/logo.ico' now points to... nothing.
Resolving Resource Paths in Packaged Apps
The fix is to build an absolute path to your resource files based on the location of the script (or the packaged app). PyInstaller sets a special attribute sys._MEIPASS that points to the temporary folder where it unpacks bundled files at runtime. During normal development, this attribute doesn't exist, so you fall back to the script's directory.
Here's a helper function that handles both cases:
import sys
import os
def resource_path(relative_path):
"""Get the absolute path to a resource, works for dev and for PyInstaller."""
if hasattr(sys, '_MEIPASS'):
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(os.path.abspath(__file__))
return os.path.join(base_path, relative_path)
Now instead of writing:
self.setWindowIcon(QIcon('resource/logo.ico'))
You write:
self.setWindowIcon(QIcon(resource_path('resource/logo.ico')))
This resolves correctly whether you're running from source or from a packaged .exe.
Telling PyInstaller to Bundle Your Icon
PyInstaller won't automatically include your resource files — you need to tell it what to bundle. You do this with the --add-data flag:
pyinstaller -w --add-data "resource/logo.ico;resource" test20.py
The format is source;destination on Windows (use : instead of ; on macOS and Linux). This tells PyInstaller to take resource/logo.ico and place it inside a resource folder in the bundled app.
If you have a whole folder of resources, you can bundle the entire directory:
pyinstaller -w --add-data "resource;resource" test20.py
Fixing the Taskbar Icon on Windows
Even after bundling the icon correctly, you might notice that the Windows taskbar still shows a generic Python icon instead of yours. This happens because Windows groups taskbar icons by "Application User Model ID," and by default your PyQt5 app inherits Python's identity.
To fix this, you need to set a unique app ID before creating your QApplication. The QtWin extras module provides the function you need:
from PyQt5.QtWinExtras import QtWin
myappid = 'com.mycompany.myapp.1.0'
QtWin.setCurrentProcessExplicitAppUserModelID(myappid)
The string can be anything, but using a reverse-domain style (like com.yourname.appname) is a good convention. Place this code before you create your QApplication instance.
Complete Working Example
Here's the full application with all the fixes in place:
import sys
import os
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout
from PyQt5.QtGui import QIcon
def resource_path(relative_path):
"""Get the absolute path to a resource, works for dev and for PyInstaller."""
if hasattr(sys, '_MEIPASS'):
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(os.path.abspath(__file__))
return os.path.join(base_path, relative_path)
# Set the app user model ID so Windows shows the correct taskbar icon.
try:
from PyQt5.QtWinExtras import QtWin
QtWin.setCurrentProcessExplicitAppUserModelID('com.mycompany.myapp.1.0')
except ImportError:
# QtWinExtras is only available on Windows, so we can safely skip this
# on other platforms.
pass
class Window(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
for arg in sys.argv:
label = QLabel(arg)
layout.addWidget(label)
self.setWindowIcon(QIcon(resource_path('resource/logo.ico')))
self.setLayout(layout)
self.setWindowTitle("Arguments")
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec_()
To package this with PyInstaller and include the icon:
pyinstaller -w --add-data "resource/logo.ico;resource" test20.py
If you also want the .exe file itself to show your icon in Windows Explorer (the file icon, not the window icon), add the --icon flag:
pyinstaller -w --icon "resource/logo.ico" --add-data "resource/logo.ico;resource" test20.py
Note that --icon sets the icon of the .exe file in your file browser, while setWindowIcon in your code sets the icon that appears in the window title bar and taskbar. You typically want both.
Handling File Arguments
One more thing worth mentioning: when a user opens a file with your packaged .exe (for example, by right-clicking a .mp4 file and choosing "Open with"), the file path is passed as a command-line argument. You can access it from sys.argv:
if len(sys.argv) > 1:
mp4_filename = sys.argv[-1]
print(f"Opened with file: {mp4_filename}")
Using sys.argv[-1] grabs the last argument, which is the file path the user opened. The first argument (sys.argv[0]) is the path to the .exe itself.
Summary
Getting icons to work in packaged PyQt5 apps comes down to three things:
- Use absolute paths — build them with a helper function that accounts for PyInstaller's
_MEIPASStemporary directory. - Bundle your resources — use
--add-datawhen running PyInstaller so your icon files are included in the package. - Set the app user model ID on Windows — use
QtWin.setCurrentProcessExplicitAppUserModelID()so the taskbar displays your icon instead of the default Python icon.
With these in place, your packaged application will look polished and professional, with your custom icon showing up everywhere it should.
Packaging Python Applications with PyInstaller by Martin Fitzpatrick
This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.