Fixing "toHtml() not enough arguments" Error in PyQt6

How to correctly use the asynchronous toHtml() method in QWebEnginePage
Heads up! You've already completed this tutorial.

If you're working with QWebEngineView in PyQt6 and trying to save a page's HTML content to a file, you might run into this error:

python
TypeError: toHtml(self, Callable[[str], None]): not enough arguments

This happens because QWebEnginePage.toHtml() doesn't work the way you might expect. It doesn't simply return a string. Instead, it's an asynchronous method that takes a callback function — a function that will be called later, once the HTML content is ready.

This is a common source of confusion, so let's walk through what's going wrong and how to fix it.

The problem

Here's the code that triggers the error:

python
def save_file(self):
    filename, _ = QFileDialog.getSaveFileName(
        self, "Save Page as", "",
        "Hypertext Markup Language (*.htm *.html);;"
        "All files (*.*)"
    )

    if filename:
        html = self.browser.page().toHtml()
        with open(filename, 'w') as file:
            file.write(html)

The line html = self.browser.page().toHtml() treats toHtml() as if it returns the HTML string directly. In the C++ Qt API, toHtml() is asynchronous and accepts a callback. PyQt6 mirrors this behavior — you must pass a callable (a function) that receives the HTML string as its argument.

The fix

To solve this, pass a callback function to toHtml(). The callback will receive the HTML string once it's available, and you can then write it to a file inside that callback.

Here's the corrected save_file method:

python
def save_file(self):
    filename, _ = QFileDialog.getSaveFileName(
        self, "Save Page as", "",
        "Hypertext Markup Language (*.htm *.html);;"
        "All files (*.*)"
    )

    if filename:
        self.browser.page().toHtml(
            lambda html: self._save_html_to_file(html, filename)
        )

def _save_html_to_file(self, html, filename):
    with open(filename, 'w') as file:
        file.write(html)

Let's walk through what's happening:

  1. When the user picks a filename, we call toHtml() and pass it a lambda — a small inline function.
  2. That lambda receives the html string (provided by Qt once it's ready) and passes it along with the filename to our helper method _save_html_to_file.
  3. _save_html_to_file does the actual file writing.

We use a lambda here because toHtml() will call its callback with a single argument (the HTML string), but we also need access to the filename that the user chose. The lambda captures filename from the surrounding scope and passes both values to our helper method.

A simpler version

If you prefer, you can skip the separate helper method and do everything inside the lambda itself:

python
def save_file(self):
    filename, _ = QFileDialog.getSaveFileName(
        self, "Save Page as", "",
        "Hypertext Markup Language (*.htm *.html);;"
        "All files (*.*)"
    )

    if filename:
        self.browser.page().toHtml(
            lambda html: open(filename, 'w').write(html)
        )

This works, but the previous version with a separate method is cleaner and easier to extend — for example, if you later want to add error handling or show a confirmation message after saving.

Why is toHtml() asynchronous?

QWebEnginePage runs web content in a separate process for security and performance reasons. When you ask for the page's HTML, the request has to cross a process boundary. The result isn't available immediately, so Qt uses a callback pattern: you provide a function, and Qt calls it when the HTML is ready.

This is different from the older QWebView (based on WebKit), where toHtml() did return a string directly. If you're following tutorials or reading older code that was written for QWebView, this kind of mismatch can catch you off guard.

Complete working example

Here's a minimal but complete browser application with a working save function, so you can try it out directly:

python
import sys

from PyQt6.QtCore import QUrl
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QToolBar, QAction,
    QFileDialog, QStatusBar
)
from PyQt6.QtWebEngineWidgets import QWebEngineView


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Mini Browser")

        self.browser = QWebEngineView()
        self.browser.setUrl(QUrl("https://www.pythonguis.com"))
        self.setCentralWidget(self.browser)

        # Navigation toolbar
        toolbar = QToolBar("Navigation")
        self.addToolBar(toolbar)

        save_action = QAction("Save Page", self)
        save_action.triggered.connect(self.save_file)
        toolbar.addAction(save_action)

        self.setStatusBar(QStatusBar(self))
        self.browser.loadFinished.connect(
            lambda: self.statusBar().showMessage("Page loaded")
        )

        self.show()

    def save_file(self):
        filename, _ = QFileDialog.getSaveFileName(
            self, "Save Page As", "",
            "Hypertext Markup Language (*.htm *.html);;"
            "All files (*.*)"
        )

        if filename:
            self.browser.page().toHtml(
                lambda html: self._save_html_to_file(html, filename)
            )

    def _save_html_to_file(self, html, filename):
        with open(filename, 'w') as file:
            file.write(html)
        self.statusBar().showMessage(f"Saved to {filename}")


app = QApplication(sys.argv)
window = MainWindow()
app.exec()

Run this, click Save Page, choose a filename, and the HTML will be written to disk. You'll see a confirmation in the status bar once the file has been saved.

The pattern of passing a callback to an asynchronous method is something you'll encounter in other parts of QWebEnginePage too — for example, toPlainText() works the same way. Once you're comfortable with the callback approach, these methods become straightforward to use.

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

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.

Find out More

Martin Fitzpatrick

Fixing "toHtml() not enough arguments" Error in PyQt6 was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.