How to Create a Filter/Search Bar for a QTableWidget in PyQt5

Use QSortFilterProxyModel to add live filtering to your tables
Heads up! You've already completed this tutorial.

If you're displaying tabular data in PyQt5, you'll probably want to let users search or filter that data at some point. Maybe you're pulling records from an SQLite database and want to add a search bar above the table so users can quickly narrow things down.

The good news is that Qt provides a built-in tool for exactly this: QSortFilterProxyModel. Despite the long name, it's straightforward to use — and yes, it works perfectly in PyQt5, not just C++.

In this tutorial, we'll build a simple table with a search bar that filters rows in real time as the user types. We'll use a custom table model backed by a Python list, but the same approach works with any Qt model, including models connected to a database.

What is QSortFilterProxyModel?

When you display data in a QTableView, you connect it to a model — an object that provides the data. A QSortFilterProxyModel sits between your data model and the view. It acts as a middleman that can sort and filter the data before it reaches the table.

The flow looks like this:

python
Data Model  →  QSortFilterProxyModel  →  QTableView

The proxy model doesn't change your original data. It just controls which rows (and in what order) are shown in the view. This makes it a clean and efficient way to add filtering. If you're not yet familiar with how Qt's Model/View architecture works, take a look at our Model View Architecture tutorial for a thorough introduction.

Setting Up the Table Model

Before we add filtering, we need a table with some data. We'll create a simple custom model by subclassing QAbstractTableModel. This model stores data as a nested Python list, where each inner list represents a row.

python
from PyQt5.QtCore import Qt, QAbstractTableModel


class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def data(self, index, role):
        if role == Qt.DisplayRole:
            return self._data[index.row()][index.column()]

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return len(self._data[0])

This is about as minimal as a table model gets. The data() method returns the value for each cell, and rowCount() and columnCount() tell the view how big the table is.

If you're loading data from SQLite, you could populate the data list from your query results and pass it into this model the same way.

Now let's put it all together. We'll create a QMainWindow with a QLineEdit (the search bar) at the top, and a QTableView below it. The proxy model connects the two — whenever the text in the search bar changes, the proxy model updates its filter and the table shows only matching rows.

Create GUI Applications with Python & Qt5 by Martin Fitzpatrick — (PyQt5 Edition) The hands-on guide to making apps with Python — Save time and build better with this book. Over 15K copies sold.

Get the book

Here's the complete working example:

python
import sys
from PyQt5.QtWidgets import (
    QApplication, QWidget, QTableView,
    QMainWindow, QVBoxLayout, QLineEdit,
)
from PyQt5.QtCore import Qt, QSortFilterProxyModel, QAbstractTableModel


class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def data(self, index, role):
        if role == Qt.DisplayRole:
            return self._data[index.row()][index.column()]

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return len(self._data[0])


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QTableView()

        data = [
            [4, 9, 2],
            [1, "hello", 0],
            [3, 5, 0],
            [3, 3, "what"],
            ["this", 8, 9],
        ]

        self.model = TableModel(data)
        self.proxy_model = QSortFilterProxyModel()
        self.proxy_model.setFilterKeyColumn(-1)  # Search all columns.
        self.proxy_model.setSourceModel(self.model)

        self.proxy_model.sort(0, Qt.AscendingOrder)

        self.table.setModel(self.proxy_model)

        self.searchbar = QLineEdit()
        self.searchbar.setPlaceholderText("Type to filter...")

        self.searchbar.textChanged.connect(
            self.proxy_model.setFilterFixedString
        )

        layout = QVBoxLayout()
        layout.addWidget(self.searchbar)
        layout.addWidget(self.table)

        container = QWidget()
        container.setLayout(layout)

        self.setCentralWidget(container)


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

Run this and you'll see a table with five rows. Start typing in the search bar and the table updates instantly, showing only the rows that contain matching text.

PyQt5 QTableView with a search bar filtering rows using QSortFilterProxyModel

How It Works

Let's walk through the important parts.

Creating the proxy model

python
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setFilterKeyColumn(-1)
self.proxy_model.setSourceModel(self.model)

We create a QSortFilterProxyModel and tell it which model to wrap using setSourceModel(). The call to setFilterKeyColumn(-1) tells the proxy to search all columns when filtering. If you only wanted to filter based on a specific column, you could pass that column's index instead (e.g., 0 for the first column, 1 for the second).

Connecting the view to the proxy

python
self.table.setModel(self.proxy_model)

Notice that we set the table's model to the proxy model, not our original TableModel. The view talks to the proxy, and the proxy talks to your real data model behind the scenes.

Wiring up the search bar

python
self.searchbar.textChanged.connect(
    self.proxy_model.setFilterFixedString
)

This single line does all the work. Every time the text in the QLineEdit changes, the textChanged signal fires and passes the current text to setFilterFixedString() on the proxy model. The proxy then hides any rows that don't contain that text, and the table view updates automatically. For more on how signals and slots work in PyQt5, see our Signals, Slots & Events guide.

Choosing a Filter Mode

QSortFilterProxyModel supports several different ways to filter. We used setFilterFixedString above, which does a simple substring match. But you have other options depending on what you need:

Slot Behavior
setFilterFixedString Matches rows containing the exact string (case-insensitive by default)
setFilterWildcard Supports * and ? wildcard characters
setFilterRegExp Full regular expression matching

To switch modes, just connect the textChanged signal to a different slot:

python
# Use wildcard matching instead
self.searchbar.textChanged.connect(
    self.proxy_model.setFilterWildcard
)

For most search bar use cases, setFilterFixedString is exactly what you want. Wildcard and regex matching are useful if you need to give users more control over their searches.

Controlling Case Sensitivity

By default, the filter is case-insensitive — typing "hello" will match "Hello", "HELLO", and so on. If you want case-sensitive matching, you can set it on the proxy model:

python
self.proxy_model.setFilterCaseSensitivity(Qt.CaseSensitive)

For a user-facing search bar, case-insensitive matching (the default) is usually the better experience.

Using This with SQLite Data

If you're working with data from an SQLite database, the approach is the same. You just need to get your data into a format your model understands. For example, you could fetch your rows and pass them in as a list of lists:

python
import sqlite3

connection = sqlite3.connect("my_database.db")
cursor = connection.cursor()
cursor.execute("SELECT * FROM my_table")
data = cursor.fetchall()  # Returns a list of tuples.

# Convert tuples to lists (optional, but consistent with our model).
data = [list(row) for row in data]

model = TableModel(data)

From here, the proxy model and search bar work exactly the same way. The filtering all happens at the Qt level, so it doesn't matter where your data originally came from. If you're working with NumPy or Pandas data instead, see our guide on displaying tabular data with QTableView.

Summary

Adding a search/filter bar to a table in PyQt5 comes down to three things:

  1. Create a QSortFilterProxyModel and set your data model as its source.
  2. Set the table view's model to the proxy model (not the original).
  3. Connect a QLineEdit's textChanged signal to one of the proxy model's filter slots.

The QSortFilterProxyModel handles all the heavy lifting — filtering rows, updating the view, and keeping your original data untouched. Once you've seen the pattern, you can drop it into any project that uses a QTableView. You can also use the same proxy model for sorting and filtering with more advanced controls.

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

Find out More

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

How to Create a Filter/Search Bar for a QTableWidget in PyQt5 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.