If you've built a QTableWidget and populated it with data — maybe from a database — you'll probably want to let users search through it. PyQt6 makes this straightforward with the built-in .findItems() method, which searches the table for matching text and returns the results as a list of items you can then highlight or select.
In this tutorial, we'll build a simple searchable table. You'll learn how to wire up a QLineEdit as a search box, use .findItems() to locate matches, and highlight the results in the table.
Setting up the table and search box
Let's start with a complete working example. We'll create a QTableWidget filled with random text, and a QLineEdit at the top that searches the table as you type.
from PyQt6.QtWidgets import (
QTableWidget, QLineEdit, QApplication,
QMainWindow, QVBoxLayout, QWidget, QTableWidgetItem
)
from PyQt6.QtCore import Qt
import random
import string
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.query = QLineEdit()
self.query.setPlaceholderText("Search...")
self.query.textChanged.connect(self.search)
n_rows = 50
n_cols = 4
self.table = QTableWidget()
self.table.setRowCount(n_rows)
self.table.setColumnCount(n_cols)
for c in range(0, n_cols):
for r in range(0, n_rows):
s = ''.join(random.choice(string.ascii_lowercase) for n in range(10))
i = QTableWidgetItem(s)
self.table.setItem(c, r, i)
layout = QVBoxLayout()
layout.addWidget(self.query)
layout.addWidget(self.table)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
def search(self, s):
items = self.table.findItems(s, Qt.MatchContains)
if items: # we have found something
item = items[0] # take the first
self.table.setCurrentItem(item)
app = QApplication([])
w = MainWindow()
w.show()
app.exec()
Run this, and you'll see a table full of random strings with a search box at the top. Start typing in the search box, and the table will jump to and highlight the first matching cell.

Now let's walk through how this works, piece by piece.
How findItems() works
The .findItems() method is the heart of the search. It takes two arguments:
- The search text — a plain string to look for.
- Match flags — a
Qt.MatchFlagsvalue that controls how the search is performed.
items = self.table.findItems(s, Qt.MatchContains)
The method returns a Python list of QTableWidgetItem objects — these are the actual data items in the table that matched your search. If nothing matched, you get an empty list.
Match flag options
The match flags determine the behavior of the search. Here are the most useful options:
| Flag | Description |
|---|---|
Qt.MatchContains |
The search text appears anywhere in the item |
Qt.MatchStartsWith |
The item starts with the search text |
Qt.MatchExactly |
The item matches the search text exactly |
Qt.MatchEndsWith |
The item ends with the search text |
Qt.MatchCaseSensitive |
Makes the search case-sensitive |
You can combine flags together using the | (bitwise OR) operator. For example, to search for items that start with a string and are case-sensitive:
items = self.table.findItems(s, Qt.MatchStartsWith | Qt.MatchCaseSensitive)
By default, searches are case-insensitive, which is usually what you want for a user-facing search box.
Selecting a matching item
Once you have a list of matching items, you can tell the table to jump to one of them using .setCurrentItem():
if items:
item = items[0] # take the first match
self.table.setCurrentItem(item)
This selects and highlights the cell in the table, scrolling to it if necessary. We check if items first to make sure we actually got results — calling .setCurrentItem() with nothing would cause problems.
Stepping through multiple results
In the basic example above, we only ever select the first match. But .findItems() can return many matches. To let users step through all of them, you can track the current position in the results list and move forward each time.
Here's an updated version that adds "Next" and "Previous" buttons:
from PyQt6.QtWidgets import (
QTableWidget, QLineEdit, QPushButton, QApplication,
QMainWindow, QVBoxLayout, QHBoxLayout, QWidget,
QTableWidgetItem, QLabel
)
from PyQt6.QtCore import Qt
import random
import string
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.search_results = []
self.current_index = 0
self.query = QLineEdit()
self.query.setPlaceholderText("Search...")
self.query.textChanged.connect(self.search)
self.prev_button = QPushButton("Previous")
self.prev_button.clicked.connect(self.previous_result)
self.next_button = QPushButton("Next")
self.next_button.clicked.connect(self.next_result)
self.result_label = QLabel("")
n_rows = 50
n_cols = 4
self.table = QTableWidget()
self.table.setRowCount(n_rows)
self.table.setColumnCount(n_cols)
for c in range(0, n_cols):
for r in range(0, n_rows):
s = ''.join(random.choice(string.ascii_lowercase) for n in range(10))
i = QTableWidgetItem(s)
self.table.setItem(c, r, i)
search_layout = QHBoxLayout()
search_layout.addWidget(self.query)
search_layout.addWidget(self.prev_button)
search_layout.addWidget(self.next_button)
search_layout.addWidget(self.result_label)
layout = QVBoxLayout()
layout.addLayout(search_layout)
layout.addWidget(self.table)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
def search(self, s):
self.search_results = self.table.findItems(s, Qt.MatchContains)
self.current_index = 0
self.highlight_current()
def highlight_current(self):
if self.search_results:
item = self.search_results[self.current_index]
self.table.setCurrentItem(item)
self.result_label.setText(
f"{self.current_index + 1} of {len(self.search_results)}"
)
else:
self.result_label.setText("No results")
def next_result(self):
if self.search_results:
self.current_index = (self.current_index + 1) % len(self.search_results)
self.highlight_current()
def previous_result(self):
if self.search_results:
self.current_index = (self.current_index - 1) % len(self.search_results)
self.highlight_current()
app = QApplication([])
w = MainWindow()
w.show()
app.exec()
Now when you type a search term, you'll see "1 of N" next to the search box, telling you how many matches were found. Clicking Next and Previous cycles through all the matches, jumping to each one in the table.
The % (modulo) operator in next_result and previous_result makes the navigation wrap around — when you reach the last match and click Next, it loops back to the first one.
A note about QTableWidget vs. QTableView
This approach works specifically with QTableWidget, which stores its data internally using QTableWidgetItem objects. If you're using a QTableView with a custom model (like QSqlTableModel or your own QAbstractTableModel), the .findItems() method isn't available. In that case, you'd need to search through the model's data yourself, or use QSortFilterProxyModel to filter rows based on search criteria.
For most cases where you're manually populating a table with data from a database, QTableWidget with .findItems() is the simplest path to a working search feature.
Create GUI Applications with Python & Qt6 by Martin Fitzpatrick
(PyQt6 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!