SQLite ships with a JSON extension that lets you store and query JSON documents directly in tables. You can keep your schema flexible while still using SQL to slice and dice structured data.
Example: extracting fields from a JSON column:
CREATE TABLE events (
id INTEGER PRIMARY KEY,
payload TEXT NOT NULL -- JSON
);
SELECT
json_extract(payload, '$.user.id') AS user_id,
json_extract(payload, '$.action') AS action,
json_extract(payload, '$.metadata') AS metadata
FROM events
WHERE json_extract(payload, '$.action') = 'login';
You can also create indexes on JSON expressions, making queries over semi-structured data surprisingly fast.
SQLite’s FTS5 extension turns it into a capable full-text search engine. Instead of bolting on an external search service, you can keep everything in a single database file.
Example: building a simple search index:
CREATE VIRTUAL TABLE docs USING fts5(
title,
body,
tokenize = "porter"
);
INSERT INTO docs (title, body) VALUES
('SQLite Guide', 'Learn how to use SQLite effectively.'),
('Local-first Apps', 'Why local storage and sync matter.');
SELECT rowid, title
FROM docs
WHERE docs MATCH 'local NEAR/5 storage';
You get ranking, phrase queries, prefix searches, and more—without leaving SQLite or managing a separate service.
SQLite supports common table expressions (CTEs) and window functions, which unlock a whole class of analytical queries that used to require heavier databases.
Example: computing running totals with a window function:
SELECT
user_id,
created_at,
amount,
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY created_at
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM payments
ORDER BY user_id, created_at;
Combine this with CTEs and you can build surprisingly rich reports and dashboards on top of a single SQLite file.
SQLite is famous (or infamous) for its flexible typing model. Modern SQLite adds STRICT tables, which enforce type constraints much more like PostgreSQL or other traditional databases.
Example: defining a strict table:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1
) STRICT;
With strict tables, invalid types are rejected at insert time, making schemas more predictable and reducing subtle bugs—especially in larger codebases.
Generated columns let you store expressions as virtual or stored columns, keeping derived data close to the source without duplicating logic across your application.
Example: a normalized search field:
CREATE TABLE contacts (
id INTEGER PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
full_name TEXT GENERATED ALWAYS AS (
trim(first_name || ' ' || last_name)
) STORED
);
CREATE INDEX idx_contacts_full_name ON contacts(full_name);
Now every insert or update keeps full_name in sync automatically, and you can index and query it efficiently.
Write-ahead logging (WAL) is a journaling mode that improves concurrency and performance for many workloads. Readers don’t block writers, and writers don’t block readers in the common case.
Enabling WAL is a single pragma call:
PRAGMA journal_mode = WAL;
For desktop apps, local-first tools, and small services, WAL mode can dramatically improve perceived performance while keeping SQLite’s simplicity and reliability.
And ON CONFLICT which can help dedupe among other things in a simple and performant way.
However, I will concede, and the article doesn't mention at all, far less are aware that you can build HA, cross region replicated SQLite using purely OSS software provided you architect your software around it. Now that would be a really good `Modern SQLite: Features You Didn't Know It Had` article!
Another interesting discussion point is how far self hosted PostgreSQL and pgBackRest can get you to a near-zero data loss high RPO, RTO setup. Its simply amazing we can self host all this.
with thing_key as (
insert into item(key, description) values('thing', 'a thing') on conflict do nothing )
insert into user_note(uid, key, note) values (123, 'thing', 'I like this thing') on conflict (uid, thing) do update set note = 'I like this thing');All of the listed features except for strict tables and generated columns have been in SQLite for 10+ years, and those two are certainly not new. The JSON APIs were not made part of the standard distribution until 3.38 (2022-02) but were added in 3.9 (2015-10) and widely used long before they were upgraded from an optional extension to a core feature.
- Generated columns: 3.31 (2020-01)
- Strict tables: 3.37 (2021-11)
Correct.
As I mentioned in my GP, these features have been around a long, long, long time and in the current age of AI that would happily tell you these features exist if you remotely even hint at it, I would assume one would really have to go out of their way to be ignorant of them.
It doesn't hurt to have an article like this reiterate all that information though, I just would have loved the same level of effort put into something that's not as easily available yet.
A gap exists, and the article doesn't mention at all, about actually configuring solutions like litestream, rqlite, dqlite (not the same!) to build HA, cross region replicated SQLite using purely OSS software provided you architect your software around it. Now that would be a really good "Modern SQLite: Features You Didn't Know It Had" article!
Another interesting discussion point is how far self hosted PostgreSQL and pgBackRest can get you to a near-zero data loss high RPO, RTO setup. It just blows my mind that we can self host all this on consumer grade hardware without having to touch a DC or "the cloud" at all.