Semantic search doesn’t need a separate vector database when the data already lives in SQLite. The sqlite-vec extension adds vector search to an ordinary SQLite file, and in this article I’ll walk you through installing it in Python and running a nearest-neighbour search.
TLDR
- sqlite-vec is a SQLite extension for vector search. You store vectors in a
vec0virtual table and query them withMATCH. - In Python:
pip install sqlite-vec, thensqlite_vec.load(db)on each connection. - Find the nearest vectors with
WHERE embedding MATCH ? AND k = 2 ORDER BY distance. - It’s pre-v1, so pin the version you test with.
- Without the extension, the file still opens. Only the
vec0table fails, with a clear error.
What is sqlite-vec, and what will we build?
sqlite-vec is a loadable extension that stores vectors in vec0 virtual tables and finds the nearest ones with a MATCH query. Its README describes it as “Written in pure C, no dependencies, runs anywhere SQLite runs” (sqlite-vec README). Because it’s a loadable extension, it works with the SQLite already built into your language, as long as that build allows loading extensions. The README also warns: “sqlite-vec is a pre-v1, so expect breaking changes!”
To show you how this works, I will build a tiny notes database. Each note has a 3-dimensional vector, and we’ll search for the notes closest to a query vector. Real embeddings have hundreds of dimensions, but with three you can check the distances by hand.
Prerequisites
You need Python whose sqlite3 module can load extensions, which means enable_load_extension must exist. Not every build has it. The Homebrew Python I used does, and macOS’s built-in Python 3.9 doesn’t:
Python 3.13.14 enable_load_extension: True
Python 3.9.6 enable_load_extension: False
These are the versions I tested with:
Python 3.13.14 with SQLite 3.53.4
sqlite-vec 0.1.9
stock sqlite3 3.51.0
Building a vector search with sqlite-vec
The whole example is one script. I’ll go through it in two steps.
Step 1: Install sqlite-vec
Install the package into a virtual environment, pinned to the version on this page:
$ python3 -m venv .venv
$ .venv/bin/pip install sqlite-vec==0.1.9
Step 2: Store vectors and find the nearest ones
The script keeps normal columns in an ordinary notes table and the vectors in a vec0 table that shares its rowid. Then it runs a k-nearest-neighbour query, and a cosine-distance query for comparison:
import sqlite3
import sqlite_vec
db = sqlite3.connect("notes.db")
db.enable_load_extension(True)
sqlite_vec.load(db) # loads the vec0 extension into this connection
db.enable_load_extension(False)
print("vec_version:", db.execute("SELECT vec_version()").fetchone()[0])
db.execute("CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL)")
db.execute("CREATE VIRTUAL TABLE vec_notes USING vec0(embedding float[3])")
rows = [(1, "sqlite tips", "[0.9, 0.1, 0.0]"), (2, "postgres tips", "[0.1, 0.9, 0.0]"), (3, "sqlite wal", "[0.8, 0.2, 0.1]")]
for id_, body, vec in rows:
db.execute("INSERT INTO notes (id, body) VALUES (?, ?)", (id_, body))
db.execute("INSERT INTO vec_notes (rowid, embedding) VALUES (?, ?)", (id_, vec))
db.commit()
knn = db.execute("""
SELECT notes.body, round(v.distance, 3) AS distance
FROM vec_notes AS v JOIN notes ON notes.id = v.rowid
WHERE v.embedding MATCH ? AND k = 2
ORDER BY v.distance
""", ("[1, 0, 0]",)).fetchall()
print("knn:", knn)
cos = db.execute("""
SELECT notes.body, round(vec_distance_cosine(v.embedding, ?), 3) AS cosine
FROM vec_notes AS v JOIN notes ON notes.id = v.rowid ORDER BY cosine
""", ("[1, 0, 0]",)).fetchall()
print("cosine:", cos)
db.close()
$ .venv/bin/python vec_demo.py
vec_version: v0.1.9
knn: [('sqlite tips', 0.141), ('sqlite wal', 0.3)]
cosine: [('sqlite tips', 0.006), ('sqlite wal', 0.037), ('postgres tips', 0.89)]
The KNN distance is Euclidean: 0.141 is √(0.1² + 0.1²), the distance from [1, 0, 0] to [0.9, 0.1, 0.0]. vec_distance_cosine() computes cosine distance directly. That scans every row, which is fine for small tables.
Running it
The database is still a normal SQLite file. I opened it with the stock sqlite3 shell, which doesn’t have the extension. Ordinary tables work, and the vec0 table fails with a clear error instead of returning wrong results:
$ sqlite3 notes.db "SELECT body FROM notes;"
sqlite tips
postgres tips
sqlite wal
$ sqlite3 notes.db "SELECT rowid FROM vec_notes;"
Error: in prepare, no such module: vec0
Common problems
These are the problems you’re likely to hit when you first try it:
- An
AttributeErroraboutenable_load_extension. Your Python’ssqlite3was built without extension loading, like macOS’s built-in Python 3.9. Use a Python build that has it, such as the Homebrew Python used here:
$ /usr/bin/python3 -c "import sqlite3; sqlite3.connect(':memory:').enable_load_extension(True)"
AttributeError: 'sqlite3.Connection' object has no attribute 'enable_load_extension'
no such module: vec0. The connection didn’t load the extension. Callsqlite_vec.load(db)on every connection that touches the vector table.- An upgrade breaks the queries. sqlite-vec is pre-v1, so pin the version and re-test before upgrading.
Conclusion
sqlite-vec puts vector search inside an ordinary SQLite file. Install it with pip and load it on each connection. Then query vec0 tables with MATCH … AND k = N. The rest of the database stays readable by any SQLite tool. Compare libSQL’s vector index, which makes stock SQLite miscount rows in the indexed table and refuse inserts and deletes there.
Further reading
- libSQL
- Python SQLite3 tutorial
- SQLite JSON
- sqlite-vec: README
Tested with Python 3.13.14 (SQLite 3.53.4), sqlite-vec 0.1.9 and the sqlite3 shell 3.51.0 on 7 October 2026.
