Open-source project
sqliteai/sqlite-vector avatar
sqliteai/sqlite-vector

SQLite-Vector: vector search as a BLOB column in an ordinary SQLite table

SQLite-Vector is a cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database.

1,194 stars53 forksCApache-2.0

At a glance

What is it?
SQLite-Vector is a C extension for SQLite that adds vector search without virtual tables, so vectors live as BLOBs in normal tables and are queried with a scan function. It supports Float32, Float16, BFloat16, Int8, UInt8, 1-bit, and TurboQuant 2, 3, and 4-bit quantization, and it defaults to 30MB of memory.
Who is it for?
SQLite-Vector fits a team that already runs SQLite on device or at the edge, that wants semantic search without standing up a separate vector service, and that accepts exact or quantized scans over an existing schema. It is the wrong choice for a server-side index over tens of millions of vectors, where a dedicated system is the right answer, and it is the wrong choice if you need a hosted, multi-tenant vector database with its own access control.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 42 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Vectors in a table you already have

SQLite-Vector is a cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database. The header puts it plainly: production-grade vector search inside SQLite, with exact search, SIMD distance kernels, and SIMD 2/3/4-bit TurboQuant scans, running anywhere SQLite runs, on mobile, in the browser, at the edge, and on a server.

The architectural decision is stated as a highlight: no virtual tables required, so you store vectors directly as BLOBs in ordinary tables. Every other vector extension in this space tends to introduce a virtual table, a special table type with its own schema, its own shadow tables, and its own way of being queried. The comparison table in the project summary puts the difference bluntly, since traditional solutions usually require special virtual tables, and the stated benefit is that it just works with your existing database schema and tools.

Two other rows in that table are worth pausing on. The preindexing row says traditional solutions can take hours for large datasets, and SQLite-Vector needs zero preindexing, no long preprocessing or index-building phases. And the external server row says traditional solutions often need Redis, FAISS, Weaviate, and the like.

The consequence of the design is that a vector search is a join against a table function rather than a special query language. The example makes it concrete:

sql
SELECT e.id, v.distance FROM images AS e
   JOIN vector_quantize_scan('images', 'embedding', ?, 20) AS v
   ON e.id = v.rowid;

That is ordinary SQL. You can filter with a WHERE clause, you can join other tables, and the query planner is the one you already have.

The memory figure is also a design constraint rather than an accident. The library uses just 30MB of memory by default, and the highlights call that a low memory footprint, and the summary positions the whole thing as ideal for Edge AI applications where the process has a hard budget.

Initialization, metrics, and the normalized shortcut

The vector data is stored as a plain BLOB, and the library needs to be told how to interpret it. That is what the init call does:

sql
SELECT vector_init('images', 'embedding', 'type=FLOAT32,dimension=384');

The three arguments are the table, the column, and a comma separated set of options. The type says how the bytes in the BLOB are laid out, and the dimension says how many values that is.

By default, the distance function is L2. To use a different metric you specify one of the listed options: `distance=L1`, `distance=COSINE`, `distance=DOT`, `distance=SQUARED_L2`, or `distance=HAMMING`. The metric is fixed at init time, which means it is a property of how the data was prepared rather than something you can change per query. If your embeddings are computed for cosine similarity, which most are, you need to set the metric at init time to match, or every distance you get back is measuring something else.

There is one optimisation available for the common case, and it is worth understanding before you use it:

sql
-- If your embeddings are already unit length, say so: FLOAT32 cosine scans then compute
-- 1 - dot instead of the full cosine, with the same results.
-- SELECT vector_init('images', 'embedding', 'type=FLOAT32,dimension=384,distance=COSINE,normalized=1');

For unit-length vectors, cosine similarity is one minus the dot product, so the `normalized=1` flag lets the kernel skip the normalisation step inside the scan. The results are the same. The flag is a claim about your data, and if the vectors are not actually unit length, the results are wrong rather than merely different.

The example table itself is as plain as it gets:

sql
CREATE TABLE images (
  id INTEGER PRIMARY KEY,
  embedding BLOB, -- store Float32/UInt8/etc.
  label TEXT
);

An integer primary key, a BLOB column, and a text label. There is no vector column type, no index declaration, and no special table. That is the point of the whole design.

TurboQuant and the two ways to search

The quantization story is the more interesting half of the library, and it has two distinct mechanisms.

The first is the ordinary quantize call, which is what you use when you want a compressed copy of your vectors:

sql
SELECT vector_quantize('images', 'embedding');

The second is TurboQuant, which is a compact data-oblivious vector quantizer inspired by the Google Research paper on online vector quantization with near-optimal distortion rate. The project summary explains the mechanism: it stores each vector as low-bit scalar codes plus one scale value, then scores directly from SIMD lookup-table kernels without reconstructing full vectors.

