Peter Hoffmann

Using SQLite with JSON Data

Using SQLite with JSON Data

SQLite does not have a native JSON column type like PostgreSQL. Instead, JSON is stored as TEXT (or sometimes BLOB), and queried or modified using SQLite’s JSON1 extension. Make sure your SQLite build includes JSON1 (for example, run: SELECT json('null');).

Key points:

Example: Blog posts with arbitrary metadata

Table schema

CREATE TABLE blogpost (
  id        INTEGER PRIMARY KEY,
  slug      TEXT NOT NULL UNIQUE,
  title     TEXT NOT NULL,
  body      TEXT NOT NULL,
  metadata  TEXT NOT NULL DEFAULT '{}',

  -- Ensure metadata always contains valid JSON
  CHECK (json_valid(metadata))
);

The metadata column can store any structure you like: tags, SEO data, flags, analytics, experiments, etc.

Inserting data

INSERT INTO blogpost (slug, title, body, metadata)
VALUES
(
  'sqlite-json',
  'Using SQLite with JSON',
  '...',
  json('{
    "tags": ["sqlite", "json"],
    "reading_time_min": 7,
    "seo": {
      "title": "SQLite JSON Guide",
      "canonical": "https://example.com/sqlite-json"
    },
    "draft": false,
    "published_at": "2025-12-01",
    "views": 1234
  }')
),
(
  'draft-post',
  'Unreleased Ideas',
  '...',
  json('{
    "tags": ["notes"],
    "draft": true,
    "assigned_to": "alex",
    "experiments": [
      {"name": "A/B", "variant": "B"}
    ]
  }')
);

Using json('...') helps normalize the JSON and ensures validity.

Querying JSON data

Extract a single value

SELECT
  id,
  title,
  json_extract(metadata, '$.reading_time_min') AS reading_time_min
FROM blogpost;

Filter by a JSON boolean (draft posts)

SELECT id, slug, title
FROM blogpost
WHERE json_extract(metadata, '$.draft') = 1;

JSON true and false are typically returned as 1 and 0.

Query nested fields

SELECT id, slug
FROM blogpost
WHERE json_extract(metadata, '$.seo.title') LIKE '%Guide%';

Query array contents (posts tagged with "json")

SELECT DISTINCT b.id, b.slug, b.title
FROM blogpost AS b
JOIN json_each(b.metadata, '$.tags') AS t
WHERE t.value = 'json';

json_each() expands a JSON array into rows.

Numeric comparisons on JSON values

SELECT
  id,
  slug,
  json_extract(metadata, '$.views') AS views
FROM blogpost
WHERE json_extract(metadata, '$.views') > 1000;

Check if a key exists

SELECT id, slug
FROM blogpost
WHERE json_type(metadata, '$.seo.canonical') IS NOT NULL;

json_type() returns NULL if the path does not exist.

Updating JSON data

Add or update a key

UPDATE blogpost
SET metadata = json_set(metadata, '$.views', 2000)
WHERE slug = 'sqlite-json';

Remove a key

UPDATE blogpost
SET metadata = json_remove(metadata, '$.assigned_to')
WHERE slug = 'draft-post';

Append a value to a JSON array

SQLite does not have a simple “append” operator, so arrays are often rebuilt explicitly:

UPDATE blogpost
SET metadata = json_set(
  metadata,
  '$.tags',
  (
    SELECT json_group_array(value)
    FROM (
      SELECT value FROM json_each(metadata, '$.tags')
      UNION ALL SELECT 'sqlite'
    )
  )
)
WHERE slug = 'draft-post';

Use UNION (instead of UNION ALL) to avoid duplicates.

Indexing JSON for performance

If you frequently query a JSON path, create an expression index:

CREATE INDEX idx_blogpost_draft
ON blogpost (json_extract(metadata, '$.draft'));

CREATE INDEX idx_blogpost_published_at
ON blogpost (json_extract(metadata, '$.published_at'));

These indexes allow SQLite to efficiently filter on JSON values.

Summary

This approach works well for flexible, evolving metadata such as blog post attributes, feature flags, or analytics data.

Further Information

The blog post SQLite JSON Virtual Columns and Indexing explains the concept of virtual columns in SQLite, which allow you to create computed columns based on JSON extraction expressions. The article also covers how to efficiently index these virtual columns for better performance when querying JSON fields in your tables, with examples demonstrating schema design, creating indexes, and writing efficient queries with SQLite's built-in JSON support. The corresponding Hacker News Discussion has more information.

You can use https://sqliteonline.com to test the examples in the browser without installing any software.

For more information, consult the SQLite JSON documentation.