Skip to main content
Charts often contain values that aren’t available as selectable text. A Google Sheets custom function can send a chart image to Unsiloed and return its categories and series as editable cells. In this guide, we’ll create UNSILOED_CHART. The function downloads a chart image, asks Unsiloed for a chart-ready Markdown table, and converts the table to numeric spreadsheet values. The finished formula looks like this:
The function accepts an image URL in A2. The value in B2 triggers another status check when parsing takes longer than one formula execution. An optional third argument returns a numeric x-axis for scatter charts. In the example below, Unsiloed extracts 20 quarters from a dual-axis chart, including revenue in millions of pounds and margin as a percentage. We then rebuild it as an editable combination chart.
Animation showing a dual-axis chart image being extracted into Google Sheets and recreated as an editable combination chart
Custom functions can return values but can’t insert or modify a Google Sheets chart. After the data spills into the sheet, we’ll use Insert → Chart to create an editable chart from it.

What We’ll Build

The custom function performs five operations:
  1. It downloads a public chart image and uploads the bytes to /parse.
  2. It asks the page analyzer to end its response with one Markdown data table.
  3. It stores the returned job ID so later calculations can reuse it.
  4. It checks the job once during each calculation.
  5. It converts the table into validated, chart-ready rows and numeric series.
Google stops a custom function after 30 seconds, and Apps Script can’t interrupt an outstanding URL fetch. If the job is still running, the formula returns a status message. Change the refresh value later to check the saved job again. For slower or business-critical workflows, use the menu-driven cookbook instead.
Paste this into Code.gs, replacing its contents. The script reads the API key from the script property configured in Step 3.
Code.gs

Requirements for the Custom Function

Before you start, gather:
  • A Google account and a spreadsheet you can edit
  • An Unsiloed API key from the Unsiloed dashboard
  • A public PNG, JPEG, or TIFF URL that returns the image bytes
A private Google Drive sharing link doesn’t work because the custom function can’t use an interactive download page. Use a direct public URL or a presigned cloud-storage URL. This guide uses the synthetic D11 dual-axis chart from the Unsiloed ChartParse-Bench dataset, published under CC BY 4.0. The dataset accompanies the chart-parsing benchmark article:

Step 1: Open the Apps Script Editor

The custom function lives in the Apps Script project attached to the spreadsheet. In Google Sheets, choose Extensions → Apps Script.
The Google Sheets Extensions menu marked 1 and Apps Script command marked 2 with red callouts
Google creates a bound script project and opens Code.gs in a new tab. Delete the empty myFunction that Google adds to a new project.

Step 2: Build the Custom Function

We’ll build the same script in twelve focused sections. Keep each addition in Code.gs in the order shown below. If you copied the completed script from the accordion, use this section to understand its request, cache, and table-conversion logic.

2.1 Configure Chart Extraction

At the top of Code.gs, add the API URL, cache limits, extraction prompt, and API key helper:
Code.gs
The prompt asks for one rectangular table with canonical numeric cells. It also keeps multi-axis values in their original units and preserves visible error bars as separate columns. The job version and API-key fingerprint become part of the cache key. We’ll add UNSILOED_API_KEY in Step 3.

2.2 Check the Parse Job

Below unsiloedApiKey, add the function that Sheets calls:
Code.gs
The @customfunction tag adds UNSILOED_CHART to Sheets autocomplete. Each calculation performs one status request. If the job is still running, changing refresh starts another calculation that checks the saved job ID.

2.3 Reuse or Create a Parse Job

Below UNSILOED_CHART, add the helper that reads the cache before taking a lock, then checks it again before submitting an image:
Code.gs
Reading before locking lets an existing job return immediately. The second read prevents two simultaneous calculations from creating ordinary duplicate jobs. A remote job accepted immediately before Apps Script terminates may still lose its ID because the parse API doesn’t expose an idempotency key.

2.4 Validate and Clear Cached Jobs

Below getOrCreateChartJob, add helpers that reject malformed cache entries and remove a failed or missing job without deleting a newer replacement:
Code.gs
The job ID comparison matters when concurrent calculations overlap. It prevents an old failed response from removing a newer job saved under the same key.