That is the key idea, and it is worth being precise about. Most quantization schemes reduce memory by storing fewer bits per dimension, and then the scan has to reconstruct the vectors or approximate the distance from the codes, which costs arithmetic. TurboQuant stores the codes and scores from lookup tables, so the scan never reconstructs a vector. The scale value per vector is what lets the codes be relative to that vector's magnitude rather than absolute.

You quantize with TurboQuant by asking for it:

sql
SELECT vector_quantize('images', 'embedding', 'qtype=TURBO,qbits=4');

and the search is the same scan function:

sql
SELECT e.id, v.distance FROM images AS e
   JOIN vector_quantize_scan('images', 'embedding', ?, 20) AS v
   ON e.id = v.rowid;

There is a preload step, and the comment gives the number:

sql
-- Optional preload quantized version in memory (for a 4x/5x speedup) 
SELECT vector_quantize_preload('images', 'embedding');

The supported storage types are Float32, Float16, BFloat16, Int8, UInt8, 1Bit, and TurboQuant 2/3/4-bit quantization. The half-precision and brain-float types are the interesting ones for on-device work, because they halve the memory of Float32 without the loss of the 8-bit and 4-bit paths, and 1-bit is the extreme.

The scan also has a streaming mode, and the comment is about the interface rather than the performance:

sql
-- Streaming mode: omit k to get rows progressively, use SQL to filter and limit
SELECT e.id, v.distance FROM images AS e
   JOIN vector_quantize_scan('images', 'embedding', ?) AS v
   ON e.id = v.rowid
   WHERE e.label = 'cat'
   LIMIT 10;

Omit the k and you get rows progressively, and because the results arrive as rows you can filter and limit in SQL. That is a genuinely different shape from a vector database that returns a fixed top-k list, and it is the shape that lets you use a vector search as a filter inside a larger query.

Loading it, and the WebAssembly route

Getting the extension onto your machine is straightforward, and there are two ways to load it.

Pre-built binaries are published on the releases page for Linux on x86 and ARM, macOS on x86 and ARM, Windows on x86, Android, and iOS. The summary also notes that the extension can be embedded directly into your application rather than loaded as a separate file, which is what you want on a platform where shipping an unsigned shared library alongside your app is awkward.

The two loading forms are:

sql
-- In SQLite CLI
.load ./vector

-- In SQL
SELECT load_extension('./vector');

The first is the SQLite command line's own dot-command for loading an extension, and the second is the SQL function that does the same thing from within a statement. Both take a path to the shared library, which on Windows would be the DLL and on macOS the dylib, as the Makefile's target names show.

The WebAssembly route is the one that makes the browser claim work. You can download the WebAssembly version of SQLite with the SQLite Vector extension enabled from the `@sqliteai/sqlite-wasm` package on npm. So the same C code that runs as a native extension on a phone is also built into a WASM build of SQLite for the browser. That is the same design decision as SQLite itself, and it is what lets the browser claim in the highlights be real rather than aspirational.

The build system in the Makefile shows how many platforms this really covers. It supports compilation for Linux, macOS, Windows, Android, and iOS, and it works out the platform from the operating system, using `uname` on Unix-like systems and a PowerShell call for the processor count on Windows. It sets the compile flags with `-Wall -Wextra -Wno-unused-parameter` and parallelises the build across all available cores. The macOS branch builds a universal binary with both `-arch x86_64 -arch arm64` unless an ARCH is specified, and the Android branch requires the ARCH to be set, erroring out if it is not, with the accepted values being x86_64, arm64-v8a, or armeabi-v7a.

The other files in the tree are the ones you would expect for a project of this kind. There is an `API.md` for the function reference, a `QUANTIZATION.md` for the quantization details, a `CHANGELOG.md`, a `Package.swift` for the iOS package, a `jitpack.yml` for the Android build integration, an `examples/` directory with a `semantic_search` example, and `packages/` for the language bindings.

The benchmark table, and how to add to it

The project has a public benchmark table, and the contribution procedure is unusually frictionless.

To add your machine to the table, one command, passing the CPU name and nothing else:

bash
make benchmark HARDWARE="Apple M5 Pro"

And then the description of what that does, which is the part that tells you the numbers are comparable. It builds `test/benchmark.c` at `-O3` with the same per-translation-unit SIMD flags the shipped extension uses, runs k=20 over 1,000,000 vectors of dimension 768 with cosine distance and 20 queries reporting the best, and prints two rows ready to paste, unedited, into the table below.

The detail that makes the table meaningful is the same per-translation-unit SIMD flags as the shipped extension. A benchmark that compiles the test differently from the product measures the wrong thing, and the fact that this is specified is the difference between a comparable table and a set of incomparable numbers.

The workload itself is a sensible default. A million vectors at dimension 768 is a realistic embedding set size, k=20 is a typical top-k, cosine distance is what most embedding models are compared with, and twenty queries is enough to get past the noise of a single query's cache behaviour.

