JSON columns
Store, query and update JSON with ->, ->>, JSON_EXTRACT, JSON_TABLE, and index it with generated columns.
Sometimes data doesn't fit neatly into fixed columns: product attributes that differ per category, user preferences, API payloads, event metadata. MySQL's native JSON type stores JSON documents, validates them on insert, keeps them in an efficient binary format, and lets you query, update and index values inside them.
Creating and inserting JSON#
Invalid JSON is rejected. JSON_OBJECT() and JSON_ARRAY() build documents safely from values, with no hand-written quoting.
Reading values: paths, -> and ->>#
A path starts with $ (the whole document). Use .key for object members and [n] for array elements, counting from 0:
col->'$.path'is shorthand forJSON_EXTRACT(col, '$.path')and returns a JSON value, so strings keep their quotes.col->>'$.path'also unquotes the result. It's what you want most of the time.- Missing paths return
NULL.
Filter and sort on JSON values like any expression:
Values from ->> are strings. CAST them when you need numeric sorting or arithmetic, otherwise '16' < '8' as text.
Searching inside documents#
Modifying JSON#
Update parts of a document without rewriting it by hand:
JSON_SETinserts or replaces,JSON_INSERTonly adds missing keys, andJSON_REPLACEonly changes existing ones.JSON_ARRAY_APPEND,JSON_ARRAY_INSERTandJSON_REMOVEedit arrays and remove members.JSON_MERGE_PATCH(a, b)merges two objects, RFC 7396 style, withbwinning on conflicts.
MySQL can apply small JSON_SET/JSON_REPLACE/JSON_REMOVE changes in place, which keeps updates and binary logs small.
JSON_TABLE: JSON to rows#
JSON_TABLE (MySQL 8.0+) turns JSON into a relational table that you can join, filter and aggregate. How many products have each port type?
It's also perfect for importing an API payload stored as one big document:
Rows to JSON#
Going the other way is handy for APIs:
JSON_OBJECTAGG(key, value) builds an object from rows instead. JSON_PRETTY(doc) formats a document for humans.
Indexing JSON#
A JSON column can't be indexed directly. Extract the value you search on into a generated column and index that:
A VIRTUAL column is computed on the fly and takes no table space, while its index stores the values. MySQL 8 can also use the index when you write the original expression (WHERE attrs->>'$.brand' = 'Zeta'), as long as it matches the generated column's definition exactly.
For arrays, MySQL 8.0.17+ supports multi-valued indexes, which speed up MEMBER OF, JSON_CONTAINS and JSON_OVERLAPS:
When to use JSON, and when not to#
✅ Good uses:
- attributes that genuinely vary per row (product specs by category)
- storing external payloads as received (webhooks, API responses)
- user settings, feature flags and other "bag of options" data
❌ Poor uses:
- data you join on, constrain or aggregate constantly. Use real columns, foreign keys and
CHECKs. - avoiding schema design. JSON gives up types,
NOT NULLand referential integrity, and every query must know the document's shape.
A common hybrid: real columns for the core, frequently-queried fields, plus one JSON column for the long tail of optional attributes. You can add CHECK (JSON_SCHEMA_VALID('{...schema...}', attrs)) (8.0.17+) to enforce a document shape.
What's next#
JSON helps with flexible data. For searching large amounts of text by words and relevance, MySQL has full-text search. That's next.
Check your understanding
Quick quiz
1.What is the difference between
doc->'$.name'anddoc->>'$.name'?2.How can you index a value inside a JSON column, such as
$.sku?3.Which function turns a JSON array into rows you can join and filter?
Finished reading?
Mark this lesson complete to track your progress.