Of course! Let's dive deep into QWebEngineView from the Qt for Python project. This is the primary component for embedding web content (like a modern browser) into your desktop applications.

What is QWebEngineView?
QWebEngineView is a widget that provides a web browser engine within a PyQt or PySide application. It uses the Chromium engine under the hood, the same engine that powers Google Chrome, Microsoft Edge, and many other modern browsers.
This means your application can display:
- Modern websites (HTML5, CSS3, JavaScript).
- Complex web applications (like Google Docs or Figma).
- Custom HTML, CSS, and JavaScript that you generate yourself.
It's part of the PyQt6WebEngine or PySide6WebEngine module, which you need to install separately.
Installation
First, you need to install the base Qt library and the WebEngine component.

For PyQt6:
pip install PyQt6 PyQt6-WebEngine
For PySide6:
pip install PySide6 PySide6-WebEngine
Basic Example: Displaying a URL
This is the simplest use case: creating a window that loads and displays a website.
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QWebEngineView
from PyQt6.QtCore import QUrl
class BrowserWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Simple Browser")
self.setGeometry(100, 100, 1024, 768)
# Create the QWebEngineView widget
self.browser = QWebEngineView()
# Load a URL
self.browser.setUrl(QUrl("https://www.python.org"))
# Set the browser as the central widget of the window
self.setCentralWidget(self.browser)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = BrowserWindow()
window.show()
sys.exit(app.exec())
To run this: Save it as simple_browser.py and execute it from your terminal. You'll see a window with the Python website rendered perfectly.
Loading Local HTML Files
Often, you'll want to load content from your application, not just from the internet. You can do this by providing a local file path.
Let's create a simple HTML file named index.html:
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>Local Page</title>
<style>
body { font-family: sans-serif; text-align: center; padding-top: 50px; }
h1 { color: #333; }
button { padding: 10px 20px; font-size: 16px; cursor: pointer; }
</style>
</head>
<body>
<h1>Hello from a Local HTML File!</h1>
<p>This page is loaded directly from your application.</p>
<button id="myButton">Click Me</button>
<script>
document.getElementById('myButton').addEventListener('click', function() {
alert('Button was clicked!');
});
</script>
</body>
</html>
Now, modify the Python script to load this local file:
# ... (imports and BrowserWindow class from above)
class BrowserWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Local HTML Viewer")
self.setGeometry(100, 100, 800, 600)
self.browser = QWebEngineView()
# Load the local file
# Use QUrl.fromLocalFile to correctly format the path
local_file_url = QUrl.fromLocalFile("/path/to/your/index.html") # <-- IMPORTANT: Change this path
self.browser.setUrl(local_file_url)
self.setCentralWidget(self.browser)
# ... (main execution block)
Key Point: Use QUrl.fromLocalFile() to ensure the path is correctly formatted for the web engine.
Communication: Python ↔ JavaScript
This is where QWebEngineView becomes incredibly powerful. You can call Python functions from JavaScript and vice-versa.
A. Calling Python from JavaScript
You can inject a Python object into the web page's JavaScript context. All of its public methods become callable from JavaScript.
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QWebEngineView, QVBoxLayout, QWidget, QLabel
from PyQt6.QtCore import QUrl
class Communicator:
@pyqtSlot(str) # Use the @pyqtSlot decorator for clarity and type safety
def print_from_py(self, message):
print(f"Message from JS: {message}")
@pyqtSlot(int, int)
def add_numbers(self, a, b):
result = a + b
print(f"JS asked to add {a} + {b} = {result}")
return result
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("JS-Python Communication")
self.setGeometry(100, 100, 800, 600)
# Create the communicator object
self.communicator = Communicator()
self.browser = QWebEngineView()
# Create a simple HTML page with JavaScript
html_content = """
<!DOCTYPE html>
<html>
<head><title>Comm Test</title></head>
<body>
<h1>Python-JS Communication</h1>
<button id="btn1">Send Message to Python</button>
<button id="btn2">Add Numbers (5 + 10)</button>
<p id="output"></p>
<script>
document.getElementById('btn1').addEventListener('click', () => {
// Call the Python method
pythonObject.print_from_py("Hello from JavaScript!");
document.getElementById('output').innerText = "Message sent to Python console.";
});
document.getElementById('btn2').addEventListener('click', () => {
// Call the Python method and get a return value
let result = pythonObject.add_numbers(5, 10);
document.getElementById('output').innerText = `Result from Python: ${result}`;
});
</script>
</body>
</html>
"""
self.browser.setHtml(html_content)
# Inject the Python object into the JavaScript page
# The name 'pythonObject' will be available in the page's JS context
self.browser.page().runJavaScript(
"window.pythonObject = { print_from_py: pyPrint, add_numbers: pyAdd };",
self.inject_communicator
)
self.setCentralWidget(self.browser)
def inject_communicator(self, result):
# This callback is executed after the JS runs
if result is None:
print("Successfully injected Python object into JS.")
# Now bind the actual Python methods to the JS names
self.browser.page().runJavaScript(
"pyPrint = (msg) => pythonObject.print_from_py(msg); pyAdd = (a, b) => pythonObject.add_numbers(a, b);"
)
else:
print(f"Error injecting object: {result}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
How it works:
- We create a
Communicatorclass with Python methods. - We load an HTML string that contains JavaScript.
- We use
page().runJavaScript()to execute JavaScript code within the page. - We inject a JavaScript object
window.pythonObjectand assign dummy functions (pyPrint,pyAdd) to it. - We then use
runJavaScriptagain to replace these dummy functions with references to our actual Python methods, effectively "binding" them. The@pyqtSlotdecorator helps Qt understand the expected argument types.
B. Calling JavaScript from Python
This is much simpler. You just use the runJavaScript method.
# Inside your MainWindow class or wherever you have the browser instance
def call_js_function(self):
js_code = "document.title = 'Title Changed by Python!';"
self.browser.page().runJavaScript(js_code)
# You could connect this to a button click or a timer event
Important Considerations & Best Practices
-
Threading (
QWebEnginePageruns in a separate thread):- The web engine page runs in its own thread, separate from your main GUI thread.
- You cannot directly access GUI elements (like
QLabel,QPushButton) from a Python method called by JavaScript. This will lead to crashes or unpredictable behavior. - Solution: Use
QMetaObject.invokeMethodto safely call methods in the main GUI thread from your Python callback.
# In your Communicator class @pyqtSlot(str) def update_gui_label(self, message): # This is called from the web engine's thread! # We must invoke the update on the main GUI thread. label_to_update = self.parent().findChild(QLabel, "statusLabel") if label_to_update: QMetaObject.invokeMethod(label_to_update, "setText", Qt.ConnectionType.QueuedConnection, Q_ARG(str, message)) -
Performance:
QWebEngineViewis resource-intensive as it embeds a full browser engine. Be mindful of memory usage, especially if you load multiple pages or complex content.- For simple HTML/CSS without JavaScript, consider the older
QTextBrowserorQWebEngineViewwith JavaScript disabled, as it will be much lighter.
-
Permissions:
- By default, it might ask for permissions like geolocation. You can handle these by subclassing
QWebEngineProfileandQWebEnginePageand overriding the permission request methods.
- By default, it might ask for permissions like geolocation. You can handle these by subclassing
-
Qt Version:
- The API can differ slightly between PyQt5/PySide5 and PyQt6/PySide6. The examples above use the modern
PyQt6syntax. If you're using PyQt5, replacepyqtSlotwith@Slotand adjust some import paths.
- The API can differ slightly between PyQt5/PySide5 and PyQt6/PySide6. The examples above use the modern
Summary
| Feature | How to Use It | Key Component/Method |
|---|---|---|
| Display Web Page | Create QWebEngineView and call setUrl() with a QUrl. |
QWebEngineView, setUrl(), QUrl |
| Display Local HTML | Use setHtml() with a string or setUrl() with QUrl.fromLocalFile(). |
setHtml(), QUrl.fromLocalFile() |
| Python → JavaScript | Use page().runJavaScript(). |
page().runJavaScript() |
| JavaScript → Python | Inject an object using page().runJavaScript() and bind its methods. |
page().runJavaScript(), @pyqtSlot |
| Cross-Thread GUI Update | Use QMetaObject.invokeMethod to safely update GUI from a callback. |
QMetaObject.invokeMethod |
QWebEngineView is a fantastic tool for building modern, feature-rich desktop applications that leverage the power of web technologies. It's perfect for creating embedded dashboards, documentation viewers, or full-fledged applications with a web-based UI.
