Skip to main content
This guide uses Excel for the web, so you can follow it on macOS without installing the Microsoft Office desktop apps.
In this guide, we’ll build an Unsiloed Table Extractor task pane for Excel. You choose an image or PDF, enter an Unsiloed API key, and click Extract table. The add-in writes the first detected table into a new worksheet. The finished add-in turns an invoice table into editable Excel cells:
The Unsiloed Excel add-in receiving a PDF invoice, processing it, and writing its line-item table to a new worksheet
The project has four parts: a local HTTPS server, an HTML task pane, its JavaScript behavior, and the XML manifest that connects the page to Excel.

What You Need

Before you start, gather: The account requirement matters because an organization administrator can disable custom add-in uploads. This guide uses a personal Microsoft account and Chrome on macOS.

What We’ll Build

The integration follows four operations:
  1. Office.js confirms that the page is running inside Excel.
  2. The task pane uploads the selected document to Unsiloed and polls the parse job.
  3. JavaScript converts the returned table HTML into rows and columns.
  4. Office.js writes the resulting array to a new worksheet in one operation.
Build each part in the guide below, or copy the complete project first.
If you want to run the integration without following each explanation, run:
Then create the following four files. You can also give this section to a coding agent and ask it to create the project.vite.config.mjs
vite.config.mjs
index.html
index.html
taskpane.js
taskpane.js
manifest.xml
manifest.xml
Continue at Step 5 to run and load the completed add-in.

Step 1: Create the Project

Open Terminal and run:
Vite serves the task-pane page while we develop it. The certificate helper creates a local certificate that Office trusts, because Office add-ins must use HTTPS even during local development.

Step 2: Serve the Add-in over HTTPS

Create vite.config.mjs in the unsiloed-excel-addin directory:
vite.config.mjs
The command === "serve" check limits certificate setup to the development server. A production build doesn’t need the local certificate. strictPort keeps the URL predictable: if port 3000 is busy, Vite reports the conflict instead of choosing a different URL that no longer matches the manifest.

Step 3: Build the Task Pane

The task pane separates presentation from behavior. We’ll first assemble index.html, then build taskpane.js in the same order that data moves through the integration.

3.1 Create the HTML Page

Create index.html beside vite.config.mjs:
index.html
The first script loads Office.js from Microsoft’s content delivery network. It supplies the Office and Excel objects used later. The final script loads our code as a JavaScript module after the form exists in the page. The API key uses a password input, but the browser still holds its value in memory. This recipe doesn’t put the key in the workbook or browser storage.

3.2 Add Task-Pane Styles

Inside the <head> element of index.html, add this <style> block after the Office.js script:
index.html
An Office task pane is a narrow browser window. The fixed content width and full-width controls keep the form usable when the reader resizes the pane. The error and success classes let the JavaScript report progress without using browser alerts.

3.3 Connect the Interface to Excel

Create taskpane.js beside index.html and add:
taskpane.js
The constants keep stable references to the controls. The two booleans track whether Office.js has connected to Excel and whether an extraction is already running. Below those declarations in taskpane.js, add the Office readiness and input listeners:
taskpane.js
A task pane is a web page, but the Excel APIs aren’t ready when the browser first parses it. Office.onReady runs after Office connects the page to its host. We enable extraction only when that host is Excel and both form fields have values.

3.4 Coordinate the Extraction

Continue taskpane.js with the form’s submit handler:
taskpane.js
This function contains orchestration rather than API details. Reading the try block from top to bottom shows the application flow: parse the file, convert a table to rows, and write those rows to Excel. Each operation lives in a focused helper function below. The isBusy guard prevents a second submission while the first job is running. The finally block clears it after either success or failure, so the reader can retry without reloading the add-in.

3.5 Send and Poll the Parse Job

