Retool's Chart component is powered by Plotly.js and supports 15+ chart types. For basic charts, use the Series configuration in the Inspector. For advanced types like candlestick, gauge, or heatmap, switch to Plotly JSON mode and provide a full Plotly data array and layout object. Use {{ chart1.selectedPoints }} to build interactive dashboards where clicking a chart filters other components.
| Fact | Value |
|---|---|
| Tool | Retool |
| Difficulty | Intermediate |
| Time required | 25-35 min |
| Compatibility | Retool Cloud and Self-hosted |
| Last updated | March 2026 |
From Basic Charts to Full Plotly JSON Customization
Retool's Chart component wraps Plotly.js, giving you access to its full visualization library. For most use cases — line charts, bar charts, scatter plots, pie charts — the built-in Series configuration in the Inspector is sufficient. You simply point the Data source at a query, choose X and Y column names, and Retool handles the rest.
But Plotly supports dozens of specialized chart types (candlestick, OHLC, waterfall, funnel, gauge, heatmap, treemap) that aren't exposed in the Series UI. For those, Retool exposes a 'Plotly JSON' toggle that lets you provide a raw Plotly data array and layout object — giving you complete control over every pixel of the chart.
This tutorial covers both approaches, plus the interactive chart.selectedPoints property that lets clicking a chart data point filter tables or trigger queries.
Prerequisites
- A Retool app with at least one query returning time-series or categorical data
- Basic familiarity with the Retool Inspector panel
- Optionally: familiarity with Plotly.js chart types (plotly.com/javascript)
Step-by-step guide
Add a Chart component and connect it to query data
Drag a Chart component onto your canvas from the component panel. In the Inspector, set the Data source to {{ query1.data }}. By default, the chart shows in Series mode — Retool tries to auto-detect X and Y columns. Switch the X axis to a Date or category column and add one or more Y axis series pointing to numeric columns. For example, X: 'date', Y Series 1: 'revenue', Y Series 2: 'expenses'. Choose 'Line' as the chart type from the Type dropdown.
Expected result: A line chart appears displaying your query data with correct X and Y axes.
Customize chart appearance using the Series Inspector options
In the Chart Inspector, expand the Axes section to set axis labels, tick formats, and ranges. Set X axis title to 'Month' and Y axis title to 'USD ($)'. Under each series, set a custom Color (hex code) and Marker size. Enable Show legend to add a chart legend. For numeric Y axes, set Tick prefix to '$' or Tick format to ',.2f' for formatted numbers. These Inspector options cover ~80% of common chart customization needs without writing any JSON.
Expected result: Chart has labeled axes, formatted tick values, and colored series.
Transform query data for multi-series charts
When your query returns data in a flat format (one row per measurement), you may need to pivot it for multi-series charts. Add a Transformer in the Code panel. The transformer receives your query results as the 'data' input and must return a reshaped array. Use JavaScript's reduce() to group by a category column, then produce separate arrays for each series. Reference the transformer output in the Chart component as {{ transformer1.value }}.
1// Transformer: pivotChartData2// Input: data = [{ date: '2026-01', category: 'A', value: 100 }, ...]3// Output: Plotly-compatible series arrays45const grouped = data.reduce((acc, row) => {6 if (!acc[row.category]) acc[row.category] = { x: [], y: [] };7 acc[row.category].x.push(row.date);8 acc[row.category].y.push(row.value);9 return acc;10}, {});1112// Return as Plotly traces array for Plotly JSON mode13return Object.entries(grouped).map(([name, series]) => ({14 name,15 x: series.x,16 y: series.y,17 type: 'scatter',18 mode: 'lines+markers',19}));Expected result: Transformer outputs a Plotly-compatible array with one object per chart series.
Switch to Plotly JSON mode for advanced chart types
In the Chart Inspector, toggle 'Plotly JSON' to ON. The Series UI is replaced by two fields: 'Data (JSON)' and 'Layout (JSON)'. Set Data (JSON) to {{ transformer1.value }} — this should be the Plotly traces array from the previous step. For the Layout (JSON), provide a JSON object controlling the chart's overall appearance. In Plotly JSON mode you have access to every Plotly.js chart type: 'candlestick', 'ohlc', 'waterfall', 'funnel', 'indicator' (gauge), 'heatmap', 'treemap', 'sunburst', and more.
1// Layout JSON object — paste into the Layout (JSON) field2{3 "title": { "text": "Monthly Revenue by Category" },4 "font": { "family": "Inter, sans-serif", "size": 13 },5 "plot_bgcolor": "rgba(0,0,0,0)",6 "paper_bgcolor": "rgba(0,0,0,0)",7 "xaxis": { "title": "Month", "showgrid": false },8 "yaxis": { "title": "Revenue (USD)", "tickprefix": "$", "gridcolor": "#e5e7eb" },9 "legend": { "orientation": "h", "y": -0.2 },10 "margin": { "l": 60, "r": 20, "t": 50, "b": 60 }11}Expected result: Chart switches to Plotly JSON mode and renders using the custom layout configuration.
Build a gauge chart using Plotly indicator type
For KPI dashboards, a gauge chart is often more impactful than a line chart. In Plotly JSON mode, set Data (JSON) to a single-element array with type 'indicator'. Set the value from a query using {{ query1.data[0].score }}. Configure the gauge range and color thresholds in the gauge object. This creates a speedometer-style gauge that updates dynamically with your query data.
1// Gauge chart — Data (JSON) field2[3 {4 "type": "indicator",5 "mode": "gauge+number+delta",6 "value": {{ query1.data[0].nps_score }},7 "delta": { "reference": {{ query1.data[0].prev_nps_score }} },8 "gauge": {9 "axis": { "range": [0, 100] },10 "steps": [11 { "range": [0, 40], "color": "#fee2e2" },12 { "range": [40, 70], "color": "#fef3c7" },13 { "range": [70, 100], "color": "#d1fae5" }14 ],15 "threshold": {16 "line": { "color": "#6366f1", "width": 4 },17 "thickness": 0.75,18 "value": 8019 }20 },21 "title": { "text": "NPS Score" }22 }23]Expected result: A gauge chart appears showing the NPS score with color-coded zones and a delta indicator.
Use chart.selectedPoints for interactive filtering
The Chart component exposes a {{ chart1.selectedPoints }} property containing an array of the data points the user clicked on. Use this to build cross-filtering: when a user clicks a bar in a bar chart, filter a table to show only rows matching that category. Set the Table component's Data source to a transformer that filters based on {{ chart1.selectedPoints[0].x }} — the X value of the selected point. Add an event handler on the Chart component's 'Point click' event to trigger a filtered query.
1// Transformer: filterTableByChartSelection2// Filters the full dataset to the selected chart category34const selectedCategory = chart1.selectedPoints?.[0]?.x;56if (!selectedCategory) {7 return query1.data; // No selection — show all8}910return query1.data.filter(row => row.category === selectedCategory);Expected result: Clicking a bar in the chart filters the table below to show only rows matching the clicked category.
Complete working example
1// Transformer: buildCandlestickData2// Converts OHLCV query data to Plotly candlestick format3// query1.data expected shape: [{ date, open, high, low, close, volume }]45const rows = data; // 'data' is the attached query's result67return [8 {9 type: 'candlestick',10 x: rows.map(r => r.date),11 open: rows.map(r => r.open),12 high: rows.map(r => r.high),13 low: rows.map(r => r.low),14 close: rows.map(r => r.close),15 increasing: { line: { color: '#10b981' } },16 decreasing: { line: { color: '#ef4444' } },17 name: 'OHLC',18 },19 {20 type: 'bar',21 x: rows.map(r => r.date),22 y: rows.map(r => r.volume),23 yaxis: 'y2',24 name: 'Volume',25 marker: { color: '#6366f1', opacity: 0.4 },26 },27];2829// Layout JSON for the chart:30// {31// "yaxis": { "title": "Price" },32// "yaxis2": { "title": "Volume", "overlaying": "y", "side": "right" },33// "xaxis": { "rangeslider": { "visible": false } },34// "plot_bgcolor": "rgba(0,0,0,0)",35// "paper_bgcolor": "rgba(0,0,0,0)"36// }Common mistakes when creating Custom Charts in Retool with Plotly
Why it's a problem: Setting Data (JSON) to {{ query1.data }} directly in Plotly JSON mode and expecting it to work as Plotly traces
How to avoid: query1.data is an array of row objects, not Plotly trace objects. You must transform the data into the Plotly traces format first using a Transformer, then bind {{ transformer1.value }} to Data (JSON).
Why it's a problem: Accessing chart1.selectedPoints[0] without checking if it exists
How to avoid: selectedPoints is empty array [] when nothing is selected. Use optional chaining: {{ chart1.selectedPoints?.[0]?.x ?? 'All' }} to handle the no-selection case.
Why it's a problem: Putting {{ }} expressions inside the Layout JSON string field
How to avoid: Layout JSON is a static JSON object — it cannot contain {{ }} Retool expressions. Dynamic layout values (like max Y axis range from a query) must be handled in the Data traces or in a Transformer that builds the full Plotly configuration.
Why it's a problem: Transformers triggering queries or calling setValue() inside their code
How to avoid: Transformers are read-only compute functions. They cannot trigger queries, set state variables, or produce side effects. Move any side-effect logic to a JS Query.
Best practices
- Use Series mode for simple charts and switch to Plotly JSON mode only when you need advanced chart types or fine-grained control
- Always set plot_bgcolor and paper_bgcolor to rgba(0,0,0,0) in Layout JSON to let the Retool app background show through
- Transform data in a Transformer or query-attached transformer before passing to the chart — keep chart Data (JSON) bindings simple
- Handle empty data gracefully: check if your data array has rows before rendering, otherwise Plotly shows a blank chart without error
- Use chart1.selectedPoints carefully — it persists until the user clicks elsewhere. Add a 'Clear selection' button that calls chart1.resetSelection()
- For dashboards with multiple charts, use one primary data query and transformers for each chart to avoid redundant database calls
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building a Retool dashboard chart using Plotly JSON mode. My query returns data shaped as [{ date: '2026-01', category: 'Electronics', revenue: 50000 }, ...]. I need: (1) a Transformer that pivots this data into Plotly multi-series scatter traces (one trace per category), (2) a Layout JSON object with a formatted Y axis (tick prefix '$'), no background, and a horizontal legend, (3) an explanation of how to use chart1.selectedPoints to filter a table component when a user clicks a data point.
Build a Retool Chart component in Plotly JSON mode for candlestick data. My query 'getOHLC' returns rows with date, open, high, low, close, volume columns. Write the Transformer code to convert this to Plotly candlestick + volume bar traces, and provide the Layout JSON for a dual Y-axis configuration.
Frequently asked questions
What chart types does Retool support beyond line and bar charts?
In Plotly JSON mode, Retool supports the full Plotly.js chart library: scatter, bar, line, area, pie, donut, box, violin, histogram, heatmap, contour, candlestick, OHLC, waterfall, funnel, gauge (indicator), treemap, sunburst, sankey, and more. Check plotly.com/javascript for the complete list and trace configuration options.
Can I make a Retool chart respond to changes in other components?
Yes — bind the Chart's Data source to a transformer that references other components (e.g., {{ dateRangePicker1.value }}, {{ select1.value }}). When those components change, the transformer re-runs and the chart updates automatically. For query-driven filtering, set the query to 'Run when inputs change' and reference the component values in the SQL WHERE clause.
How do I add click event handlers to a Retool chart?
In the Chart Inspector, scroll to the Interaction section and add a handler for the 'Point click' event. The handler runs when a user clicks any data point. Use {{ chart1.selectedPoints }} to access the clicked point's data within the event handler or in downstream queries and transformers.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation