How Plotly Python Serializes Figures to JSON
Plotly relies on a clean separation between its Python interface and
its client-side JavaScript rendering engine, Plotly.js. In Python,
high-level figure declarations created with Plotly Express or
plotly.graph_objects are translated into a standardized,
JSON-serializable tree structure consisting of data, layout, and
configuration parameters. This article explains how the Python library
processes Python-native objects—such as NumPy arrays, Pandas DataFrames,
and datetime values—into an optimized JSON payload that Plotly.js can
parse and render in the browser.
The Anatomy of a Plotly Figure
In Python, a Plotly figure is an instance of the
plotly.graph_objects.Figure class. Regardless of how
complex the visualization is, every figure can be distilled into a tree
structure containing three primary top-level keys:
data: A list of trace specifications (e.g., scatter, bar, heatmap), which define the visual marks and the data points mapped to them.layout: A dictionary specifying the global properties of the plot, including axis configurations, titles, annotations, shapes, and margins.frames: An optional list of sub-figures used for animations.
Internally, each property in a Figure is validated and
stored in structured dictionary-like objects called
BasePlotlyType.
The Serialization Pipeline
To transfer a figure to the browser, Plotly must convert these
internal graph objects into a valid JSON string. The primary function
responsible for this is plotly.io.to_json(), also
accessible directly via the fig.to_json() method.
The serialization pipeline involves the following stages:
- Dictionary Extraction: The
Figure.to_plotly_json()method recursively traverses the figure tree, stripping away internal Python-specific validation wrappers and metadata to produce standard Python primitives (dict,list,str,int,float,bool). - Type Coercion and Normalization: Real-world data
structures often contain types not supported by standard JSON
specifications, such as NumPy
ndarray, PandasSeries,NaN/Infinityvalues, and datetime objects. Plotly transforms these non-standard types:- NumPy Arrays and Pandas Series: Converted into standard nested Python lists.
- Missing and Non-Finite Values:
NaNandNonevalues are converted to JSONnull, whileInfinityand-Infinityare typically stringified or normalized so as not to break standard JSON parsers. - Dates and Times: Native Python
datetimeobjects and PandasTimestampinstances are serialized into ISO 8601 strings (e.g.,YYYY-MM-DDTHH:MM:SS.sssZ), which Plotly.js natively recognizes and parses on the client side.
Serialization Engines
Plotly utilizes two main approaches to handle JSON conversion efficiently:
orjsonEngine (Default when installed): For high-performance serialization, Plotly supportsorjson, a Rust-based JSON library.orjsonnatively understands many non-standard data types—including NumPy arrays, UUIDs, and datetimes—allowing Plotly to skip Python-level intermediate transformations and serialize directly to binary JSON with minimal CPU overhead and memory allocation.PlotlyJSONEncoder(Fallback): Whenorjsonis unavailable, Plotly falls back on the standard library'sjson.dumps()paired with a customplotly.utils.PlotlyJSONEncoder. This encoder defines customdefault()handlers that evaluate objects during iteration, converting unrecognized types into JSON-compliant representations on the fly.
Client-Side Handoff to Plotly.js
Once serialized, the resulting JSON string represents the exact data schema expected by Plotly.js. When rendering a figure—whether in a Jupyter Notebook, a Dash web application, or an exported standalone HTML file—the serialization output is embedded into the document.
Plotly writes out a <script> tag that passes the
serialized JSON directly to the client-side JavaScript entry point:
Plotly.newPlot('div-id', figureJson.data, figureJson.layout, figureJson.config);On the client side, Plotly.js parses the JSON payload, instantiates its internal SVG or WebGL rendering contexts, and draws the visualization interactively in the DOM.