Skip to main content
Everything runs inside Google Sheets. After adding one Apps Script file and an API key, you can extract tables with a spreadsheet menu command.

Why Extract Tables in the Sheet

A table might arrive as a screenshot, a photo, or an image embedded in a report. Retyping it is slow and introduces errors. In this guide, we’ll add a menu command that extracts the table from a selected image and writes it to a second sheet. The script uses /parse to identify the document structure, including tables. It doesn’t need a predefined schema for each table layout.

What We’ll Build

A spreadsheet-bound Apps Script that:
  1. Reads the image out of the selected cell.
  2. Sends it to /parse and waits for the job to finish.
  3. Finds the first table in the result and turns its HTML into rows.
  4. Writes those rows to a new results sheet.
Build the script in Step 2, or copy the completed version below.
Paste this into Code.gs, replacing its contents. The script reads the API key from a script property configured in Step 3.
Code.gs

Requirements for Extracting Tables in Google Sheets

Before you start, gather:
  • A Google account and a spreadsheet you can edit.
  • An Unsiloed API key from the dashboard.
  • A PNG, JPEG, or TIFF image containing a table. This guide uses a fund performance page so you can follow along with the same numbers.

Step 1: Put the Image in a Cell

Google Sheets can hold an image two ways. Insert image in cell makes the image a cell value. Insert image over cells leaves it floating above the grid. Pasting with Ctrl+V creates the floating kind. The script can read only an in-cell image. The OverGridImage class used for floating images doesn’t expose the image bytes.
1

Insert the image into the cell

Select the cell you want the image in, then choose Insert → Image → Insert image in cell.
The Google Sheets Insert menu with the Image submenu open and the Insert image in cell option highlighted, above the Insert image over cells option
Paste the image URL, or upload a file, and click Insert.
2

Check that it really is in the cell

The image should sit within the cell borders and resize with the row and column.
A fund performance table displayed inside cell A1 of a Google Sheet, filling the resized cell
If it floats above the grid, open its three-dot menu and choose Put image in cell.

Step 2: Write the Script

We’ll add the script in sections. If you copied the completed version above, use this section as a reference.

2.1 Open the Script Editor

From the spreadsheet, choose Extensions → Apps Script. This creates a script bound to this spreadsheet and opens it in a new tab.
The Google Sheets Extensions menu with the Apps Script item highlighted
The editor opens Code.gs with an empty myFunction. Delete it, then add each code block below.
The Apps Script editor showing Code.gs highlighted in the file list and the save icon highlighted in the toolbar

2.2 Configuration and the Menu

In Code.gs, add the API configuration and spreadsheet menu:
Code.gs
Apps Script runs the reserved onOpen function when the spreadsheet opens, which adds the Unsiloed menu. The unsiloedApiKey helper reads the current key from a script property and reports a clear error if it is missing. Step 3 configures that property.

2.3 Read the Image Out of the Cell

Add imageBlob below onOpen. The getValue() method returns a CellImage, so the function fetches its bytes from a Google-hosted URL:
Code.gs
Keep the setName call. The /parse endpoint chooses a decoder from the file extension, so an unnamed blob is rejected. The MIME-type lookup accepts the three supported image formats and gives the blob a matching filename.
A blob without an extension still returns 200 and a job ID, but the job later fails with Unsupported file type. Check the job status, not only the submission response.

2.4 Send the Image to Parse

Add parseImage below imageBlob. Because /parse is asynchronous, the function submits the image and polls the job endpoint until parsing succeeds or fails:
Code.gs
The UrlFetchApp service builds a multipart request from the blob in payload and uses the blob name as its filename.
Menu-driven scripts can run for six minutes, while spreadsheet custom functions stop after 30 seconds. In our tests, a single-page image usually finishes in about 20 seconds, but processing time varies. A menu command leaves more room for slower jobs.

2.5 Turn the Table Into Rows

Add tableRows below parseImage. It finds the first Table segment and converts its html field into rows:
Code.gs
Two details matter here:
  • The parser validates that the result contains rows and cells, preserves line breaks, and decodes common named and numeric HTML entities.
  • Formula-leading text gets an apostrophe prefix before setValues, preventing extracted document content from executing as a spreadsheet formula.
  • Spanning cells can make some rows shorter. Because setValues rejects uneven rows, the function pads missing cells at the end. It doesn’t reproduce complex rowspan or middle-column colspan layouts.
