PyQt5 runJavaScript with QtWebEngine

Execute JavaScript in web pages embedded in your PyQt5 applications
Heads up! You've already completed this tutorial.

PyQt5's QtWebEngineWidgets module lets you embed full web pages inside your desktop applications using QWebEngineView. That alone is useful, but the real power comes when you start interacting with those web pages from Python — clicking buttons, filling in forms, or sending commands to JavaScript-based interfaces.

The way you do this is with the runJavaScript() method on QWebEnginePage. This method lets you inject and execute arbitrary JavaScript code inside the loaded web page, giving your Python application direct control over the web content.

In this tutorial, we'll walk through how runJavaScript() works, starting from a simple example and building up to more practical use cases.

Setting up a basic QWebEngineView

Before we can run any JavaScript, we need a web view with a loaded page. Let's start with the basics: creating a QWebEngineView that loads a simple HTML page.

python
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QUrl


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QtWebEngine Example")
        self.resize(800, 600)

        self.browser = QWebEngineView()
        self.browser.setHtml("""
            <html>
            <body>
                <h1 id="title">Hello from QtWebEngine!</h1>
                <p id="content">This is a web page embedded in PyQt5.</p>
            </body>
            </html>
        """)

        central_widget = QWidget()
        layout = QVBoxLayout()
        layout.addWidget(self.browser)
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

This creates a window with an embedded web page. The page is defined inline using setHtml(), but you could also load a URL with setUrl(QUrl("https://example.com")). If you're new to building windows with PyQt5, see our tutorial on creating your first PyQt window.

Running JavaScript on the page

Now let's modify the page after it's loaded. The runJavaScript() method is available on the page object (accessed via self.browser.page()), and you call it with a string of JavaScript code.

Here's how you'd change the heading text from Python:

python
self.browser.page().runJavaScript(
    'document.getElementById("title").innerText = "Modified by Python!";'
)

There's one thing to watch out for: you need to make sure the page has finished loading before you try to run JavaScript on it. If you call runJavaScript() before the page is ready, your JavaScript will have nothing to work with.

Connect to the loadFinished signal to know when it's safe:

python
self.browser.loadFinished.connect(self.on_load_finished)

Then define your slot. If you're not familiar with how signals and slots work in PyQt5, take a look at our signals, slots and events tutorial:

Bring Your PyQt/PySide Application to Market — Specialized launch support for scientific and engineering software built using Python & Qt.

Find out More

python
def on_load_finished(self, ok):
    if ok:
        self.browser.page().runJavaScript(
            'document.getElementById("title").innerText = "Modified by Python!";'
        )

Let's put that together into a complete example:

python
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
from PyQt5.QtWebEngineWidgets import QWebEngineView


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("runJavaScript Example")
        self.resize(800, 600)

        self.browser = QWebEngineView()
        self.browser.setHtml("""
            <html>
            <body>
                <h1 id="title">Hello from QtWebEngine!</h1>
                <p id="content">This is a web page embedded in PyQt5.</p>
            </body>
            </html>
        """)
        self.browser.loadFinished.connect(self.on_load_finished)

        central_widget = QWidget()
        layout = QVBoxLayout()
        layout.addWidget(self.browser)
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

    def on_load_finished(self, ok):
        if ok:
            self.browser.page().runJavaScript(
                'document.getElementById("title").innerText = "Modified by Python!";'
            )


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

When you run this, you'll see the heading briefly show "Hello from QtWebEngine!" and then change to "Modified by Python!" once the page finishes loading.

Getting values back from JavaScript

runJavaScript() can also return values from the JavaScript back to Python. You do this by passing a callback function as the second argument. The callback receives whatever value the JavaScript expression evaluates to.

python
def on_load_finished(self, ok):
    if ok:
        self.browser.page().runJavaScript(
            'document.getElementById("title").innerText;',
            self.handle_result
        )

def handle_result(self, result):
    print(f"The title text is: {result}")

The JavaScript expression needs to evaluate to something — think of it like the return value of the last expression in your script. Simple values like strings, numbers, booleans, and even JSON-serializable objects can be passed back this way.

Here's a complete example that reads a value from the page:

