# Real Charts in Figma From Your Real Data
_August 17, 2026 · 12 min read · by Systergon_
> The dashboard looks superb in the mockup — six categories, round numbers, one-word labels. Then it meets the real data: forty-one categories, one called “Enterprise (EMEA, excl. UK)”, a value of −3, and an outlier eleven times the median. Connect a Google Sheet, a REST API, an Airtable base or a Supabase table to a chart in Figma, and design against the numbers you will actually ship. Covers all eight sources and which four are genuinely live, the column-detection rules that decide your mapping, where your API key is stored and who can read it, why refresh is manual, and the chart types that refuse a linked source.
Tags: Figma, Charts, Data, Google Sheets, Plugin
URL: https://systergon.com/blog/figma-charts-live-data-from-google-sheets-and-apis
---
[Install Figma Charts — it’s free to try →](https://www.figma.com/community/plugin/1345221583582136577/figma-charts) [Read the help centre →](https://systergon.com/figma-charts/help)

The dashboard looks superb in the mockup. Six categories, tidy round numbers, labels that happen to be one word each, a Y axis that tops out at a satisfying 100. Then it meets the actual data: forty-one categories, one of them called “Enterprise (EMEA, excl. UK)”, a value of −3, and an outlier eleven times the median that flattens everything else into a grey smear along the bottom.

Nothing was wrong with the design. It was just never tested against the thing it exists to display. This is how to build charts in Figma from your real numbers instead — a live Google Sheet, a REST API, an Airtable base or a Supabase table — and how the connection behaves afterwards, including the parts that will surprise you.

![The Figma Charts data source manager listing three connected sources: Q3 Revenue from Google Sheets synced 30 minutes ago, headcount.csv synced a day ago, and an Analytics REST API synced five minutes ago](https://systergon.com/figma-charts-help/data-source-manager-light.webp)

## First — get the plugin

Everything below happens inside **Figma Charts**, a Figma plugin. It runs the real JavaScript charting libraries — Google Charts, Highcharts, D3-based Nivo, ApexCharts and Apache ECharts — inside the plugin window, so the chart on your canvas is the library’s own output rather than a drawing of one.

[Install Figma Charts from the Figma Community →](https://www.figma.com/community/plugin/1345221583582136577/figma-charts)

Once it is installed there are two ways to open it, and the first is faster:

- **Quick actions** — press **⌘/** (**Ctrl /** on Windows), type _Figma Charts_, hit Enter.
- **Right-click the canvas** → **Plugins** → **Figma Charts**.

Open any chart from the gallery and you land in the editor, with a **Data** tab beside Configuration. That tab is where all of this lives.

## What placeholder numbers hide from you

Sample data is not neutral. It is quietly optimistic, and it hides the four things that actually break a chart:

- **Label collision.** Six short labels fit. Forty-one long ones do not — so the library starts dropping every other tick, or rotating them 45°, and your careful baseline alignment goes with it.
- **Outliers.** One value eleven times the median rescales the axis and squashes the rest into noise. You cannot design around an outlier you have never seen.
- **Negative values.** A bar chart with a −3 in it grows an axis line through the middle of the plot area. If your design assumed bars grow up from the bottom, it now has a hole in it.
- **Cardinality.** A palette of six colours is a legend of six. It is not a legend of forty-one, and no amount of restyling will make it one.

Because Figma Charts runs the real library, all four of those surface at design time, in front of you, rather than in the built product. That is only worth anything if the numbers going in are the real ones.

## Eight ways in — four of them live

The source picker offers eight, and it is worth being clear about what each actually is, because the distinction is not signposted in the UI:

- **Google Sheets** — connect to a spreadsheet. **Live.**
- **REST API** — fetch from an endpoint. **Live.**
- **Airtable** — connect to a base. **Live.**
- **Supabase** — connect to a table. **Live.**
- **CSV** — upload or paste CSV. A snapshot: the text is stored in the file.
- **JSON** — upload or paste JSON. A snapshot.
- **Excel** — upload an `.xlsx`. A snapshot.
- **Manual Entry** — type into the table. Not a connection at all.

The four live ones fetch over the network every time you refresh. The other four re-read content that was stored inside your Figma file when you added them, so refreshing one returns exactly what it returned last time. They still appear in the source manager with a “Connected” badge and a last-sync time, which reads as more alive than it is — a CSV that says _1d ago_ is telling you when you uploaded it, not when it last changed.

If you want a chart that tracks a number as it moves, it has to be one of the first four.

## Connecting a Google Sheet

This is the one most teams want, and it is deliberately the least ceremonious: **no API key, no Google sign-in, no OAuth consent screen**. The plugin has no Google account of its own. It reads the sheet the same way an anonymous visitor would, which means the sharing setting is doing all the work.

In Google Sheets, press **Share** and set access to **Anyone with the link** as **Viewer**. Copy the URL out of your browser’s address bar. Then in the plugin, on the chart’s **Data** tab, press **Connect Source** → **Create New** → **Google Sheets**.

![The Connect Google Sheets wizard on step 1 of 5, Enter URL: a field for the spreadsheet URL with a Paste button, a note that the sheet must be shared as Anyone with the link can view, and a Validate URL button](https://systergon.com/figma-charts-help/sheets-wizard-light.webp)

The wizard runs five steps — **Enter URL**, **Select Sheet**, **Map Columns**, **Transform**, **Recommendations**. Two things about that sequence are worth knowing before you start:

- **Validate URL is a real check, not a format test.** It attempts to read the sheet and reports **Public (no API key needed)** when it succeeds. If it fails complaining the sheet is private, the fix is in Google, not in the plugin.
- **The fifth step disappears if you started from a chart.** Recommendations suggests chart types that suit the data it just read, which is only useful when you have not already chosen one. Open the wizard from an existing chart and it applies the data to that chart instead.

Published sheets work too. If someone used **File → Share → Publish to web**, paste that published link — the plugin recognises the format and reads the CSV directly.

## Mapping columns

Every connector converges on the same step, and it is the one that decides whether the chart is right: which column holds your **labels**, and which hold your **data**.

![The Map Your Columns step: a list of columns — Region and Quarter marked as Text, Revenue marked as Number with the range 38,000 to 62,000 — each with Labels and Data role buttons, above a preview of the first five rows](https://systergon.com/figma-charts-help/column-mapper-light.webp)

The wizard proposes a split and shows you why: each column carries its detected type and, for numeric columns, its actual range — _Number · 38,000 – 62,000_. That range is the most useful thing on the screen. It is your axis maximum, before you have drawn anything.

Two behaviours that save trouble:

- **Text-only columns are left out of the series automatically.** A sheet with a Notes or Owner column charts cleanly without you pruning it first.
- **A preview of the first five rows sits underneath.** If the mapping is wrong, it is wrong there, visibly, before the chart is built.

The **Transform** step that follows creates calculated fields — an extra series derived from the ones you have. It is optional and it has a **Skip** button. Skip it the first time.

## How to lay the sheet out so it just works

The proposed mapping is not a guess. The connector scores every column by the proportion of its non-empty cells that parse as a number, and decides from that. Knowing the rules means you can lay a sheet out that maps itself correctly first time:

- **Row 1 is the header row, always.** Those cells become your series names, so they end up in the legend. “Revenue (&pound;k)” is a better header than “col_3” for exactly that reason.
- **Your labels column is the first text-dominant one** — the first column where fewer than half the values look numeric. Put it on the left and keep it text.
- **Every other majority-numeric column becomes a series.** Mixed columns and text columns are dropped, which is why a Notes column costs you nothing.
- **If every column is numeric**, the first one is used as labels. A sheet of years across the top and figures underneath still charts, with the years as labels.
- **Currency symbols, thousands separators and percent signs are stripped before parsing**, so `$1,200` and `45%` are read as 1200 and 45. You do not have to clean formatting out of a finance export.

**The one trap:** inside a column that has already been judged numeric, a value that will not parse becomes **0** — it is not skipped, and it does not leave a gap. So a _N/A_, a _TBC_ or an em dash sitting in a revenue column draws a bar at zero, which looks like a real measurement of nothing rather than missing data. If your source uses a placeholder for “no figure yet”, clear those cells before you connect: an empty cell is genuinely empty, a _N/A_ is a lie the chart will tell confidently.

## APIs, Airtable and Supabase

The other three live connectors follow the same shape: pick the type, hand over a credential, point the wizard at the rows, map the columns.

**REST API.** Enter the endpoint and choose **GET** or **POST** — POST gets a body field for the payload. Authentication is **None**, **API Key** (where you also name the header, such as `X-API-Key`) or **Bearer Token**. Then set the **Data Path**: the route to the array inside the response, written like `data.results`. You rarely have to type it — the wizard lists the arrays it found in the response and previews them, so clicking the right one is usually enough. **Test & Preview** shows the raw response if you need to confirm.

**Airtable.** Paste a **Personal Access Token** from your Airtable account settings, then the **Base ID** (it starts `app`) and the table’s ID or name. A **View ID** is optional and narrows the rows to that view — which is the tidiest way to chart a filtered subset without filtering in the plugin.

**Supabase.** Give the **Project URL** and an **API Key** — the anon key for public tables, the service_role key for private ones — then the **Table Name**. **Limit** and **Order By** are optional and both are worth setting: a chart of the most recent fifty rows is almost always what you meant.

Airtable and Supabase each offer **Test Connection** on their table step. Use it. A credential problem discovered on step two is a typo; the same problem discovered on step four is a debugging session.

## Where your key is stored, and who can read it

This one deserves a plain statement rather than a footnote.

**The credential you enter is saved with the data source inside the Figma file.** It is written to the file’s plugin data, which means it travels with the file: anyone who can open the file can refresh the chart, and that is precisely why it works — a colleague opening your file gets a working, refreshable chart rather than a broken connection and a request for your token.

The trade-off is the obvious one. Treat that key the way you would treat anything else pasted into a shared Figma file:

- Prefer a **read-only or scoped** credential. An Airtable PAT limited to one base, a Supabase anon key on a public table, an API key that can only GET.
- Never use a **service_role** key in a file that is shared beyond the people who already have database access. It bypasses row-level security by design.
- If a file is going to a client or a contractor, unlink the source before you hand it over — unlinking keeps the numbers and drops the connection.

Nothing here is unusual for a design-tool integration, but it is the kind of thing that is much better known than discovered.

## Keeping it up to date

Once a chart is linked, a strip appears at the top of its **Data** tab: the source name, and when it last synced — **Updated 5 min ago**, or **Never synced** if it has not run yet. Alongside it sit **Refresh**, **Change** and an unlink button.

![The Link Data Source dialog listing the data sources saved in the current Figma file, with a search box and a Create New button](https://systergon.com/figma-charts-help/data-link-modal-light.webp)

- **Refresh** fetches again and redraws the chart with whatever comes back.
- **Change** opens the **Link Data Source** dialog, which lists every source saved in this file with a search box — so a second chart on the same sheet is two clicks, not a second trip through the wizard.
- **Unlink** breaks the connection and keeps the numbers exactly as they are. It asks for **Confirm** first, so it is hard to do by accident.

When the plugin notices the source no longer matches what your chart is showing, an amber **Source data has changed** warning appears and asks you to refresh. It does not refresh for you. Your chart is never rewritten behind your back — which matters when the file is a design review and the numbers on screen are the ones being discussed.

**Now the part that surprises people: refresh is manual.** There is no scheduler in the interface. The plugin carries the machinery for timed refreshes internally — intervals of five, fifteen and thirty minutes and an hour — but no screen in the shipped product exposes a control to set one, so in practice a linked chart updates when you press the button and not before.

And refreshing only happens while the plugin is open. A chart sitting on your canvas with the plugin closed is a picture: nothing is fetched, nothing changes, and nothing is quietly wrong. If you want the numbers current for a review, open the plugin and refresh before it starts.

## Getting the new numbers onto the canvas

Refreshing updates the chart _in the editor_. The frame already sitting on your canvas is a separate thing, and it does not move until you say so.

![The editor header while editing a chart already on the canvas: an Update SVG button in place of the usual insert button, with an Auto-update checkbox beside it](https://systergon.com/figma-charts-help/update-buttons-light.webp)

Select the chart on the canvas with the plugin open and it reopens in the editor with the data and settings it was inserted with. The insert buttons become **Update SVG** (or **Update PNG**, if that is how you inserted it), and pressing one replaces the chart _in place_ — same position, size, rotation, name and parent frame. You do not get a second chart to align by hand.

There is also an **Auto-update** checkbox beside it, which live-syncs editor changes to the canvas while you work. It is off by default. Turn it on while you are iterating and off before a review, unless you want the board changing under discussion.

Two details worth carrying:

- **An update rebuilds the chart as a fresh layer.** Renamed sublayers, styles applied to its individual vectors and prototype links do not survive. Do that kind of tidying after your last update, not before.
- **Updates never count against the free insert allowance.** Only brand-new charts do.

If what you want to show is the change itself rather than the latest figure, capture the before and after as snapshots instead — [animated charts as native Figma prototypes](https://systergon.com/blog/figma-charts-animated-charts-native-figma-prototypes) covers that.

## When the source moves under you

Someone will rename a column. Someone will delete the header row, or add a currency symbol to a numeric column, or reorder the tabs. Here is what actually happens.

![A dismissable error toast on the Data tab reporting that the data could not be parsed](https://systergon.com/figma-charts-help/data-error-toast-light.webp)

**If the refresh returns something that is not valid chart data** — no labels array, no series, an empty result — the plugin warns you once and **keeps the last good version**. It does not draw a broken chart, and it does not warn again on every subsequent attempt, so a source that has gone bad does not turn into a stream of toasts.

**If the data is valid but does not suit this chart type**, you get a specific refusal rather than a mangled chart: _this chart type uses a specialised data format and cannot accept linked data sources_.

That refusal is worth planning around, because it is not evenly distributed:

- **Highcharts and ApexCharts never refuse a link.** There is no incompatible-type list for either, so any chart you can build in those two, you can feed from a source.
- **Nivo refuses fifteen types** — tree, treemap, sunburst, circle packing, icicle, sankey, chord, network, parallel, boxplot, bullet, geo, marimekko, swarm plot and voronoi.
- **ECharts refuses eleven** — sankey, chord, tree, treemap, sunburst, graph, box plot, calendar, parallel, theme river and custom.
- **Google Charts refuses ten** — treemap, sankey, word tree, geo, gauge, org chart, table, calendar, timeline and Gantt.

The pattern is consistent: anything whose data is a _structure_ rather than a table — a hierarchy, a set of flows, a grid — is edited in the plugin rather than fed from a sheet. If a live Sankey is the requirement, that is a reason to build it in Highcharts.

## Charts that take more than one source

Some charts need two or three unrelated pieces of data, and those can each be linked separately rather than crammed into one table:

- **Heatmaps** get **X-Axis Source**, **Y-Axis Source** and **Values Source**.
- **Sankey and node-link charts** get **Connect Nodes Source** and **Connect Links Source**.
- **Radar charts** can link their axes and their series independently.

Each part then refreshes on its own, which is genuinely useful when the categories live in one system and the numbers in another — a fixed list of teams from a sheet, live figures from an API.

## Managing sources across a file

Sources are saved into the Figma file, not into your account, so they are shared with everyone who opens it and they survive you closing the plugin.

To see them all, go back to the home screen and press **Data**. That lists every source in the file with its type, its last sync time and a preview of the data it returns, plus buttons to edit or delete. It is also where you add a source before you have a chart — finish the wizard there and it suggests chart types that suit what it just read.

One consequence worth planning for: because the sources belong to the file, duplicating a file duplicates its connections and its stored credentials. A template file with a live source in it will hand that source to everyone who duplicates it.

## What this costs

All seven connectors are PRO. **Manual Entry is the free one** — typing numbers into the table, pasting from a spreadsheet with **⌘V**, and exporting the table as CSV are all ungated. Everything that reads a file or a service, including CSV and JSON import, needs a paid plan.

Figma Charts Pro is **$38 a year**. If you are evaluating, the honest test is to paste your real numbers into the table by hand on the free plan first. Most of the value in this post — discovering that your design breaks on forty-one categories — arrives the moment real numbers hit the chart, whether they arrived over the network or through the clipboard.

## A ten-minute recipe

1. Share the sheet as **Anyone with the link · Viewer**, and copy the URL.
2. Open the chart type you think you want. **Data** → **Connect Source** → **Create New** → **Google Sheets**.
3. Paste, **Validate URL**, pick the tab.
4. On **Map Your Columns**, check the proposed split and read the numeric range — that is your axis.
5. **Skip** the Transform step.
6. Look at the chart with real data in it, and now design it. Fix the label collision, decide what happens to the outlier, choose whether forty-one categories should be a bar chart at all.
7. Insert it, then keep the plugin open and press **Refresh** → **Update SVG** whenever the sheet moves.

Step six is the whole point. Everything before it is plumbing; that step is where the design either survives contact with your data or gets rethought — which is much cheaper to discover in Figma than in a pull request.

[Install Figma Charts — it’s free to try →](https://www.figma.com/community/plugin/1345221583582136577/figma-charts) [Which chart library should you use? →](https://systergon.com/blog/figma-charts-choosing-the-right-chart-library)