Add the function that uploads the file:
taskpane.js
The FormData object creates the multipart/form-data request expected by POST /parse. Unsiloed returns a job ID rather than the completed document, because parsing can take longer than a single HTTP request should remain open. The final line hands that ID to a separate polling function. Below parseDocument, add the bounded polling loop:
taskpane.js
The loop checks GET /parse/{job_id} every 2.5 seconds. It returns the job as soon as Unsiloed reports Succeeded, surfaces terminal failures immediately, and passes one abort signal to both the delays and network requests. That signal ends the entire polling phase after five minutes, including a stalled request. Continue taskpane.js with the authenticated request and abortable delay helpers:
taskpane.js
Every Unsiloed request needs the api-key header. Centralizing that header in apiFetch avoids repeating authentication code in the upload and polling functions. The helper also turns unsuccessful responses into JavaScript errors, which the submit handler already knows how to display. The delay helper listens to the same abort signal as fetch, so the deadline covers both waiting and network activity.

3.6 Convert Table HTML into a Grid

Add the table conversion functions:
taskpane.js
The parse response organizes content as chunks containing segments. We flatten those segments, select the first Table, and use the browser’s DOMParser to read its HTML rows and cells. The pending map reserves positions covered by rowspan, while the nested loops expand colspan into empty placeholders. Replacing <br> elements with newlines preserves multiline descriptions in a single Excel cell. The final mapping pads every row to the same width. The safeCellText function prefixes values beginning with =, +, -, or @ with an apostrophe. Excel then treats untrusted document content as text instead of a formula.

3.7 Write the Grid and Finish the Interface

Continue taskpane.js with:
taskpane.js
Office.js uses a queued object model. sheets.load("items/name") requests the existing worksheet names, and the first context.sync() retrieves them. We use those names to avoid overwriting a previous extraction. Setting every destination cell’s number format to @ makes Excel preserve values such as 00124, $86.00, and 02 July 2026 as extracted text. Assigning rows to range.values then queues the complete table as a single write. The second context.sync() sends the write, formatting, activation, and selection operations to Excel together. Finish taskpane.js with the interface helpers:
taskpane.js
The button stays disabled when Excel isn’t ready, either input is missing, or an extraction is already running. At this point, taskpane.js should match the complete version at the top of the guide.

Step 4: Describe the Add-in to Excel

Excel doesn’t discover a local web page by itself. The manifest identifies the add-in, declares where it can run, points Excel to the task pane, and requests workbook access.

4.1 Add the Identity and Display Details

Create manifest.xml beside the other project files and add:
manifest.xml
The first line declares an XML file encoded as UTF-8. The two namespace URLs identify Microsoft’s Office manifest vocabulary and the XML Schema Instance vocabulary; Excel uses the latter to interpret xsi:type="TaskPaneApp". The URLs are identifiers, not pages the add-in downloads at runtime. The remaining elements describe the add-in:
  • Id is a stable UUID that distinguishes this add-in from others. Keep this value for the recipe, or generate a new UUID with uuidgen when adapting it.
  • Version tracks releases of the add-in rather than the Office.js version.
  • ProviderName and DefaultLocale identify the publisher and fallback language.
  • DisplayName and Description appear in Excel’s add-in interface.
  • IconUrl and SupportUrl provide the metadata expected by Microsoft’s distribution validator.
The root element remains open because we’ll add Excel-specific settings next. IconUrl and SupportUrl are formally optional for local sideloading, but Microsoft’s distribution validator expects them. They do not affect the localhost connection.

4.2 Grant Excel Access and Load the Page

Append the rest of the manifest:
manifest.xml
The Workbook host restricts this add-in to Excel. SourceLocation must match the HTTPS host and port in vite.config.mjs; it tells Excel which page to place in the task pane. ReadWriteDocument allows Office.js to create the worksheet and write the extracted values. The manifest contains no API credentials. Excel uses it only to register the add-in and decide what workbook access to grant.

Step 5: Start the Add-in