find takes the first table on the page. See Extend the Google Sheets Integration for handling several.

2.6 Write the Rows to the Sheet

Add extractTable below tableRows. This menu handler validates the selection, runs the helpers, and writes the result:
Code.gs
The valueType check accepts only in-cell images. Each run creates a new sheet, using a timestamp in the name when Extracted already exists, so it never clears an earlier result. A single setValues call writes the full table without making a separate Sheets request for every cell. Save Code.gs.

Step 3: Store Your API Key

Store the API key outside the source code so it isn’t included when you share or copy the script.
1

Add the property

In the Apps Script editor, open Project Settings from the left sidebar and scroll to Script Properties. Click Add script property. If the project already has properties, click Edit script properties first.Name it UNSILOED_API_KEY and paste your key as the value.
The Apps Script Project Settings page with a script property named UNSILOED_API_KEY and its value field highlighted
Click Save script properties.
Spreadsheet editors can view and modify its bound Apps Script project. Use a dedicated API key, restrict edit access to trusted collaborators, and rotate the key if the spreadsheet is shared unexpectedly.

Step 4: Authorize the Script

The first run asks for permission to edit the spreadsheet and call the Unsiloed API.
1

Run it once and grant access

Back in the spreadsheet, reload the page. A new Unsiloed menu appears in the menu bar. Select the cell holding your image and choose Unsiloed → Extract Table From Image.Google Sheets shows Authorization required.
The Authorization required dialog in Google Sheets saying a script attached to this document needs permission to run, with the OK button highlighted
Click OK, then pick your Google account in the window that opens.
2

Get past the unverified app warning

Google warns that it hasn’t verified the app because this is your unpublished script. Click Advanced, then Go to <your project name> (unsafe).Review the two permissions it asks for and click Allow:
  • Spreadsheet access to read the image and write the rows
  • External service access to call the Unsiloed API

Step 5: Extract the Table

With the script authorized, the menu command is the whole workflow. Select the cell holding the image and choose Unsiloed → Extract Table From Image.
The Unsiloed menu open in Google Sheets with the Extract Table From Image item highlighted
In our tests, the script writes the rows in about 20 seconds for a single-page image. Processing time varies with the image and current service load.

Sample Output

For the sample fund performance page, the Extracted sheet preserves the group rows and the em dash for the missing five-year return:
The Extracted sheet holding the fund returns table as rows, with share class labels in column A and one, five, and ten year returns in columns B through D

What to Expect From the Output

The rows preserve the table text rather than normalizing it:
  • Values arrive as text. 7.76% keeps its percent sign, and an em dash remains an em dash. Formula-leading values are escaped before writing. Convert the values in Sheets if you need numbers.
  • Layout rows come through. A grouping row like Class A Shares arrives as a label with empty cells beside it, exactly as it sits on the page.
  • Row grouping can vary. A label and its values might share a row or split across two rows. Don’t write formulas that assume a fixed row offset.
If you need typed values and a fixed set of columns, use /v2/extract with a schema instead. This requires a schema but returns typed values in predictable columns.

Troubleshoot the Script

The selected cell has no in-cell image. Either the wrong cell is selected, or the image is floating over the grid rather than in a cell. See Step 1.
/parse found no table. Try a larger or sharper image, especially if the layout has no ruling lines or consistent columns.
Use a PNG, JPEG, or TIFF image. The script rejects other MIME types before submission and gives supported blobs a matching file extension.
Reload the spreadsheet to run onOpen and rebuild the menu. If it still shows old items, run onOpen once from the Apps Script editor.
The API key is missing or wrong. Check the UNSILOED_API_KEY script property, then run the command again.

Extend the Google Sheets Integration

The script takes the first table it finds. To pull every table out of a multi-table page, collect all the Table segments instead of calling find, and write each one below the last. To process many images, place one image per row, loop over the range, and write each result to its own sheet. Keep enough margin below the six-minute Apps Script limit for slower jobs and retries. For larger batches, submit every job first, then poll them together so they run concurrently.

Parsing

What /parse returns for a document, segment by segment.

Element Types

The full list of segment types, including Table.

Extraction

Pull typed fields against a schema when you need numbers rather than text.

API Reference

The full request and response specs for /parse.