Contracts, quotations and slide decks are often embedded straight into an Excel workbook: what you see on the sheet is an icon or a thumbnail, while the real document data sits in the xl/embeddings part of the package. Pulling those attachments out for archiving one by one means opening each object and saving a copy by hand.Spire.XLS for JavaScript does the job in the browser through WebAssembly, using a virtual file system (VFS) for the input and the output, with no backend service involved.
This article covers two feature points:
For installation and project configuration, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module has finished initializing.
Read the OLE object information of a worksheet
Before extracting anything it helps to know what a workbook actually carries. A listing sets out the kind, original name, cell and size of every embedded object in one place and serves as an attachment register, so archiving or handing the file over does not mean double-clicking each icon in turn.
The steps are:
- Use
HasOleObjectsto check whether the worksheet holds any embedded objects, and stop early if it does not - Walk every object of the worksheet through the
OleObjectscollection - Read four pieces of information off each object:
ObjectType: the kind of object, such asWordDocumentorPowerPointPresentationOleOriginName: the file name the object had before it was embeddedLocation: the cell the object sits on- the length of
OleData: the data size
- Join the four fields into one line per object and write the listing to
ListOleObjects.txt
The example below collects those fields into a plain text listing:
function App() {
const listOleObjects = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the input file into VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'OLEObjects.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and take the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Check whether the worksheet contains OLE objects
if (!sheet.HasOleObjects) {
alert('The worksheet contains no OLE objects.');
return;
}
// Collect the listing as text: a header line, then one tab-separated line per object
const lines = [['No.', 'Object Type', 'Original File Name', 'Location', 'Data Size (bytes)'].join('\t')];
// Walk the OLE objects of the worksheet, one line per object
let index = 1;
for (const oleObject of sheet.OleObjects) {
lines.push([
index,
String(oleObject.ObjectType),
String(oleObject.OleOriginName),
String(oleObject.Location.RangeAddress),
oleObject.OleData.length,
].join('\t'));
index += 1;
}
// Release the workbook object
workbook.Dispose();
// Write the listing to a text file in the VFS
const outputFileName = 'ListOleObjects.txt';
window.dotnetRuntime.Module.FS.writeFile(outputFileName, lines.join('\n'));
// Read the result file from VFS and trigger the download
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>List OLE Objects</h1>
<button onClick={listOleObjects}>List OLE objects</button>
</div>
);
}
export default App;
The effect of reading the OLE object information:

Extract OLE object payloads by type
A listing only says what is embedded; archiving the attachment itself means getting the document out. An OLE object carries its payload as a byte array, so writing those bytes out restores the document exactly as it was embedded, with no re-typesetting or format conversion.
The steps are:
- Use
HasOleObjectsto confirm the worksheet holds embedded objects - Walk every object in the
OleObjectscollection and decide the name and MIME type of the output file fromObjectType - Take the raw bytes of the object through
OleData, write them into the virtual file system, read them back and wrap them in a downloadable Blob - Dispose of the workbook once the walk is done, then trigger the downloads one by one
The example below walks every object of the worksheet and writes the Word, PowerPoint and PDF attachments out as .docx, .pptx and .pdf files, offering each one for download:
function App() {
const extractOleObjects = async () => {
// Get the Spire.XLS WASM module
const xlsModule = window.wasmModule?.spirexls;
// Check if the module is ready
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// Load the font and the input file into VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'OLEObjects.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// Load the workbook and take the first worksheet
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// Collect the extracted files so the page can offer them for download
const results = [];
// Walk the OLE objects of the worksheet and write each one out in its own format
if (sheet.HasOleObjects) {
for (const oleObject of sheet.OleObjects) {
const type = oleObject.ObjectType;
let outputFileName = '';
let mimeType = '';
// Word document
if (type === xlsModule.OleObjectType.WordDocument) {
outputFileName = 'ExtractWord.docx';
mimeType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
}
// PowerPoint presentation: .pptx and .sldx fall under two different enum members
else if (
type === xlsModule.OleObjectType.PowerPointPresentation ||
type === xlsModule.OleObjectType.PowerPointSlide
) {
outputFileName = 'ExtractPowerPoint.pptx';
mimeType = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
}
// PDF document
else if (type === xlsModule.OleObjectType.AdobeAcrobatDocument) {
outputFileName = 'ExtractPdf.pdf';
mimeType = 'application/pdf';
}
// Any other type is left alone
if (!outputFileName) continue;
// Write the raw data of the object into the virtual file system
window.dotnetRuntime.Module.FS.writeFile(outputFileName, oleObject.OleData);
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
results.push({
name: outputFileName,
url: URL.createObjectURL(new Blob([fileArray], { type: mimeType })),
});
}
}
// Release the workbook object
workbook.Dispose();
// Trigger the downloads one by one
results.forEach(({ name, url }) => {
const a = document.createElement('a');
a.href = url;
a.download = name;
a.click();
URL.revokeObjectURL(url);
});
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Extract OLE Objects</h1>
<button onClick={extractOleObjects}>Extract OLE objects by type</button>
</div>
);
}
export default App;
The effect of extracting the attachment documents:

FAQ
OleObjectType.PowerPointSlide never matches a PowerPoint attachment
Cause: OleObjectType splits objects by the real format of the embedded file. A .pptx presentation reports PowerPointPresentation; only slideshow formats such as .sldx and .ppt land on PowerPointSlide, so testing for the latter alone misses the vast majority of PowerPoint attachments.
Solution: match both enum members in the branch, for example:
else if (
type === xlsModule.OleObjectType.PowerPointPresentation ||
type === xlsModule.OleObjectType.PowerPointSlide
) {
outputFileName = 'ExtractPowerPoint.pptx';
}
Only the attachments of the first worksheet come out
Cause: OleObjects is a per-worksheet collection. The example takes the first sheet through Worksheets.get(0) and walks the OleObjects of that sheet alone, so objects embedded on any other worksheet are never visited. When the attachments are spread over several sheets, the extraction silently comes up short, with no error to show for it.
Solution: walk the worksheets instead, taking the OleObjects of each one in turn:
for (let i = 0; i < workbook.Worksheets.Count; i++) {
const worksheet = workbook.Worksheets.get(i);
for (const oleObject of worksheet.OleObjects) {
// write the attachment out by type
}
}
Get a Free License
Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.