From unsiloed-excel-addin, run:
The first run may ask for your macOS password to trust the localhost certificate. Keep this Terminal window open. Open this page in Chrome and confirm that it doesn’t show a certificate warning:
The page says Open this page as an Excel add-in when viewed directly. That is expected because Office.js isn’t connected to a workbook yet. When Excel loads the localhost task pane, Chrome may ask whether Excel can connect to devices on your local network. Click Allow. Chrome treats a public site loading a loopback URL as local network access.

Step 6: Load the Add-in in Excel

Open Excel for the web, sign in, and create a blank workbook. Microsoft uses different labels for the custom add-in menu across account types. If Manage My Add-ins isn’t present, look for More Settings or Advanced. The Microsoft sideloading guide documents the current routes. If none of these options appears, your organization may have disabled custom add-ins; use a personal account or ask your administrator to enable sideloading.
1

Open the Add-ins menu

On the Home tab, click Add-ins (1).
A blank Excel workbook with a red callout around the Add-ins button on the Home ribbon
In the panel that opens, click More Add-ins (2).
A blank Excel workbook with the Add-ins panel open and a red callout around More Add-ins
2

Upload the manifest

Open Manage My Add-ins and choose Upload My Add-in (3).
The Office Add-ins dialog over a blank workbook with a red callout around Upload My Add-in
Click Browse (4), select unsiloed-excel-addin/manifest.xml, and then click Upload (5). The Upload button becomes available after you select the file.
The Upload Add-in dialog over a blank workbook with red callouts around the Browse and Upload buttons
3

Check the task pane

The Unsiloed Table Extractor pane should open on the right side of the workbook (6).
A blank Excel workbook with a red callout surrounding the open Unsiloed Table Extractor task pane
Excel for the web stores this sideloaded manifest in the current browser profile. Upload it again if you clear the browser data or switch browsers.

Step 7: Extract a Table

Use the task pane to process the sample document:
  1. Enter your Unsiloed API key.
  2. Click Choose File and select the downloaded sample-invoice.pdf file.
  3. Click Extract table.
The numbered controls in the task pane follow the same order:
The Unsiloed task pane with the sample invoice selected and red callouts around the API key field, file chooser, and Extract table button
Unsiloed parses the document, and Excel creates an Extracted worksheet (4). The sample produces 10 rows and four columns. The header row should contain DESCRIPTION, QTY, UNIT PRICE, and AMOUNT; the final row should contain Total due and $11,557.22. Running the add-in again creates Extracted 2, preserving the earlier result.
Excel showing the extracted invoice line-item table with a red callout around the new Extracted worksheet tab
The code formats the destination range as text before assigning values, which preserves currency symbols, leading zeros, dates, percentages, and table labels. Use schema-based extraction when you need typed fields in fixed columns.

Troubleshoot the Add-in

Confirm that npx vite is still running and that https://localhost:3000/index.html opens in the same browser without a certificate warning. In Chrome’s site settings for excel.cloud.microsoft, set Local network access to Allow, reload the workbook, and upload manifest.xml again.
Open Home → Add-ins and select Unsiloed Table Extractor. If it isn’t listed, use Upload My Add-in to load the manifest again.
Confirm that the table has visible rows or aligned columns, then try a larger or sharper image. The recipe stops instead of writing ordinary text segments when the parse result contains no Table segment.
Re-enter the Unsiloed API key. The add-in doesn’t save the key between task pane sessions.

Limitations and Next Steps

This example has a few limitations:
  • It writes the first Table segment. For documents with several tables, add a preview or selector before calling writeRows.
  • It writes every cell as text. Use typed schema extraction when downstream formulas need numbers or dates.
  • It processes one local file at a time and stops polling after five minutes.
  • It keeps the API key in task-pane memory. That works for a personal local tool, but a distributed add-in should use per-user authentication or a backend that keeps shared credentials out of browser code.
The parsing and element-type references below are the best places to extend the example without changing its Office.js foundation.

Parsing

Review the asynchronous parse workflow and response structure.

Element Types

See the segment types returned by parsing, including Table.