NOW AVAILABLE: Summer 2025 Release
By Roger Dunham | 2025 Aug 15
6 min
Summary: This article explores how to use the API to programmatically work with data in the Spreadsheet Editor. Learn how to enhance user workflows and add dynamic functionality—unlocking the full power of Apryse’s AI-ready, browser-based spreadsheet editing.
Apryse released the first version of their web-based Spreadsheet Editor in April 2025. While initially view-only, the second release - just three months later – added a wealth of additional functionality. It’s a great piece of software allowing users to view and edit spreadsheets directly within the browser without the need to ever download them to a local machine.
Recently I wrote a blog that described how to create a Spreadsheet Editor in a React based Web App. In this article we will look at how we can programmatically interact with the UI- offering the ability to customize the way that your users work.
I’m starting this article at the point where I left the previous one – with the Spreadsheet Editor running in the browser, with an initial document open, but also with the ability to select a different file using a file picker.
The code at that time was as follows:
import { useRef, useEffect } from 'react'
import './App.css'
import WebViewer from '@pdftron/webviewer'
function App() {
const viewerDiv = useRef<HTMLDivElement>(null)
const isInitialized = useRef<boolean>(false)
useEffect(() => {
//Not needed in production
if (isInitialized.current) return
isInitialized.current = true
WebViewer.Iframe({
path: "lib/webviewer",
initialMode: WebViewer.Modes.SPREADSHEET_EDITOR,
licenseKey:"[Your license key]",
enableFilePicker: true,
initialDoc:"invoice_template.xlsx"
},
viewerDiv.current as HTMLDivElement).then((instance => {
})
);
}, [
])
return (
<>
<div className='webviewer' ref={viewerDiv}>
</div>
</>
)
}
export default App
This code creates a WebViewer with a feature rich WCAG 2.2 compliant UI, that allows you to work with Spreadsheet data directly within the browser.
The essential part of this code is initialMode:WebViewer.Modes.SPREADSHEET_EDITOR which sets the UI to be the Spreadsheet Editor, rather than the DOCX or default PDF (and other files type) viewer and editor. In a future blog I’ll write about how to swap between the types of document that are displayed after WebViewer has been initialized.
Figure 1 - The Apryse Spreadsheet Editor shown running within the Apryse showcase
Now let’s go one step further.
In order to interact with the WebViewer API we need to get access to the WebViewer instance. There are several ways to do this, but the easiest (in my opinion) is to use the instance object that is returned when the WebViewer constructor resolves.
In the code at the start of this article we had access to that object but didn’t actually use it.
The instance object provides a mechanism that lets us perform a wide range of tasks, either on the UI, or within the document itself. When working with the Spreadsheet Document it is essential to allow it to fully load before interacting with it.
You can write code that will apparently let you access the Spreadsheet API but gives an error when you use it. The following example tries to get the value that is displayed in the top-left cell.
//DON’T DO THIS!
viewerDiv.current as HTMLDivElement).then((instance => {
const { documentViewer, SpreadsheetEditor } = instance.Core;
//@ts-ignore
const spreadsheetEditorManager = documentViewer.getSpreadsheetEditorManager();
//@ts-ignore
const spreadsheetEditorDocument = documentViewer.getDocument().getSpreadsheetEditorDocument();
const workbook = spreadsheetEditorDocument.getWorkbook();
const sheet = workbook.getSheetAt(0);
let cell = sheet.getRowAt(0).getCellAt(0);
console.log(cell.getDisplayStringValue());
Note: there are a couple of known issues with Type definitions that I’m working around by using //@ts-ignore. If you find others, then please let us know.
The code all looks good, but when it is run, you are likely to get an error when you try to access the SpreadsheetEditorDocument
Figure 2 - If you try to access the SpreadsheetEditor API before it is ready then you will get errors logged to the console.
A “null object” sounds like a problem! The solution is, however, simple.
Before we try to access the Spreadsheet Editor API we need to wait for the SPREADSHEET_EDITOR_READY event to be fired – the name explains it all!
WebViewer.Iframe(...)
.then(instance => {
const { documentViewer, SpreadsheetEditor } = instance.Core;
const spreadsheetEditorManager = documentViewer.getSpreadsheetEditorManager();
const SpreadsheetEditorEvents = SpreadsheetEditor.SpreadsheetEditorManager.Events;
spreadsheetEditorManager.addEventListener(SpreadsheetEditorEvents.SPREADSHEET_EDITOR_READY, () => {
//Do things such as get cell values
});
});
Now, we can now get the value displayed in the first cell (I haven’t included the code for that here - we’ll look at that in a minute).
Figure 3 - An example of getting data from the spreadsheet - in this case the displayed value from cell A1.
Before we delve into working with the data, let’s first look at how to set the editor mode programmatically.
The Apryse Spreadsheet Editor can be configured to allow the data in the spreadsheet to be editable or for it to be view-only. (Before v. 11.6 it was only available as view-only, so if you have an earlier version you will want to upgrade).
One way to switch between editor modes is using the Mode Flyout on the UI.
Figure 4 - The Mode flyout on the UI - you can use this to switch between View and Edit mode.
An alternative way is via the API.
At its simplest you can use this to make all spreadsheets editable (or all view-only) when they open. But if you need something more sophisticated, then you can leverage the API to make specific spreadsheets editable, but others are not. For example, you could make spreadsheets created this month editable, but ones created earlier are view-only.
Alternatively, you could get user data from your authentication system and create a system whereby some users can edit all files, and others can edit only some. There is flexibility to create whatever system your business rules require.
Regardless of the logic you use, the process of implementing the control is the same – in the following code whether something is editable depends on their filename, but you swap that logic for your own.
spreadsheetEditorManager.addEventListener(SpreadsheetEditorEvents.SPREADSHEET_EDITOR_READY, () => {
const filename = documentViewer.getDocument().getFilename();
const isEditable = filename.startsWith('invoice')
if (isEditable) {
spreadsheetEditorManager.setEditMode(SpreadsheetEditor.SpreadsheetEditorEditMode.EDITING);
}
else {
spreadsheetEditorManager.setEditMode(SpreadsheetEditor.SpreadsheetEditorEditMode.VIEW_ONLY);
}
});
As we noted earlier, we need to wait until the SpreadsheetEditorEvents.SPREADSHEET_EDITOR_READY event fires (which will happen each time that a new document is loaded).
Spreadsheet editability is controlled using spreadsheetEditorManager.setEditMode(), passing in the appropriate enum.
Of course, setting the UI programmatically to restrict editing is moot if the flyout for editing mode is still available – but with the modular UI it is a breeze to remove (or add) UI elements. For now, though, I’ll leave it in place so that it is obvious whether or not editing is allowed.
Figure 5 - The code has allowed a document whose name starts with "invoice" to be editable.
Figure 6 - The code means that a document whose name doesn't start with "invoice" is view-only. Note that even in view-only mode, you can still see what calculations are being performed in a cell.
It is always wise to verify a user’s access permissions when implementing a RESTful API for getting and saving data, and not just rely on the UI to control that, regardless of how easy it is to use the API to restrict editing of spreadsheets to particular users.
Let’s now look at how we can interact with specific cells. We touched on this earlier when we saw the “Gotcha!” of trying to interact with the Spreadsheet before it was ready.
As an example, let’s look at how we can work with formulas in cells, as that demonstrates a range of API calls.
We will use the invoice sample spreadsheet from https://showcase.apryse.com/spreadsheet-editor. As an example, let’s get the contents of the cell H17 which contains a formula.
Figure 7 - The invoice sample document from the Apryse Showcase
When getting a specific cell, we typically specify the location using zero indexed row and column - as such the cell “H17” is found in row 16, column 7.
spreadsheetEditorManager.addEventListener(SpreadsheetEditorEvents.SPREADSHEET_EDITOR_READY, () => {
//@ts-ignore
const spreadsheetEditorDocument = documentViewer.getDocument().getSpreadsheetEditorDocument()
const workbook = spreadsheetEditorDocument.getWorkbook();
const sheet = workbook.getSheetAt(0) as Core.SpreadsheetEditor.Sheet;
const cell = sheet.getRowAt(16).getCellAt(7);
const f = cell.cellFormula;
const v = cell.numericCellValue;
const displayString = cell.getDisplayStringValue();
console.log('Cell formula is: ' + f)
console.log('Cell value is ' + v)
console.log('Cell display value is ' + displayString)
const style = cell.getStyle();
console.log('Cell style is ' + style.getDataFormatString())
});
If we run that code then we can see the formula, calculated value, display value and the formatting applied to the cell.
Figure 8 - The output of running the code snippet.
That’s a lot of information for a single cell!
In particular note how there is a cell value (which is the result of the calculation specified in the formula), and the displayed value, which depends on the cell value and the cell formatting style.
That formatting style is complex – indicating the level of control that you have over the way that the data is formatted.
The Spreadsheet Editor comes with a lot of cell formats built into the UI.
Figure 9 - The prebuilt formats that are available in the Spreadsheet Editor.
You can also specify border styles, fonts, colors and so on – all from the UI.
As you would expect though, with the powerful Apryse Web SDK behind it, it is also possible to specify styling programmatically.
We’ll also use the opportunity to change the cell content.
Figure 10 - The example document. We will be modifying the content of the date in cell H5
As an example, let’s look at the cell H5 which contains the date “2/21/2018”. In fact, that date is hard-coded text. It’s not ideal, but it is the kind of thing that is regularly found in real spreadsheets – sometimes just because people couldn’t work out how to make the date format the way they wanted.
Let’s imagine that we want to change that value to the current date. We will also update the date format to avoid confusion if the sheet is read by someone outside of the US, by showing the month as a word, rather than as a number.
Finally, and just for the fun of it, we will set the cell background color to pale green.
We could do a lot more with the code, but that’s a good example to start with.
If we know that the date will be in cell H5, then we could just edit that cell.
However, if someone updates the spreadsheet by adding (or removing) columns or rows then there is a risk that the cell that we are looking for will no longer be at “H5”.
To make our code mor robust we can search all of the cells in the spreadsheet until we find one that has the text “2/21/2018”.
const rowCount = sheet.rowsCount;
const colCount = sheet.columnsCount;
let found = false;
for (let r = 0; r < rowCount && !found; r++) {
for (let c = 0; c < colCount && !found; c++) {
let cellTest = sheet.getCellAt(r, c);
const scv = cellTest.stringCellValue;
if (scv == "2/21/2018") {
const formatted = getNow();
cellTest.setDateValue(formatted)
const style = cellTest.getStyle();
style.setDataFormatString('d mmmm yyyy');
style.backgroundColor ='lightgreen',
cellTest.setStyle(style)
cellTest.setStyle(style)
found = true;
}
}
}
Most of the code is iterating through the cells so that we can test each one. The actual test uses cellTest.stringCellValue – that function returns the contents of the cell as a string. If nothing is in the cell, then an empty string is returned.
Once a cell is found that contains the text that we are looking for (in this case “2/23/2018), we call setDateValue, passing in the current date converted to a string in the format '2025-07-29 13:13:00' (I haven’t included the function which creates that formatted, so that we can concentrate on the Spreadsheet Editor code).
Next, we get the cell style of the selected cell, set the DataFormatString to the way that we want and set the backgroundColor property to “lightgreen”
Then we call setStyle on the cell.
Note: In 11.6 there is a bug that requires you to call that function twice if you want to update the DataFormatString.
Finally, we set found to true, allowing us to short-circuit out of the loops searching through all the cells in the sheet.
Now when we run the code we get:
Figure 11 - The output of our code - we have updated the cell with the current date, specified a date format and set the background color.
Perfect!
At this point you could download the spreadsheet, serialize it and save it via a web API or do whatever you need to do.
Regardless of what you do next, the changes we made are still there – you can check that by opening it in Excel.
Figure 12 - Our modified file, now shown in Excel - our changes are still present.
That’s a great result.
You could, if you want, extend this mechanism to allow you to dynamically populate spreadsheets in a similar way to how Apryse Document Generation will allow you to create DOCX and PDFs using Office templates and JSON.
Or you could interactively edit the spreadsheet with a mixture of UI based and programmatic commands.
Or you could search for values that are greater than expected (in a quote, for example) and flag them for manual review. What a huge range of opportunities!
We have only just scratched the surface of what Spreadsheet Editor offers. If that was not enough, with just a couple of changes in the code you can have a DOCX editor, or a PDF viewer and editor.
So, grab yourself a free trial license and try things out for yourself. There’s documentation to get you started and the Apryse Discord channel where you can ask for help or make suggestions.
Roger Dunham
Share this post
PRODUCTS
Platform Integrations
End User Applications
Popular Content