2.5 Bound the Job Cache

Below clearCachedChartJob, add cleanup and cache-key helpers:
Code.gs
Cleanup retains at most 50 current jobs and removes expired, malformed, and older entries. Hashing the API key into the property name starts a new cache when the spreadsheet changes Unsiloed accounts without exposing the key.

2.6 Build the Chart Parse Request

Below jobPropertyName, add the payload that tells Unsiloed how to analyze the image and which response fields to return:
Code.gs
The page analyzer sees the complete standalone chart, including its labels and axes. Because the image is one chart, the whole page is the chart, and the prompt controls the shape of the table that comes back. The custom response profile returns the Markdown table the sheet needs and omits fields the formula doesn’t use. The request turns off chart_data because the prompt already produces the table. For charts embedded in multi-page documents, send extract_charts=true instead and read the structured chart_data field on each chart segment. See the parse job response reference for that field.

2.7 Download the Source Image

Below chartParsePayload, add the image downloader:
Code.gs
Downloading in Apps Script ensures Unsiloed receives image bytes and the correct content type. Apps Script doesn’t expose a per-request fetch timeout, so a slow download or API request can still reach the custom function’s runtime limit.

2.8 Send Authenticated API Requests

Below fetchChartImage, add the shared request helper:
Code.gs
The helper adds the API key and surfaces API error messages in the formula cell. It also preserves the HTTP status so the main function can clear a missing job without treating temporary server errors as permanent.

2.9 Select the Chart Data Table

Below unsiloedRequest, add the helper that validates every extracted table:
Code.gs
The function accepts a table only after numeric-series validation. It preserves the first column as text by default, protecting numeric-looking category labels. Pass TRUE as the optional third formula argument when a scatter chart needs a numeric x-axis. In that mode, a missing or nonnumeric x value produces an error.

2.10 Parse Markdown Tables

Below firstChartRows, add the tolerant Markdown table parser:
Code.gs
The parser accepts tables with or without outside pipes, verifies that the header and delimiter widths match, and pads short data rows into a rectangular array. It rejects wider rows because an extra pipe could otherwise shift or hide a value.

2.11 Split Markdown Cells Safely

Below isMarkdownRow, add the row parser and backslash helpers:
Code.gs
Only punctuation consumes a Markdown backslash escape. Ordinary text such as C:\temp keeps its literal backslash, while \| remains a pipe inside its cell.

2.12 Validate Series and Numbers

At the bottom of Code.gs, add the numeric-series and cell-value validators:
Code.gs
Every returned series must contain numeric values. Empty, text-only, and mixed numeric/text columns fail with the column name and offending value instead of disappearing silently. Equal numeric series remain separate, and numeric overflow or unsafe integers produce an error rather than a corrupted spreadsheet value. When Code.gs contains all twelve sections, save the project and confirm that Apps Script reports no syntax error. The finished script should use the chart-v6 cache version and accept imageUrl, refresh, and numericX as the function’s three arguments.
The completed script in Code.gs with the chart-v6 cache version marked 1 and the three-argument UNSILOED_CHART signature marked 2

Step 3: Store Your API Key

Keep the API key out of spreadsheet cells and source code by storing it as a script property.
1

Open the script properties

In the Apps Script editor, open Project Settings from the left sidebar. Scroll to Script Properties, then click Add script property.
2

Add the API key

Set the property name to UNSILOED_API_KEY, paste your API key into its value field, and click Save script properties.
The Apps Script project settings page with the UNSILOED_API_KEY property name and placeholder value outlined in red
Spreadsheet editors can view its bound Apps Script project and script properties. Use a dedicated API key, restrict edit access to trusted collaborators, and rotate the key if the spreadsheet is shared unexpectedly. Treat presigned image URLs as credentials too, and give them short expiration times.

Step 4: Extract the Chart Data

We’ll keep the image URL and refresh value separate from the formula so a status check doesn’t require editing the formula text.
1

Add the image URL and refresh value

Return to the spreadsheet and add these labels and values:The red box shows the input range. The URL appears truncated in the cell, but Sheets retains its complete value.
A Google Sheet with the chart image URL and refresh input range A1 through B2 outlined in red
2

