Complete reference for SQLite JSON functions covering json_extract, json_set, json_insert, json_remove, json_each, json_tree, json_array, json_object, and json_patch. Every function includes syntax, path expression examples, and differences from PostgreSQL JSONB functions. Updated for SQLite 3.52.
SQLite JSON functions provide full JSON storage and querying support, storing JSON as plain TEXT. json_extract() retrieves values from a JSON string using path expressions (e.g. $.key or $[0]), json_set() updates values, json_each() expands a JSON array into rows, and json_tree() recursively walks the entire JSON structure. Unlike PostgreSQL's JSONB, SQLite JSON has no binary storage format or native indexing — use generated columns with indexes to accelerate JSON queries.
json
SQLite 3.9.0 (2015-10-14); JSON5 support added in SQLite 3.38.0 (2022-02-22) as built-in, 3.42.0 for JSON5 reading→ TEXT
Verifies that its argument is valid JSON (or JSONB) and returns a minified, canonical RFC-8259 text representation with all unnecessary whitespace removed. Converts JSON5 input to canonical JSON. Throws an error if the input is not well-formed.
Signature
json(json)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
A well-formed JSON string, JSON5 string, or JSONB blob. Whitespace is stripped and the result is canonical JSON text.
Useful for normalizing stored JSON before comparison or display
sql
SELECTjson(profile) FROMusersWHEREid=1;
→Minified JSON profile string
⚠Anti-Pattern— Using json() to pretty-print output
Developers sometimes call json() expecting formatted, readable output. In reality json() minifies the input — it strips all whitespace and returns the most compact canonical form. Passing pretty-printed JSON through json() produces a single dense line with no newlines or indentation.
✓ Instead: Use json_pretty() when you need human-readable, indented JSON output.
When passing a text string as a VALUE argument to functions like json_insert() or json_object(), SQLite treats it as a literal string by default. Wrap it with json() to make SQLite treat it as actual JSON structure: json_object('data', json('[1,2,3]')) produces {"data":[1,2,3]} rather than {"data":"[1,2,3]"}.
→Stores validated, whitespace-stripped JSON; raises error if malformed
◆SQLite Note
SQLite's json() works on TEXT strings (storing JSON as plain text). This differs from PostgreSQL's JSONB type, which is a binary format. In PostgreSQL, casting to JSON with CAST(x AS JSON) or using json_build_object() is the equivalent. SQLite also understands JSON5 extensions (unquoted keys, trailing commas, comments) since 3.42.0, but always outputs canonical RFC-8259.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
Returns the binary JSONB (internal parse-tree) representation of the input JSON text or JSONB blob. Storing JSON as JSONB avoids re-parsing on every access. If the input is already JSONB, returns a copy without deep validation.
Signature
jsonb(json)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
A well-formed JSON string or JSONB blob. Returns the JSONB binary blob representation.
Examples
Store JSON as binary JSONB for faster subsequent reads
⚠Anti-Pattern— Storing jsonb() output in a TEXT column
jsonb() returns a BLOB, not TEXT. If the destination column is declared TEXT, SQLite silently coerces the blob during INSERT. The stored value is no longer valid JSONB binary, so subsequent jsonb_ function calls must re-parse it as text, defeating the entire performance benefit.
✓ Instead: Declare JSONB columns as BLOB (or ANY) so the binary representation is preserved without silent coercion.
When multiple JSON operations occur in sequence, keep data in JSONB throughout the pipeline using jsonb_ variants. Only convert to text at the very end: SELECT json(jsonb_set(jsonb_insert(jsonb(raw), '$.a', 1), '$.b', 2)) avoids two intermediate parse steps. The aggregate functions (json_group_array etc.) are an exception — they actually prefer text inputs.
→Stores as binary JSONB; faster for subsequent json_extract() calls
◆SQLite Note
JSONB in SQLite (added 3.45.0) is SQLite's own internal binary parse-tree format stored as a BLOB. It is NOT compatible with PostgreSQL's JSONB binary format — they share the name but not the format. PostgreSQL JSONB offers O(1) key lookup; SQLite JSONB is O(N) but avoids repeated parsing. Use json_valid(x, 4) to check if a BLOB is JSONB.
Constructs and returns a well-formed JSON array text from zero or more arguments. SQL TEXT values become JSON strings unless they come directly from another JSON function, in which case they are embedded as JSON. NULL becomes JSON null. BLOBs raise an error.
Signatures
json_array()
json_array(value1, value2, ...)
Parameters
Parameter
Type
Description
value
ANY (variadic)
Values to include in the array. TEXT becomes a JSON string. INTEGER/REAL become JSON numbers. NULL becomes JSON null. Results of other JSON functions are embedded as JSON.
Examples
Mix integers and strings; text is quoted
sql
SELECTjson_array(1, 2, 'three', 4);
→'[1,2,"three",4]'
Nest arrays by passing json_array() as a value argument
Without json() wrapper the string '[1,2]' would be embedded as a quoted string
sql
SELECTjson_array('a', json('[1,2]'));
→'["a",[1,2]]'
Zero arguments returns an empty JSON array
sql
SELECTjson_array();
→'[]'
⚠Anti-Pattern— Building JSON arrays with string concatenation
Concatenating strings to form a JSON array — e.g. '["' || col || '"]' — does not escape special characters, quotes, or control characters inside values. A value containing a double-quote or backslash will produce malformed JSON that silently passes string checks but fails later parsing.
✓ Instead: Always use json_array(col) to construct arrays; it handles all escaping automatically and returns guaranteed-valid JSON.
SQLite distinguishes between VALUE arguments (which default to strings) and the output of JSON functions (which are treated as JSON). If you have a column that stores JSON text and want it embedded as actual JSON structure — not a quoted string — wrap it with json(): json_array(json(col)) instead of json_array(col).
→'[1,"Alice",9850]' — each row serialised as a JSON array
◆SQLite Note
Equivalent to PostgreSQL's json_build_array() or ARRAY constructor. In PostgreSQL you can use ARRAY[1,2,'three'] for typed arrays, or json_build_array(1,2,'three') for JSON. SQLite json_array() works entirely on TEXT output. Use jsonb_array() to get BLOB output for chaining with other jsonb_ functions.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
Works exactly like json_array() but returns the constructed JSON array as a binary JSONB blob instead of text. Use when the result will be passed to other jsonb_ functions or stored in a JSONB column for performance.
Signatures
jsonb_array()
jsonb_array(value1, value2, ...)
Parameters
Parameter
Type
Description
value
ANY (variadic)
Values to include in the array. Same coercion rules as json_array(). Returns BLOB instead of TEXT.
Examples
Returns a BLOB — not human-readable; use json() to convert back to text
sql
SELECTjsonb_array(1, 2, 3);
→BLOB (binary JSONB representing [1,2,3])
Wrap with json() to get readable text output
sql
SELECTjson(jsonb_array(1, 2, 3));
→'[1,2,3]'
Store as JSONB for faster subsequent json_extract calls
⚠Anti-Pattern— Storing jsonb_array() result in a TEXT column
jsonb_array() returns a BLOB. Inserting or assigning this BLOB into a TEXT column causes SQLite to silently convert it to a text representation that is NOT valid JSONB. All subsequent jsonb_ calls on that column then fall back to text-path processing with no error.
✓ Instead: Always store jsonb_array() results in a BLOB-typed column to preserve the binary format.
When building a JSON structure with multiple nested function calls, use jsonb_ variants for intermediate results and only call json() at the outermost level. This avoids repeated text parsing at each step: json(jsonb_set(jsonb_insert(jsonb_array(...), ...), ...)).
example
SELECTjsonb_array('x', 42, null) ASresult;
→JSONB binary encoding of ["x",42,null]
◆SQLite Note
No direct PostgreSQL equivalent — PostgreSQL's json_build_array() always returns text JSON. JSONB in SQLite is a performance optimization at the function-call level. Store JSONB blobs in columns declared as BLOB or ANY (not TEXT) to avoid accidental TEXT coercion.
Returns the number of elements in the top-level JSON array, or in the array located at the given path. Returns 0 if the target is not a JSON array. Returns NULL if the path does not exist. Throws an error for malformed JSON.
Signatures
json_array_length(json)
json_array_length(json, path)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
A well-formed JSON string or JSONB blob.
path
TEXT
Optional JSONPath expression (e.g., '$.items'). If omitted, the top-level element is used.
Examples
Count elements in a top-level array
sql
SELECTjson_array_length('[1,2,3,4,5]');
→5
Count items in a nested array within a stored JSON column
Returns 0 because element at index 1 (value 2) is not an array
sql
SELECTjson_array_length('[1,2,3]', '$[1]');
→0
⚠Anti-Pattern— Calling json_array_length on a JSON object and expecting an error
When passed a JSON object, json_array_length() silently returns 0, not an error or NULL. A WHERE clause like WHERE json_array_length(col) > 0 will silently exclude both empty arrays and all objects, losing rows without any indication that the wrong type was encountered.
✓ Instead: Check json_type(col) = 'array' before calling json_array_length() so objects and non-arrays are handled explicitly.
json_array_length() returns NULL when the path doesn't exist, and 0 when the path exists but points to a non-array. This distinction matters in WHERE clauses: WHERE json_array_length(col, '$.tags') > 0 will only include rows where '$.tags' exists AND is a non-empty array, silently skipping rows where '$.tags' is absent.
→Posts with more than 5 tags stored as a JSON array
◆SQLite Note
Equivalent to PostgreSQL's jsonb_array_length(jsonb) or json_array_length(json). PostgreSQL requires the value to actually be an array and throws an error otherwise. SQLite returns 0 for non-arrays. PostgreSQL uses -> operator for path navigation; SQLite uses a path argument string.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
Returns 0 if the input is valid JSON or JSON5 text, or valid JSONB. If the input contains a syntax error, returns the 1-based character position of the first error in text, or an approximate 1-based byte position for BLOB inputs. Unlike most JSON functions, this does NOT throw an error on bad input.
Signature
json_error_position(json)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
Any text or BLOB to check for JSON validity. Does not throw on malformed input — that is the point of this function.
⚠Anti-Pattern— Treating the returned position as an exact character offset for BLOB inputs
For TEXT inputs json_error_position() returns a reliable 1-based character position. For BLOB (JSONB) inputs the returned value is an approximate byte offset into the binary format, not a character position in the original JSON source. Using that offset to slice the original source string produces misleading context.
✓ Instead: For BLOB inputs, first convert to text with json() and then run json_error_position() on the text form to get a meaningful character position.
Most JSON functions throw errors on bad input — json_error_position() is the exception. Use it as a guard before calling other JSON functions on untrusted input. A value of 0 means safe to proceed. This is particularly useful in ETL pipelines where you want to log and skip bad rows rather than abort the entire query.
→12 — character position of the first JSON syntax error
◆SQLite Note
No direct equivalent in PostgreSQL. PostgreSQL typically raises an exception on malformed JSON; you'd use a PL/pgSQL block with EXCEPTION to catch errors. SQLite's json_error_position() provides a non-throwing alternative specifically for validation. Added in SQLite 3.42.0 — earlier versions need json_valid() which only returns 0/1 without a position.
SQLite 3.9.0 (2015-10-14); ->> operator requires SQLite 3.38.0 (2022-02-22)→ ANY
Extracts and returns one or more values from a JSON string or JSONB blob. With a single path, returns the native SQL type: NULL for JSON null, INTEGER/REAL for numbers, TEXT for strings, and TEXT JSON for arrays/objects. With multiple paths, always returns a JSON array of results.
Signatures
json_extract(json, path)
json_extract(json, path1, path2, ...)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
A well-formed JSON string or JSONB blob.
path
TEXT
A JSONPath expression starting with $. Use $ for the whole document, $.key for an object field, $[N] for array index (0-based), $[#-1] for last element.
Examples
Extract a string — returns native SQL TEXT (no quotes)
⚠Anti-Pattern— Expecting individual scalar values when passing multiple paths
With a single path, json_extract() returns a native SQL scalar. With two or more paths it always returns a JSON array text — even if only one of those paths was added dynamically. Code that branches on the return type will behave differently once a second path is supplied, causing silent type mismatches.
✓ Instead: Call json_extract() once per path when you need individual native-typed values; use the multi-path form only when a JSON array result is explicitly desired.
Since SQLite 3.38.0, col ->> '$.key' is shorthand for json_extract(col, '$.key') and always returns the SQL native type (like json_extract with a single path). The -> operator returns JSON representation instead. You can also chain: data -> '$.address' ->> 'city'. This syntax is compatible with PostgreSQL's JSONB operators.
→'Portland' — city from verified users' nested profile JSON
◆SQLite Note
Comparable to PostgreSQL's jsonb_extract_path() or the ->> operator. Key difference: SQLite json_extract() with a single scalar path returns a native SQL type (TEXT for strings, INTEGER for numbers), whereas MySQL's json_extract() always returns JSON. PostgreSQL ->> returns TEXT. Use jsonb_extract() instead to get JSONB blobs for array/object results. SQLite paths start with $ and use dot/bracket notation similar to PostgreSQL's jsonpath.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
SQLite 3.45.0 (2024-01-15)→ ANY (BLOB for arrays/objects, native type for primitives)
Works the same as json_extract() except that when the result would be a JSON array or object, it returns it as a JSONB blob instead of text JSON. For primitive values (string, number, null, boolean), the behavior is identical to json_extract().
Signatures
jsonb_extract(json, path)
jsonb_extract(json, path1, path2, ...)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
A well-formed JSON string or JSONB blob.
path
TEXT
JSONPath expression. Same syntax as json_extract().
Examples
Extract a nested object as JSONB, then convert to text for display
⚠Anti-Pattern— Using jsonb_extract() when the column is stored as TEXT
jsonb_extract() expects BLOB input for peak efficiency. When the column is TEXT (because JSONB was accidentally stored with TEXT affinity), SQLite must re-parse the text on every call, giving the same performance as json_extract() but with added confusion. There is no error — it silently works slower.
✓ Instead: Ensure JSONB columns are declared BLOB so jsonb_extract() receives a true BLOB and skips the text-parsing step entirely.
If you extract a sub-document and immediately pass it to another JSON function, jsonb_extract() avoids the TEXT→JSONB parse step that json_extract() would require. The performance benefit is largest when the extracted sub-document is complex or large.
→'auth_failure' — extracts event type from JSONB error logs
◆SQLite Note
No direct PostgreSQL equivalent by this name. PostgreSQL's jsonb -> 'key' operator returns JSONB when key exists. In SQLite, jsonb_extract() is the JSONB-returning counterpart to json_extract(). Only available since 3.45.0. For compatibility with older SQLite, fall back to json_extract().
Inserts new values into a JSON document. Only creates new keys/elements — does NOT overwrite existing values. To append to an array, use the special path '$[#]'. Multiple path/value pairs are applied left-to-right.
⚠Anti-Pattern— Using json_insert to update an existing key
json_insert() silently ignores any path that already exists in the document. If used expecting upsert behavior — intending to update an existing field — the existing value is left unchanged and no error or warning is raised. This is a common source of bugs when switching from json_set().
✓ Instead: Use json_set() for upsert semantics; reserve json_insert() only when you explicitly want to protect existing values from overwrite.
SQLite has three modification functions with different upsert semantics: json_insert() creates new keys but never overwrites existing ones; json_replace() updates existing keys but never creates new ones; json_set() does both (upsert). Choose based on your intent to avoid accidental overwrites or silent no-ops.
→Adds email notifications key only if not already present
◆SQLite Note
In PostgreSQL you would use jsonb_set() which always upserts, or the || (concatenation) operator for merging. There is no direct 'insert only' equivalent in PostgreSQL JSONB. SQLite's distinction between insert/replace/set provides finer control. The jsonb_insert() variant returns JSONB blob instead of TEXT.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
Works identically to json_insert() but returns the modified JSON document as a binary JSONB blob instead of text. Only inserts at paths that do not already exist.
⚠Anti-Pattern— Expecting jsonb_insert to overwrite existing keys
Like json_insert(), jsonb_insert() only adds new keys and silently does nothing for paths that already exist. Developers migrating from jsonb_set() sometimes switch to jsonb_insert() and then observe that updates stop happening in production without any error, because the key was already present.
✓ Instead: Use jsonb_set() for upsert behavior; use jsonb_insert() only when the intent is strictly to add absent keys.
If your table stores JSON in a BLOB column (as JSONB), use jsonb_insert() instead of json_insert() to avoid a TEXT→BLOB round-trip on the UPDATE. The stored JSONB is parsed, modified, and the result is written back as JSONB without ever converting to text.
example
SELECTjsonb_insert('{"a":1}', '$.b', 2);
→JSONB binary of {"a":1,"b":2} — inserts key only if absent
◆SQLite Note
PostgreSQL does not have a separate insert-only JSON function. This is SQLite-specific. In PostgreSQL, jsonb_set() with create_missing=true is the upsert, and there is no native 'insert only if absent' for JSONB keys outside of custom logic.
Constructs a well-formed JSON object from alternating label/value pairs. Labels must be TEXT. Values follow standard JSON value coercion: SQL TEXT becomes a JSON string unless it comes from a JSON function. Returns an empty object '{}' with no arguments.
Signatures
json_object()
json_object(label1, value1, label2, value2, ...)
Parameters
Parameter
Type
Description
label
TEXT
Key name for the JSON object property.
value
ANY
Value for the property. TEXT is quoted unless it comes from another JSON function or -> operator.
Without json() wrapper, the string is embedded as a literal string, not an array
sql
SELECTjson_object('data', '[1,2,3]');
→'{"data":"[1,2,3]"}'
⚠Anti-Pattern— Embedding a JSON text column directly without wrapping it in json()
If a column holds JSON text (e.g., '[1,2,3]') and you pass it directly to json_object() as a value, SQLite treats it as a plain SQL string and double-quotes it in the output. The resulting object looks correct in a text dump but contains a JSON string literal where a structured value was expected.
✓ Instead: Wrap JSON-valued column references with json(): json_object('tags', json(col)) to embed them as actual JSON structures rather than string literals.
Combine json_object() with column references to produce JSON row representations without a custom serializer: SELECT json_object('id', id, 'name', name) FROM users. For arrays of rows, wrap with json_group_array(): SELECT json_group_array(json_object('id', id, 'name', name)) FROM users.
Equivalent to PostgreSQL's json_build_object() or jsonb_build_object(). Syntax is identical: alternating key/value pairs. PostgreSQL also has row_to_json() which converts an entire row. SQLite has no row_to_json() but json_object() with explicit columns achieves the same result. Use jsonb_object() for BLOB output.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
Works identically to json_object() but returns the constructed JSON object as a binary JSONB blob instead of text. Use when the object will be stored in a JSONB column or passed to other jsonb_ functions.
Signatures
jsonb_object()
jsonb_object(label1, value1, label2, value2, ...)
Parameters
Parameter
Type
Description
label
TEXT
Key name for the JSON object property.
value
ANY
Value for the property. Same coercion rules as json_object().
⚠Anti-Pattern— Immediately casting jsonb_object() output to TEXT for storage
The whole benefit of jsonb_object() is producing a BLOB that future jsonb_ reads and writes can use without re-parsing. If the result is immediately coerced to TEXT — by column affinity, CAST, or concatenation — the binary representation is lost and the extra cost of jsonb_object() over json_object() was wasted.
✓ Instead: Store jsonb_object() output in a BLOB column and only call json() for display or export at the final step.
When inserting many rows with JSON columns, constructing each row with jsonb_object() instead of json_object() avoids the TEXT→JSONB parse on read. The write is slightly more expensive but all subsequent reads and modifications are faster.
Equivalent to PostgreSQL's jsonb_build_object() in intent, but returns SQLite's own internal JSONB binary format (not PostgreSQL's JSONB format). Not binary-compatible across databases.
Applies an RFC 7396 JSON Merge Patch to the target JSON document. Merge Patch can add, modify, or delete fields in a JSON object. Setting a key to JSON null deletes that key. Arrays are treated as atomic values — you cannot append or modify individual array elements with a patch.
Signature
json_patch(target, patch)
Parameters
Parameter
Type
Description
target
TEXT or BLOB
The base JSON document to patch.
patch
TEXT or BLOB
An RFC 7396 merge patch document. Keys present with non-null values are set/updated; keys with null values are deleted; keys absent from patch are left unchanged.
⚠Anti-Pattern— Using json_patch to delete keys by setting them to null — then being surprised
RFC 7396 merge patch defines keys set to null in the patch document as deletions, not as setting the target key to JSON null. This means json_patch(doc, '{"key":null}') removes "key" entirely from the output. Developers expecting the key to remain present with a null value find it gone silently.
✓ Instead: Use json_patch for field deletion intentionally; to set a key to JSON null while keeping it present, use json_set(doc, '$.key', null) instead.
When a REST API sends partial updates (PATCH requests), json_patch() maps directly to RFC 7396 semantics: the payload is the patch. Absent fields are preserved, present fields are updated, and fields explicitly set to null are deleted. For array modifications (append, update element), use json_insert() or json_set() instead.
→Sets status to 'published' and removes the draft key via RFC 7396 merge patch
◆SQLite Note
No direct equivalent in PostgreSQL by this name. PostgreSQL uses the || (concatenation/merge) operator for shallow merges: '{"a":1}' || '{"b":2}'. For deep nested merges, PostgreSQL requires custom functions. Note that RFC 7396 (json_patch) is a shallow merge for top-level keys but recursively merges nested objects — it does NOT deep-clone nested objects on unchanged paths.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
⚠Anti-Pattern— Chaining jsonb_patch on a TEXT column expecting the JSONB output to be preserved
jsonb_patch() returns a BLOB, but if the target column has TEXT affinity the UPDATE silently coerces the BLOB back to TEXT. On the next call to jsonb_patch() the input is TEXT again and must be re-parsed, negating all performance gains with no error to indicate the problem.
✓ Instead: Confirm the storage column is declared BLOB or ANY before using jsonb_patch() in UPDATE statements so the JSONB result is preserved across writes.
If both the stored document and incoming patch are in JSONB format, jsonb_patch() requires no text parsing at all. This is the fastest possible update path for partial JSON document updates in SQLite.
→JSONB binary of {"a":1,"c":3} — b removed (null), c added
◆SQLite Note
JSONB-returning variant of json_patch(). No PostgreSQL equivalent. PostgreSQL's || merge operator on JSONB is shallow. SQLite's json_patch/jsonb_patch implement RFC 7396 which does recursive object merging.
Returns the JSON document formatted with indentation and newlines for human readability. Default indentation is 4 spaces per level. The optional second argument specifies a custom indentation string (e.g., 2 spaces, or a tab character).
Signatures
json_pretty(json)
json_pretty(json, indent)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
A well-formed JSON string or JSONB blob to format.
indent
TEXT
Optional. Indentation string per nesting level. Defaults to 4 spaces if omitted or NULL.
⚠Anti-Pattern— Storing json_pretty() output persistently in the database
json_pretty() is intended for display and debugging. Storing pretty-printed JSON in a table column inflates storage by 20–40% depending on nesting depth, slows full-table scans, and provides no functional benefit over minified JSON since all json_ functions handle both forms equally.
✓ Instead: Store JSON in its minified form using json() or jsonb(); call json_pretty() only in the SELECT list for display or in application-layer logging.
json_pretty() is ideal for development, logging, and user-facing output where readability matters. Always store JSON in its minified form (using json() or jsonb()) to reduce storage and improve scan performance. Switch to json_pretty() only at the presentation layer.
→Human-readable indented JSON for debug output or admin UI
◆SQLite Note
Similar to PostgreSQL's jsonb_pretty(jsonb) function which also formats JSONB as indented text. PostgreSQL jsonb_pretty() does not take an indent argument (always 4 spaces). SQLite json_pretty() adds the flexible indent parameter. Both are primarily debug/display utilities.
Converts an SQL value to its JSON representation. Strings are double-quoted and escaped; numbers are returned as-is; NULL becomes the JSON null literal. If the argument is already the result of a JSON function, it is returned unchanged (no-op).
Signature
json_quote(value)
Parameters
Parameter
Type
Description
value
ANY
An SQL value (TEXT, INTEGER, REAL, or NULL) to convert to a JSON representation.
Examples
Wraps the string in JSON double-quotes
sql
SELECTjson_quote('hello world');
→'"hello world"'
Numbers pass through unchanged
sql
SELECTjson_quote(3.14159);
→3.14159
SQL NULL becomes the JSON string 'null'
sql
SELECTjson_quote(NULL);
→'null'
Special characters are properly escaped in the JSON output
sql
SELECTjson_quote('It''s a "test"');
→'"It''s a \"test\""'
A JSON-looking string is escaped, not parsed — use json('[1,2,3]') if you want actual JSON
sql
SELECTjson_quote('[1,2,3]');
→'"[1,2,3]"'
⚠Anti-Pattern— Using json_quote() to serialize objects or arrays
json_quote() converts a single SQL scalar into a JSON string literal — it does not serialize arrays or objects. Passing a TEXT string that looks like JSON (e.g., '[1,2,3]') through json_quote() double-quotes and escapes it, producing '"[1,2,3]"' — a JSON string, not an array.
✓ Instead: Use json_object() or json_array() to construct JSON objects and arrays; use json_quote() only when you specifically need to wrap a scalar SQL value as a JSON string literal.
When manually constructing JSON via string concatenation (which you should generally avoid), json_quote() correctly handles escaping. But prefer json_object() or json_array() which do not require manual escaping and are safer. json_quote() is most useful for understanding what SQLite considers a valid JSON-encoded value.
Similar in purpose to PostgreSQL's to_json() or to_jsonb() functions. PostgreSQL's to_json(val) converts any SQL value (including composite types and arrays) to JSON. SQLite json_quote() only handles scalars. For complex type serialization in SQLite, use json_object() or json_array().
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
Returns a copy of the JSON document with all elements identified by path arguments removed. Paths that do not exist are silently ignored. Removals are applied left to right, so earlier removals can affect later paths. Called with no path arguments, reformats the JSON (removes whitespace).
Signatures
json_remove(json)
json_remove(json, path)
json_remove(json, path1, path2, ...)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
The base JSON document.
path
TEXT (variadic)
JSONPath expression(s) identifying element(s) to remove. Non-existent paths are silently ignored.
Examples
Remove a single object key
sql
SELECTjson_remove('{"a":1,"b":2,"c":3}', '$.b');
→'{"a":1,"c":3}'
Remove array element at index 2
sql
SELECTjson_remove('[0,1,2,3,4]', '$[2]');
→'[0,1,3,4]'
Strip sensitive fields before returning data to an API
⚠Anti-Pattern— Removing a nonexistent path and expecting an error or NULL return
json_remove() silently returns the unchanged document when the specified path does not exist. There is no NULL result, no 0 rows, and no error. Code that checks the return value to confirm a field was removed will always see a non-null result and incorrectly infer success.
✓ Instead: Check json_type(col, '$.key') IS NOT NULL before calling json_remove() if you need to confirm the target path actually existed.
When removing multiple array elements, remember that each removal shifts indices. Removing '$[0]' and then '$[2]' removes what was originally at index 0 and then what was originally at index 3 (since everything shifts left). To remove by stable positions, work from the end: remove higher indices first, or use '#-N' notation.
→Removes two deprecated keys from the preferences JSON in one call
◆SQLite Note
Equivalent to PostgreSQL's jsonb_delete(jsonb, text) for key deletion or jsonb_delete_path(jsonb, text[]) for path deletion. PostgreSQL also supports the - operator: data - 'key' removes a top-level key. SQLite json_remove() supports full path syntax including array indices. Use jsonb_remove() for BLOB output.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
⚠Anti-Pattern— Assigning jsonb_remove() output to a TEXT column and losing the BLOB
jsonb_remove() returns a BLOB. If the result is assigned to a TEXT column — by column affinity or implicit coercion — the binary JSONB structure is silently converted to text. All subsequent jsonb_ calls on that column must then re-parse from text, and no error indicates the round-trip occurred.
✓ Instead: Always target a BLOB column when assigning jsonb_remove() output; verify column affinity with PRAGMA table_info before deploying.
For complex document transformations involving multiple remove and set operations, keeping the data in JSONB throughout the chain avoids repeated text parsing: jsonb_set(jsonb_insert(jsonb_remove(data, '$.old'), '$.new', val), '$.ts', datetime('now')).
example
SELECTjsonb_remove('{"a":1,"b":2,"c":3}', '$.b');
→JSONB binary of {"a":1,"c":3}
◆SQLite Note
No direct PostgreSQL counterpart by this name. PostgreSQL's - operator on JSONB (jsonb - text) removes a top-level key and returns JSONB. For path-based deletion, PostgreSQL uses #- operator. SQLite's jsonb_remove() supports full JSONPath with nested paths and array indices.
Updates existing values in a JSON document. Only updates keys/elements that already exist — does NOT create new keys. Multiple path/value pairs are applied left to right. Returns the modified JSON text.
⚠Anti-Pattern— Using json_replace to add a new key when it does not yet exist
json_replace() silently ignores paths that do not exist — it only updates. If the key is absent (perhaps not yet written by an older app version), json_replace() returns the original document unchanged with no warning. Developers expecting upsert behavior end up with missing fields in production.
✓ Instead: Use json_set() when the key may or may not exist; reserve json_replace() only for cases where skipping a missing key is the deliberate intent.
json_replace() is ideal for update operations where you want to guarantee you're updating an existing field — if the field is missing (perhaps the schema changed), the operation silently does nothing rather than inserting an unexpected key. Use json_set() when you want upsert semantics.
No direct 'replace only if exists' equivalent in PostgreSQL JSONB. PostgreSQL's jsonb_set(target, path, value, create_if_missing=false) comes closest. The default jsonb_set() is an upsert. SQLite's json_replace() is strictly 'update existing only'.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
→JSONB with updated timestamp and incremented counter
⚠Anti-Pattern— Expecting jsonb_replace to create missing keys
jsonb_replace() is strictly update-only and silently returns the document unchanged when the target path does not exist. A common mistake is using it after a schema change that renamed keys, where the old path no longer exists and updates silently stop being applied with no error.
✓ Instead: Use jsonb_set() for upsert behavior; use jsonb_replace() only when skipping absent keys is the desired outcome.
In a single UPDATE statement, you can combine jsonb_replace() (for fields that must exist) and jsonb_set() (for fields that may or may not exist) by chaining: jsonb_set(jsonb_replace(data, '$.existing_field', val1), '$.maybe_new_field', val2).
→JSONB binary of {"status":"active"} — replaces existing key only
◆SQLite Note
Equivalent to PostgreSQL's jsonb_set(target, path, value, false) which does not create missing keys. SQLite separates the create_if_missing flag into distinct functions (json_insert vs json_replace vs json_set) rather than a parameter.
Upserts values in a JSON document. Creates new keys if they do not exist AND overwrites existing keys if they do. Combines the behavior of json_insert() (create) and json_replace() (update). Multiple path/value pairs are applied left to right.
Signatures
json_set(json, path, value)
json_set(json, path1, value1, path2, value2, ...)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
The base JSON document.
path
TEXT
JSONPath to the element to set.
value
ANY
The value to set. TEXT is quoted unless from a JSON function or -> operator.
Examples
Overwrites existing key 'a'
sql
SELECTjson_set('{"a":1,"b":2}', '$.a', 99);
→'{"a":99,"b":2}'
Creates new key 'c' since it does not exist
sql
SELECTjson_set('{"a":1,"b":2}', '$.c', 99);
→'{"a":1,"b":2,"c":99}'
Upsert pattern: set last_login regardless, create login_count if absent
⚠Anti-Pattern— Calling json_set in a loop to update many fields one at a time
Each json_set() call parses the entire JSON document, applies one change, and serializes a new string. Calling it in a loop of N fields costs N full parse-and-serialize cycles. For even 5–10 fields this is measurably slower than a single multi-argument call, and per-row loops in application code can dominate query time.
✓ Instead: Chain all path-value pairs in a single json_set() call: json_set(doc, '$.a', 1, '$.b', 2, '$.c', 3) to parse and serialize only once.
For most application code, json_set() is the safest default: it creates fields that are missing and updates fields that exist. Use json_insert() when you explicitly want to protect existing values from accidental overwrite. Use json_replace() when a missing field should be a bug signal (silent no-op) rather than auto-created.
SELECTjsonb_set('{}', '$.created', datetime('now'), '$.modified', datetime('now'), '$.active', 1) FROM (SELECT1) init;
→JSONB object built from empty document
⚠Anti-Pattern— Using separate jsonb_set() calls per field instead of chaining
Each individual jsonb_set() call parses the BLOB, applies one change, and writes a new BLOB. Separate calls in application code or nested subqueries for each field multiply the parsing cost unnecessarily. The multi-argument form exists precisely to avoid this.
✓ Instead: Pass all path-value pairs to a single jsonb_set() call: jsonb_set(doc, '$.a', v1, '$.b', v2) so the document is parsed and rewritten only once.
When your table has a BLOB column storing JSONB, jsonb_set() gives you the full upsert capability while keeping the data in binary format throughout. For high-frequency updates on frequently-read JSONB columns, this reduces CPU time significantly compared to text JSON functions.
example
SELECTjsonb_set('{"a":1}', '$.b', 2);
→JSONB binary of {"a":1,"b":2} — inserts or replaces
◆SQLite Note
Comparable to PostgreSQL's jsonb_set(target, path, value) which also returns JSONB. PostgreSQL's path is a text array ('{key1,key2}'), SQLite's path is a dot-notation string ('$.key1.key2'). Both create missing keys by default. SQLite's three-way insert/replace/set split does not exist in PostgreSQL.
Returns the type of the outermost JSON element or the element at the specified path. Returns one of: 'null', 'true', 'false', 'integer', 'real', 'text', 'array', or 'object'. Returns SQL NULL if the path does not exist.
Signatures
json_type(json)
json_type(json, path)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
A well-formed JSON string or JSONB blob.
path
TEXT
Optional JSONPath expression. If omitted, returns the type of the root element.
Examples
Root element is an object
sql
SELECTjson_type('{"a":1,"b":"hello","c":[1,2]}');
→'object'
Check that '$.tags' is an array before calling json_array_length()
⚠Anti-Pattern— Calling json_array_length or json_each without first checking json_type
json_array_length() and json_each() silently return NULL or an empty result set when the target element is not an array. Omitting a json_type() guard means rows with object-valued or scalar-valued columns pass the query without error but contribute NULL or empty results that can produce incorrect aggregates.
✓ Instead: Guard array operations with WHERE json_type(col, '$.field') = 'array' to exclude non-array values explicitly.
In schema-less JSON columns, the same key might hold different types across rows (string in some, object in others). json_type() lets you branch on the actual type at query time using CASE expressions. This is especially useful during data migrations when you need to handle both old and new schema shapes.
→'object' | 'array' — root is an object, items key holds an array
◆SQLite Note
Equivalent to PostgreSQL's jsonb_typeof(jsonb) which returns similar type names ('null', 'boolean', 'number', 'string', 'array', 'object'). Note PostgreSQL uses 'boolean' for both true/false and 'number' for both integer/real, while SQLite distinguishes 'true'/'false' and 'integer'/'real' separately. This distinction is useful for type-precise validation.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
SQLite 3.9.0 (2015-10-14); flags parameter added in SQLite 3.45.0 (2024-01-15)→ INTEGER (0 or 1)
Returns 1 if the argument is valid JSON (or JSONB), 0 if not. The optional flags bitmask controls what counts as valid: 1=RFC-8259 text (default), 2=JSON5 text, 4=superficially valid JSONB blob, 8=strictly valid JSONB blob. Flags can be OR'd together. Returns NULL if either argument is NULL.
Signatures
json_valid(json)
json_valid(json, flags)
Parameters
Parameter
Type
Description
json
ANY
The value to validate. Does not throw on malformed input.
flags
INTEGER
Optional bitmask: 1=strict JSON, 2=JSON5, 4=probably JSONB, 5=JSON or JSONB, 6=JSON5 or JSONB (recommended for general use), 8=strict JSONB, 9=strict JSON+JSONB. Defaults to 1.
Examples
Valid RFC-8259 JSON
sql
SELECTjson_valid('{"x":35}');
→1
JSON5 unquoted key is not valid RFC-8259; returns 0 with default flags=1
sql
SELECTjson_valid('{x:35}');
→0
Valid JSON5 — use flags=2 to accept JSON5 extensions
sql
SELECTjson_valid('{x:35}', 2);
→1
flags=6 (JSON5+JSONB) is the recommended general-purpose check
⚠Anti-Pattern— Using json_valid as a NULL-safe gate without handling NULL input
json_valid(NULL) returns NULL, not 0 or 1. A WHERE clause like WHERE json_valid(col) = 1 silently excludes rows where col IS NULL — the NULL result from json_valid is not equal to 1. This makes json_valid an unreliable gate if the column can be null, causing silent data exclusion.
✓ Instead: Use WHERE json_valid(col) = 1 AND col IS NOT NULL, or COALESCE(json_valid(col), 0) = 1, to treat NULL input as invalid JSON explicitly.
json_valid(x, 6) accepts RFC-8259 JSON, JSON5 extensions, and JSONB blobs — everything that SQLite's JSON functions can process. This is the recommended value when you want to know if a value is a valid input to other JSON functions. Use the default (flags=1) only when you need strict RFC-8259 compliance.
→3 — number of rows with invalid JSON that need fixing
◆SQLite Note
Similar to PostgreSQL's approach of catching exceptions on JSON cast, but SQLite provides a non-throwing function. PostgreSQL equivalent: try CAST(x AS jsonb) and catch the error. Some PostgreSQL setups use a custom is_valid_json() function for this purpose. SQLite json_valid() is cleaner. The flags parameter (added for JSON5/JSONB) has no PostgreSQL equivalent.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
Aggregate function that collects all values in a group into a JSON array. Respects ORDER BY and DISTINCT within the aggregate (SQLite 3.44.0+ for ORDER BY). Each value is encoded as its JSON equivalent: numbers as numbers, strings as quoted strings, NULL as null, and JSON function outputs as embedded JSON.
Signatures
json_group_array(value)
json_group_array(value ORDER BY ...)
json_group_array(DISTINCT value)
Parameters
Parameter
Type
Description
value
ANY
The value to aggregate. Works like json_array() per element: SQL values become JSON scalars, JSON function results embed as JSON.
Examples
Collect employee names per department into a JSON array
⚠Anti-Pattern— Omitting ORDER BY inside json_group_array and relying on row order
Without an explicit ORDER BY inside the aggregate, the order of elements in the resulting JSON array is non-deterministic. SQLite may return rows in any order depending on the query plan, storage layout, or index chosen. Code that relies on the first element, last element, or positional indexing into the array will behave inconsistently.
✓ Instead: Always specify an ORDER BY clause inside the aggregate: json_group_array(name ORDER BY created_at) to guarantee deterministic element order.
Unlike scalar JSON functions, the aggregate functions json_group_array() and json_group_object() process their input as text internally. This means passing JSON function results in their text form (json() or plain json_object()) is more efficient than passing JSONB blobs (jsonb()). The jsonb_ aggregate variants return JSONB output but still process inputs more efficiently as text.
→'[{"id":1,"total":49.99},{"id":2,"total":129.0}]' — all orders per user as JSON array
◆SQLite Note
Equivalent to PostgreSQL's json_agg(expression) or jsonb_agg(expression). PostgreSQL's json_agg() preserves SQL NULL as JSON null in the array. SQLite json_group_array() also includes NULLs. PostgreSQL supports json_agg(x ORDER BY y) and json_agg(x) FILTER (WHERE cond) for ordered/filtered aggregation.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
⚠Anti-Pattern— Passing JSONB values as per-row inputs expecting faster aggregation
Unlike scalar jsonb_ functions, jsonb_group_array() processes its per-row inputs internally as text. Passing jsonb(x) as the input value forces an extra BLOB-to-text conversion per row before aggregation. The JSONB benefit of jsonb_group_array() is only in the output blob, not in per-row input handling.
✓ Instead: Pass text JSON (via json_object(), json(), or bare columns) as inputs to jsonb_group_array(); only the output is JSONB.
Unlike scalar jsonb_ functions, aggregate functions do their internal work in text format. So json_group_array(json(x)) is more efficient than json_group_array(jsonb(x)). Use jsonb_group_array() when you need BLOB output, but keep the per-row inputs as text JSON rather than JSONB.
Equivalent to PostgreSQL's jsonb_agg(expression) which aggregates to JSONB. PostgreSQL jsonb_agg() accepts any SQL value and produces a proper JSONB array. SQLite jsonb_group_array() is the JSONB-output variant added in 3.45.0.
Aggregate function that constructs a JSON object from all NAME/VALUE pairs in a group. Each row contributes one key-value pair. If the same name appears multiple times, later values overwrite earlier ones (order is not guaranteed unless ORDER BY is used). Returns an empty object '{}' for empty groups.
Signature
json_group_object(name, value)
Parameters
Parameter
Type
Description
name
TEXT
The key name for each row's contribution to the JSON object.
⚠Anti-Pattern— Using json_group_object with a non-unique key column
When the name column contains duplicate values within a group, json_group_object() silently keeps only the last value for each duplicated key. No error, no warning, and no indication that rows were discarded. In a key-value settings table with stale duplicates, this causes silent data loss depending on row order.
✓ Instead: Ensure the key column is unique within each group, or use a subquery with MAX/MIN to deduplicate before aggregating with json_group_object().
A common pattern in SQLite is storing settings or metadata in a (key, value) table. json_group_object(key, value) turns an entire partition of that table into a single JSON object in one pass — far simpler than multiple self-joins or CASE expressions.
→'{"debug":false,"max_retries":3,"timeout":30}' — key-value pairs collapsed to JSON
◆SQLite Note
Equivalent to PostgreSQL's json_object_agg(name, value) or jsonb_object_agg(name, value). PostgreSQL's version is stricter: duplicate keys cause an error in some versions, or the last value wins. SQLite json_group_object() silently uses the last value for duplicate keys. PostgreSQL also supports FILTER (WHERE ...) on aggregate functions for conditional aggregation.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
⚠Anti-Pattern— Expecting jsonb_group_object to error on duplicate keys
Like json_group_object(), jsonb_group_object() silently overwrites earlier values when the same key appears multiple times in the group. No error is raised, so duplicate-key bugs in a source table produce silently truncated JSONB objects that appear valid but are missing rows.
✓ Instead: Deduplicate the key column before the aggregate, or add a check upstream to detect and reject duplicate keys before they reach the aggregate.
When building lookup maps from relational tables (e.g., id→name mappings), jsonb_group_object() lets you store the map as JSONB in one INSERT...SELECT. Subsequent lookups using json_extract() on the JSONB blob are significantly faster than repeated JOIN queries.
→JSONB binary of all user settings as a single object
◆SQLite Note
Equivalent to PostgreSQL's jsonb_object_agg(name, value). Both return a key-value JSONB map. PostgreSQL's version errors on NULL keys; SQLite's json_group_object silently skips rows where name is NULL.
Table-valued function that generates one row per immediate child of the top-level JSON array or object. With a path argument, starts at the specified nested element instead of the root. Columns: key, value, type, atom, id, parent (always NULL for json_each), fullkey, path. The 'value' column returns text JSON for arrays/objects and native SQL types for primitives.
Signatures
json_each(json)
json_each(json, path)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
A well-formed JSON string or JSONB blob to iterate over.
path
TEXT
Optional JSONPath to start iteration from a nested element instead of the root.
Examples
Iterate over object keys — key is field name, value is native SQL type for scalars
⚠Anti-Pattern— Using json_each without joining it back to the parent table
json_each() is a table-valued function and must be properly correlated to its source table. Writing FROM json_each('[1,2,3]') alongside another table without a JOIN condition produces a cartesian product — every row from the table is paired with every element of the array silently, with no error.
✓ Instead: Always join json_each to its parent table by referencing the column: FROM posts p, json_each(p.tags) t WHERE t.value = 'sql'.
When joining json_each() with a table, reference the virtual table columns using the function name as alias: FROM users u, json_each(u.tags) t WHERE t.value = 'admin'. Without the alias, SQLite may be ambiguous about whether 'value' refers to the json_each virtual column or a user table column. The columns key, value, type, atom, fullkey, and path are all available.
→key:0 value:'express', key:1 value:'fragile' — array elements as rows
◆SQLite Note
Equivalent to PostgreSQL's json_each(json) or jsonb_each(jsonb) table functions. PostgreSQL returns (key text, value json) pairs. SQLite returns more columns including type, atom, id, parent, fullkey, path. PostgreSQL also has json_each_text() which returns text values. SQLite's ->> operator achieves similar scalar extraction. jsonb_each() variant (SQLite 3.51.0+) returns JSONB for object/array values instead of text.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
Works identically to json_each() with the same column schema, except the 'value' column returns JSONB blobs instead of text JSON when the type is 'array' or 'object'. For primitive types (integer, real, text, null, true, false), it returns the same native SQL values as json_each().
Signatures
jsonb_each(json)
jsonb_each(json, path)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
A well-formed JSON string or JSONB blob to iterate over.
path
TEXT
Optional JSONPath to start iteration from a nested element.
Examples
Wrap value with json() when type is array/object to get readable text
⚠Anti-Pattern— Using jsonb_each without joining it back to the parent table
jsonb_each() shares the same table-valued function join requirement as json_each(). Using it in a FROM clause without referencing the parent table column creates a cartesian product — every row of the parent table is multiplied by every element of the parsed array, with no query error to indicate the mistake.
✓ Instead: Always reference the parent table column inside jsonb_each(): FROM orders o, jsonb_each(o.items) j so that each call is scoped to the current row.
If your json_each() loop processes nested objects or arrays (type='object' or type='array') and you want to modify them with jsonb_set(), jsonb_insert(), etc., use jsonb_each() instead. The value column already comes back as JSONB, skipping a text parse step for each sub-document.
→All top-level key-value pairs from the JSONB config as rows
◆SQLite Note
PostgreSQL's jsonb_each(jsonb) similarly returns (key text, value jsonb) pairs where value is JSONB. SQLite jsonb_each() adds extra columns (type, atom, id, parent, fullkey, path) not present in PostgreSQL's version. Available since SQLite 3.51.0 — use json_each() for compatibility with older SQLite versions.
Table-valued function that recursively walks the entire JSON structure, returning one row for every element at every level of nesting. Unlike json_each() which only visits immediate children, json_tree() visits all nodes in the tree. The same columns are returned: key, value, type, atom, id, parent, fullkey, path. The 'parent' column is populated (unlike in json_each where it is always NULL).
Signatures
json_tree(json)
json_tree(json, path)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
A well-formed JSON string or JSONB blob to walk recursively.
path
TEXT
Optional JSONPath specifying a subtree root to start from. Only that subtree is walked.
Examples
Walk all leaves — filter WHERE atom IS NOT NULL to skip containers
⚠Anti-Pattern— Using json_tree when only top-level iteration is needed
json_tree() walks every node at every nesting depth recursively. For a deeply nested document it can generate dozens or hundreds of rows per document even when the query only needs immediate children. Using json_tree() where json_each() suffices significantly increases the number of rows processed and slows the query.
✓ Instead: Use json_each() for top-level iteration; only use json_tree() when you genuinely need to search or process all nested levels.
json_tree() returns every node including containers (objects and arrays). For most search and decomposition tasks, you only want leaf (primitive) values. The cleanest way to filter: WHERE atom IS NOT NULL — because 'atom' is populated for primitives and NULL for containers. Alternatively: WHERE type NOT IN ('object', 'array').
→All string nodes anywhere in nested JSON that contain 'error'
◆SQLite Note
No direct equivalent in PostgreSQL. PostgreSQL's jsonb_each() and json_each() are non-recursive (like SQLite's json_each). PostgreSQL has jsonb_path_query() for JSONPath queries which can traverse nested structures. For full recursive tree walking in PostgreSQL, you'd use jsonb_populate_recordset() or recursive CTEs. SQLite json_tree() is uniquely powerful for deep JSON searches.
⚙ Availability: SQLite 3.9.0+ (JSON1 extension; built-in since 3.38.0)
Works identically to json_tree() — recursively walks the entire JSON structure depth-first — but the 'value' column returns JSONB blobs instead of text JSON when the type is 'array' or 'object'. For primitive types, the behavior is identical to json_tree().
Signatures
jsonb_tree(json)
jsonb_tree(json, path)
Parameters
Parameter
Type
Description
json
TEXT or BLOB
A well-formed JSON string or JSONB blob to walk recursively.
path
TEXT
Optional JSONPath to start recursive walk from a specific subtree.
Examples
Walk all leaves; value is JSONB for containers but we filter them out here
⚠Anti-Pattern— Using jsonb_tree for top-level iteration instead of jsonb_each
jsonb_tree() recursively visits every node in the JSON tree at all depths. For large or deeply nested documents it produces far more rows than jsonb_each(), which only visits immediate children. The extra rows increase memory usage and slow the query without providing useful data if only the top level is needed.
✓ Instead: Use jsonb_each() when only the immediate children of the root (or a specified path) need to be iterated; use jsonb_tree() only for genuinely deep recursive searches.
If you use json_tree() to locate sub-documents (type='object' or type='array') and then pass the 'value' column to jsonb_set() or other jsonb_ functions, use jsonb_tree() instead — the value column comes back as JSONB, eliminating a text parse step per sub-document.
→All paths in the JSONB document where the value is an array
◆SQLite Note
No PostgreSQL equivalent for recursive JSON tree walking by this name. PostgreSQL's jsonb_path_query(jsonb, jsonpath) can traverse nested paths with JSONPath expressions. SQLite jsonb_tree() is the JSONB-value variant of json_tree(). Both json_tree() and jsonb_tree() are unique to SQLite's JSON function set. Added in 3.51.0.