python
import sys
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QLabel,
)
from PyQt5.QtWebEngineWidgets import QWebEngineView


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("runJavaScript Return Values")
        self.resize(800, 600)

        self.browser = QWebEngineView()
        self.browser.setHtml("""
            <html>
            <body>
                <h1 id="title">Hello from QtWebEngine!</h1>
                <p id="counter">0</p>
                <script>
                    var count = 0;
                    function increment() {
                        count += 1;
                        document.getElementById("counter").innerText = count;
                        return count;
                    }
                </script>
            </body>
            </html>
        """)

        self.result_label = QLabel("Click the button to increment the counter")
        self.button = QPushButton("Increment Counter")
        self.button.clicked.connect(self.increment_counter)

        central_widget = QWidget()
        layout = QVBoxLayout()
        layout.addWidget(self.browser)
        layout.addWidget(self.result_label)
        layout.addWidget(self.button)
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

    def increment_counter(self):
        self.browser.page().runJavaScript("increment();", self.handle_result)

    def handle_result(self, result):
        self.result_label.setText(f"Counter value from JavaScript: {result}")


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

Each time you click the button, Python calls the increment() JavaScript function, the counter on the page updates, and the return value is sent back to Python and displayed in the label.

Triggering JavaScript from buttons

A common pattern is to have buttons in your PyQt5 interface that trigger actions in the embedded web page. This is straightforward — connect your button's clicked signal to a method that calls runJavaScript().

python
self.button.clicked.connect(self.run_my_script)

def run_my_script(self):
    script = """
        document.body.style.backgroundColor = '#2c3e50';
        document.body.style.color = '#ecf0f1';
    """
    self.browser.page().runJavaScript(script)

You can make the JavaScript as simple or complex as you need. Multi-line scripts work well when formatted as Python triple-quoted strings.

Passing Python variables into JavaScript

Often you'll want to include Python data in your JavaScript code. Since runJavaScript() takes a string, you can use Python string formatting to inject values:

python
username = "admin"
message = "Welcome back!"

script = """
    document.getElementById("title").innerText = "%s";
    document.getElementById("content").innerText = "User: %s";
""" % (message, username)

self.browser.page().runJavaScript(script)

Be careful with special characters in your variables. If a string might contain quotes or newlines, you'll want to properly escape them. For simple, controlled values this approach works well. For more complex data, consider using json.dumps() to safely serialize your Python values:

python
import json

data = {"username": "admin", "message": "Welcome back!"}

script = """
    var data = %s;
    document.getElementById("title").innerText = data.message;
""" % json.dumps(data)

self.browser.page().runJavaScript(script)

Using json.dumps() handles escaping automatically and is much safer when your data might contain unexpected characters.

Interacting with JavaScript libraries on the page

When you load a web page that includes its own JavaScript libraries or APIs, you can call those functions directly from runJavaScript(). This is where things get really powerful — your Python application can drive any web-based interface.

For example, if a loaded page has a JavaScript object called app with a sendMessage() method, you could call it like this:

python
def send_message(self, text):
    script = """
        app.sendMessage(`%s`);
    """ % text
    self.browser.page().runJavaScript(script)

Things to keep in mind

Asynchronous execution. runJavaScript() is asynchronous. The JavaScript executes in the web engine's process, and your Python code continues running immediately. If you need the result, always use the callback form.

Page must be loaded. Always wait for loadFinished before running JavaScript. Any scripts executed before the page is ready will silently fail.

String escaping. When injecting Python strings into JavaScript, use json.dumps() to handle escaping. This prevents bugs from quotes, backslashes, or newlines in your data.

Security. If you're loading external web pages (not ones you control), be cautious about what JavaScript you inject. Running arbitrary code on untrusted pages could have unintended consequences.

With runJavaScript(), your PyQt5 application can fully control any web-based interface embedded within it. Whether you're automating a web-based terminal, scraping data from a loaded page, or building a hybrid desktop-web application, this method gives you the bridge between Python and the browser engine. Once your application is ready, you can package it for distribution with PyInstaller.

PyQt/PySide Office Hours 1:1 with Martin Fitzpatrick — Save yourself time and frustration. Get one on one help with your projects. Bring issues, bugs and questions about usability to architecture and maintainability, and leave with solutions.

60 mins ($195)

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

PyQt5 runJavaScript with QtWebEngine was written by Martin Fitzpatrick.

Martin Fitzpatrick is the creator of Python GUIs, and has been developing Python/Qt applications for the past 12+ years. He has written a number of popular Python books and provides Python software development & consulting for teams and startups.