Run the custom function

Select A4 and enter:
For a scatter chart that needs numeric x values, use:
Sheets displays Loading... while the function submits or checks the parse job. Keep the cells below and to the right of A4 empty so the result has room to expand.
3

Refresh an unfinished job

The first calculation may return this status instead of data:
Wait about 20 seconds, then increase B2 from 1 to 2. Sheets recalculates the function and checks the saved job instead of submitting the image again. Repeat after another short wait if the job is still processing.
The refresh value 2 in cell B2 outlined in red
4

Review the extracted table

When the job succeeds, the formula expands into the chart’s categories and series.
The UNSILOED_CHART result containing quarter, revenue, and margin columns outlined in red
The result contains one row for each of the 20 quarters. Unsiloed keeps the revenue (£m) and margin (%) units in the headers while estimating the plotted values from the chart’s pixels. For example, the extracted quarter 7 margin is 16.2, while the synthetic source data is approximately 16.15. Verify values against source data before using them for consequential decisions. The margin values use a 0–100 percentage scale, so 13.9 means 13.9%. Don’t apply Sheets’ percentage format, which would display that value as 1,390%. Each image URL reuses a saved job for up to 24 hours, subject to the 50-entry cache limit. To deliberately submit a fresh job, change UNSILOED_JOB_VERSION, save Code.gs, and change B2. If you instead delete its UNSILOED_JOB_... script property, also change B2 to recalculate the formula.

Step 5: Recreate the Chart in Google Sheets

The extracted cells become the data source for the native chart. Select the extracted category and value columns, including their headers. Then choose Insert → Chart.
The extracted chart data selected, with Insert marked 1 and Chart marked 2 using red callouts
In the Chart editor, choose Clustered Column - Line on Secondary Axis. This uses columns for revenue and a line for margin, matching the source chart’s two scales. Enable Use row 4 as headers and Use column A as labels if Sheets doesn’t detect them automatically.
A native Google Sheets combination chart with revenue columns and a margin line, with the dual-axis chart type outlined in red
The recreated chart is editable and linked to the extracted cells. It preserves the extracted categories, estimated values, series types, units, and two axes. Google Sheets applies its own fonts, spacing, and colors. The source image’s gray highlighted interval is a visual annotation rather than a data series, so add that styling manually if you need it. Treat the result as a faithful data reconstruction, not a pixel-for-pixel copy of the source image.

Troubleshoot the Custom Function

The first argument is empty, a filename, or a non-HTTP value. Put a complete public or presigned image URL in the referenced cell.
The URL returned an HTML download page instead of image bytes. Use a direct PNG, JPEG, or TIFF URL.
The bound script has no API key. Add the property in Step 3. Script properties aren’t copied with a spreadsheet, so add it again in the copied sheet’s script project.
Wait about 20 seconds, then change the refresh value. The next calculation resumes the existing job. Apps Script stops custom functions after 30 seconds, so a slow parse can require more than one check.
Another calculation briefly held the script lock. Change the refresh value after a few seconds. This reuses the existing job when one is available.
Temporary HTTP errors usually resolve on a later refresh. For an account or credit error, check the Unsiloed dashboard before retrying.
Clear the cells below and to the right of the formula. Sheets doesn’t let a spilled array overwrite existing values.
Some spreadsheet locales use semicolons between arguments. Enter =UNSILOED_CHART(A2; B2) instead.
Chart extraction reconstructs values from pixels. Small labels, overlapping marks, unlabeled ticks, and compressed images can reduce fidelity. Use the highest-resolution source available and verify decision-critical values against the original image.

Choose Between a Formula and a Menu Command

Use this custom function when one public chart image should produce one dataset at the formula location. Use the menu-driven table extraction guide when you need to read an image stored inside a cell, create a new worksheet, or process several source images in one run.

Chart Parsing Benchmark

See how Unsiloed evaluates chart accuracy and point density.

Extract an In-Cell Image

Run a menu command that reads an image and writes a new worksheet.

Parsing

Review the parse workflow and the document structures it returns.

Google Sheets Integration

Connect a spreadsheet to Unsiloed and compare workflow patterns.