SQLite FTS5.
Create
CREATE VIRTUAL TABLE posts_fts USING fts5(
title, body,
content='posts', content_rowid='id',
tokenize='porter unicode61'
);
External content: keep main data in posts, only index in fts.
Triggers (keep in sync)
CREATE TRIGGER posts_ai AFTER INSERT ON posts BEGIN
INSERT INTO posts_fts(rowid, title, body) VALUES (new.id, new.title, new.body);
END;
CREATE TRIGGER posts_au AFTER UPDATE ON posts BEGIN
INSERT INTO posts_fts(posts_fts, rowid, title, body) VALUES('delete', old.id, old.title, old.body);
INSERT INTO posts_fts(rowid, title, body) VALUES (new.id, new.title, new.body);
END;
CREATE TRIGGER posts_ad AFTER DELETE ON posts BEGIN
INSERT INTO posts_fts(posts_fts, rowid, title, body) VALUES('delete', old.id, old.title, old.body);
END;
Search
SELECT * FROM posts_fts WHERE posts_fts MATCH 'redis';
SELECT * FROM posts_fts WHERE posts_fts MATCH '"exact phrase"';
SELECT * FROM posts_fts WHERE posts_fts MATCH 'redis OR memcached';
SELECT * FROM posts_fts WHERE posts_fts MATCH 'redis AND tutorial';
SELECT * FROM posts_fts WHERE posts_fts MATCH 'NEAR(redis tutorial, 5)';
SELECT * FROM posts_fts WHERE posts_fts MATCH 'title:redis';
Join with main
SELECT p.* FROM posts p
JOIN posts_fts fts ON p.id = fts.rowid
WHERE fts.posts_fts MATCH 'redis';
Ranking (bm25)
SELECT *, bm25(posts_fts) AS rank
FROM posts_fts
WHERE posts_fts MATCH 'redis'
ORDER BY rank; -- lower = better
Snippets / highlights
SELECT snippet(posts_fts, 1, '<b>', '</b>', '...', 10), bm25(posts_fts)
FROM posts_fts WHERE posts_fts MATCH 'redis';
SELECT highlight(posts_fts, 0, '<mark>', '</mark>')
FROM posts_fts WHERE posts_fts MATCH 'redis';
Rebuild
INSERT INTO posts_fts(posts_fts) VALUES('rebuild');
Tokenizers
unicode61(default): handles Unicode.porter: stemming.ascii: ASCII only.- Custom tokenizer extension.
Prefix queries
SELECT * FROM posts_fts WHERE posts_fts MATCH 'redi*';
Performance
FTS5 is fast. Indexes word-by-word. Searches in milliseconds even for millions of docs.
Common mistakes
- Forgetting to sync via triggers.
- Mixing FTS5 syntax with regular WHERE.
- Search query injection (escape special chars).
Read this next
- SQLite Cheatsheets index — back to the series TOC.
If you want my FTS5 setup, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .