Menu

MySQL JSON Data Type: Store and Query JSON

Create a MySQL JSON column, insert valid documents, extract and aggregate values with JSON paths, and index a scalar path with a generated column.

MySQL’s native JSON data type validates documents and stores them in an optimized binary format. Use it when a record needs flexible properties, while keeping frequently filtered or joined fields in regular columns.

Create a table with JSON columns

This example stores page-view and purchase events. properties holds event-specific data, and browser holds browser details:

CREATE TABLE events (
    event_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    event_type VARCHAR(32) NOT NULL,
    visitor_id INT NOT NULL,
    properties JSON NOT NULL,
    browser JSON NOT NULL
);

Insert JSON objects with JSON_OBJECT() so MySQL constructs valid JSON values:

INSERT INTO events (event_type, visitor_id, properties, browser)
VALUES
    ('page_view', 1, JSON_OBJECT('page', '/'),
        JSON_OBJECT('name', 'Safari', 'os', 'macOS')),
    ('page_view', 2, JSON_OBJECT('page', '/contact'),
        JSON_OBJECT('name', 'Firefox', 'os', 'Windows')),
    ('purchase', 3, JSON_OBJECT('amount', 200.00),
        JSON_OBJECT('name', 'Firefox', 'os', 'Windows')),
    ('purchase', 3, JSON_OBJECT('amount', 150.00),
        JSON_OBJECT('name', 'Chrome', 'os', 'Windows'));

The JSON column rejects malformed JSON documents. A JSON column can have an expression default in MySQL 8.0.13 and later; see the JSON data type reference for the version and syntax details.

Extract JSON values

Use -> to extract a JSON value. A JSON string remains quoted in the JSON result:

SELECT event_id, browser->'$.name' AS browser_json
FROM events;

Use ->> when you need the unquoted scalar text, such as a value to display or compare:

SELECT event_id, browser->>'$.name' AS browser_name
FROM events;

The operators are shorthand for JSON_EXTRACT() and JSON_UNQUOTE(JSON_EXTRACT()), respectively. The path $.name selects the top-level name property. For nested objects, use a path such as $.resolution.width.

Filter rows by a JSON property with WHERE:

SELECT event_id, visitor_id
FROM events
WHERE properties->>'$.page' = '/contact';

Aggregate values from JSON

->> returns text, so cast numeric JSON values before arithmetic or aggregation:

SELECT
    visitor_id,
    SUM(CAST(properties->>'$.amount' AS DECIMAL(10,2))) AS revenue
FROM events
WHERE event_type = 'purchase'
GROUP BY visitor_id;

Index a frequently queried JSON path

MySQL does not create a regular index on the entire JSON document. For a scalar path that appears often in filters, add a generated column and index it:

ALTER TABLE events
    ADD COLUMN page_path VARCHAR(255)
        GENERATED ALWAYS AS (
            JSON_UNQUOTE(JSON_EXTRACT(properties, '$.page'))
        ) STORED,
    ADD INDEX idx_events_page_path (page_path);

Then filter on the generated column:

SELECT event_id, visitor_id
FROM events
WHERE page_path = '/contact';

For JSON arrays, InnoDB also supports multi-valued indexes beginning with MySQL 8.0.17. Index only paths and array values your queries need; see the MySQL JSON reference for indexing details.

MySQL’s official JSON documentation covers validation, JSON paths, partial updates, and indexing restrictions.