The licence is Apache-2.0, the primary language is C, and the release history is short and recent: 1.0.0 in May 2026, 1.1.0 in August 2026, and 1.1.2 on 2026-09-11, with the last push on the same day as the 1.1.2 tag. A 1.0 release in 2026 for a library whose project has existed longer than that is a signal that the API was only recently settled, which matters if you are depending on it.

Where SQLite-Vector is the wrong tool

The limits follow from the design, and several of them are about scale.

First, this is a scan, not an index. There is no preindexing, which is the feature that makes it usable on a dataset you already have, and it is also the reason a query is a linear scan over the vectors. At a million vectors that is fine, and the benchmark is run at a million. At a hundred million vectors, a linear scan per query is a different problem, and the honest answer at that scale is a purpose-built index or a dedicated service.

Second, the accuracy cost of quantization is on you to measure. TurboQuant at 2, 3, or 4 bits reduces the memory per vector by a factor of four, eight, or sixteen against Float32, and the scan returns a distance computed from codes rather than from the original values, so the nearest neighbour is a near neighbour. The design is well founded, the paper it is based on is a research result, and neither of those things tells you what your recall is on your data.

Third, the alternative comparison table in the project summary lists what traditional solutions give you, and one of those is a scale you do not have. The table is a fair summary of a real difference, and it is also a comparison against a category of tool chosen on the grounds that the tool does not need those features. If your workload is a hundred million vectors with a high recall requirement and a concurrent write load, the honest comparison is not against a vector database, it is against doing the search in a specialised system.

Fourth, and this is a platform one, the mobile and embedded story has real constraints. Thirty megabytes of memory by default is fine for a desktop and noticeable on a phone, the WASM build is a different performance profile from the native one, and iOS distribution means either embedding the extension in your app or signing a dylib. The library supports these platforms, and the packaging work is on you.

Fifth, the managed offering is a separate product. The header links to a free managed instance with 512 MB and 20 connections and no credit card, and there is a family of sibling projects, Vector, Sync, Columnar, JS, AI, Agent, Memory, and MCP. That is a company building a platform around this extension, and it is worth being clear in your own assessment that you are adopting one component of a commercial ecosystem rather than a standalone library with an independent governance model.

Sixth, the API is new enough to move. A 1.0 release in May 2026 for a project with an earlier life, and the licence file is Apache-2.0, so the terms are favourable, but a young API with a young release history is a thing to pin carefully.

Editorial conclusion

SQLite-Vector fits a team that already runs SQLite on device or at the edge, that wants semantic search without standing up a separate vector service, and that accepts exact or quantized scans over an existing schema. It is the wrong choice for a server-side index over tens of millions of vectors, where a dedicated system is the right answer, and it is the wrong choice if you need a hosted, multi-tenant vector database with its own access control. Check three things before you adopt it: that the distance metric and the normalized flag match your embeddings, since the metric defaults to L2 and the normalized shortcut changes the arithmetic, that the quantization path you pick is one you have measured rather than one that sounds cheaper, and that your platform has a prebuilt binary, since the release page covers Linux, macOS, Windows, Android, and iOS with specific architectures.

Frequently asked questions

What does SQLite-Vector add to SQLite and how are vectors stored?

It is a C extension that adds vector search to SQLite, and it stores vectors directly as BLOBs in ordinary tables, with no virtual tables required. You initialise a column with a call like `vector_init('images', 'embedding', 'type=FLOAT32,dimension=384')`, and the distance function defaults to L2, with L1, COSINE, DOT, SQUARED_L2, and HAMMING available as options.

What is TurboQuant in SQLite-Vector and how do I use it?

TurboQuant is a compact data-oblivious vector quantizer inspired by a Google Research paper on online vector quantization. It stores each vector as low-bit scalar codes plus one scale value and scores directly from SIMD lookup-table kernels without reconstructing full vectors, and you enable it with `vector_quantize('images', 'embedding', 'qtype=TURBO,qbits=4')`.

How do I load the SQLite-Vector extension?

Download the pre-built binary for your platform from the releases page, which cover Linux, macOS, Windows, Android, and iOS, then load it with `.load ./vector` in the SQLite CLI or with `SELECT load_extension('./vector')` in SQL. You can also embed it directly into your application, and a WebAssembly build with the extension enabled is published as the `@sqliteai/sqlite-wasm` npm package.

What is the memory footprint of SQLite-Vector and can I preload a quantized index?

The library defaults to just 30MB of RAM usage, which is what makes it suitable for edge and on-device workloads. There is an optional preload step, `vector_quantize_preload('images', 'embedding')`, which loads the quantized version into memory for a 4x/5x speedup according to the project documentation.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. sqliteai/sqlite-vector on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sqliteai-sqlite-vector.svg)](https://hysenlabs.com/projects/sqliteai-sqlite-vector)

Was this useful?