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:

  1. Use HasOleObjects to check whether the worksheet holds any embedded objects, and stop early if it does not
  2. Walk every object of the worksheet through the OleObjects collection
  3. Read four pieces of information off each object:
    • ObjectType: the kind of object, such as WordDocument or PowerPointPresentation
    • OleOriginName: the file name the object had before it was embedded
    • Location: the cell the object sits on
    • the length of OleData: the data size
  4. 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:

Read the OLE object information of a worksheet


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:

  1. Use HasOleObjects to confirm the worksheet holds embedded objects
  2. Walk every object in the OleObjects collection and decide the name and MIME type of the output file from ObjectType
  3. 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
  4. 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:

Extracted OLE files


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.

Published in Objects

Besides holding cell data, an Excel workbook is often used as a container for files: a quotation carries a Word version of the contract terms, a product sheet carries a PDF datasheet, and double-clicking the object opens the source file directly. Files embedded into a worksheet like this are OLE objects (Object Linking and Embedding). Inserting one by hand takes two steps in the Excel UI—Insert → Object—but doing it from code in the browser needs a dedicated API.Spire.XLS for JavaScript performs this work directly in the browser through WebAssembly, managing input and output files with a virtual file system (VFS) and requiring no backend service.

This article covers two key features:

For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is already installed and the WebAssembly module has been initialized.


Insert an OLE Object in Excel

OleObjects.Add inserts an external file into a worksheet. It takes three arguments: the file to embed, the icon the object shows on the sheet, and the link type—OleLinkType.Embed embeds the file into the workbook, OleLinkType.Link inserts it as a link. After the object is in place, Location decides which cell it is anchored to and ObjectType declares what was embedded, which is how Excel knows which program to use when the object is double-clicked. The steps are:

  1. Create a new workbook and write a caption into a cell.
  2. Open the workbook to be embedded and render its worksheet to an image, to use as the display icon.
  3. Embed that Excel file into the worksheet with OleObjects.Add.
  4. Set Location and ObjectType.
  5. Save the workbook.

Here is a complete code example that inserts an Excel file into a worksheet as an OLE object in React:

function App() {
  const insertOleObject = 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 into VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Load the Excel file to be embedded into VFS
    const embeddedFileName = 'OLEObjects.xlsx';
    await window.spire.FetchFileToVFS(embeddedFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

    // Create a new workbook and write the caption
    const workbook = new xlsModule.Workbook();
    const sheet = workbook.Worksheets.get(0);
    sheet.Range.get("A1").Text = "Here is an OLE object.";

    // Open the embedded workbook and render its worksheet to an image as the display icon
    const embeddedBook = new xlsModule.Workbook();
    embeddedBook.LoadFromFile(embeddedFileName);
    const embeddedSheet = embeddedBook.Worksheets.get(0);
    embeddedSheet.PageSetup.LeftMargin = 0;
    embeddedSheet.PageSetup.RightMargin = 0;
    embeddedSheet.PageSetup.TopMargin = 0;
    embeddedSheet.PageSetup.BottomMargin = 0;
    const image = embeddedSheet.ToImage(1, 1, 19, 5);
    embeddedBook.Dispose();

    // Embed the Excel file into the worksheet; the file data is stored with the workbook
    const oleObject = sheet.OleObjects.Add(
      embeddedFileName,
      image,
      xlsModule.OleLinkType.Embed
    );

    // Anchor the object at cell B4 and declare it as an Excel worksheet
    oleObject.Location = sheet.Range.get("B4");
    oleObject.ObjectType = xlsModule.OleObjectType.ExcelWorksheet;

    // Save the workbook
    const outputFileName = "InsertOLEObject.xlsx";
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

    // Release resources
    workbook.Dispose();

    // Read the result file from VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Insert an OLE Object</h1>
      <button onClick={insertOleObject}>Start</button>
    </div>
  );
}

export default App;

The icon here is taken directly from the rendered embedded worksheet, so the OLE object shows its own content on the sheet. Switching ObjectType to values such as OleObjectType.WordDocument or OleObjectType.AdobeAcrobatDocument declares other kinds of embedded files.

Running it, the effect of inserting a workbook as an OLE object:

Insert an OLE Object in Excel


Insert an OLE Object with a Custom Icon

ToImage has to open a workbook and render a row/column range every time, which suits cases where the object should present its own content; when a single icon should be applied to every attachment, reading a ready-made picture is simpler, and the same picture can be reused across attachments. The second argument of OleObjects.Add accepts both kinds of input. The steps are:

  1. Load the icon image and the attachment file into VFS.
  2. Create a new workbook and read the icon image into a stream with new xlsModule.Stream.
  3. Insert the attachment as an embedded object with OleObjects.Add, passing the icon stream.
  4. Set Location and ObjectType.
  5. Save the workbook.

Here is a complete code example that inserts a PDF attachment with a custom icon in React:

function App() {
  const insertOleObjectWithIcon = 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 into VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Load the icon image and the attachment into VFS
    const iconFileName = 'OLEIcon.png';
    const attachmentFileName = 'Attachment.pdf';
    await window.spire.FetchFileToVFS(iconFileName, '', `${process.env.PUBLIC_URL}/static/data/`);
    await window.spire.FetchFileToVFS(attachmentFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

    // Create a new workbook
    const workbook = new xlsModule.Workbook();
    const sheet = workbook.Worksheets.get(0);

    // Read the icon image as a stream to use as the display icon of the OLE object
    const iconStream = new xlsModule.Stream(iconFileName);

    // Embed the PDF attachment into the worksheet
    const oleObject = sheet.OleObjects.Add(
      attachmentFileName,
      iconStream,
      xlsModule.OleLinkType.Embed
    );

    // Anchor the object at cell B4 and declare it as a PDF document
    oleObject.Location = sheet.Range.get("B4");
    oleObject.ObjectType = xlsModule.OleObjectType.AdobeAcrobatDocument;

    // Save the workbook
    const outputFileName = "InsertOLEObjectWithIcon.xlsx";
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

    // Release resources
    workbook.Dispose();

    // Read the result file from VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Insert an OLE Object with a Custom Icon</h1>
      <button onClick={insertOleObjectWithIcon}>Start</button>
    </div>
  );
}

export default App;

Running it, the effect of inserting a PDF attachment with a custom icon as an OLE object:

Insert an OLE Object with a Custom Icon


FAQ

Only a blank icon shows up on the sheet after inserting?

Cause: The second argument of OleObjects.Add decides the icon an OLE object shows on the sheet. When the image comes from ToImage, a region that falls outside the used range of the worksheet yields a blank picture, so the inserted object also shows only blank space.

Solution: Keep the region inside the part of the sheet that actually has content, or use a ready-made image file instead:

// Use a fixed picture as the icon, independent of the worksheet content
const iconStream = new xlsModule.Stream('OLEIcon.png');
const oleObject = sheet.OleObjects.Add('Attachment.pdf', iconStream, xlsModule.OleLinkType.Embed);
oleObject.Location = sheet.Range.get("B4");
oleObject.ObjectType = xlsModule.OleObjectType.AdobeAcrobatDocument;

What happens if ObjectType is set to something else?

Cause: ObjectType is not merely a comment—its value is written into the progId field of the workbook, and Excel uses that identifier to find the right program when the object is double-clicked. The same PDF attachment declares a progId of Acrobat Document under OleObjectType.AdobeAcrobatDocument; declare it as OleObjectType.ExcelWorksheet and the progId becomes Worksheet, so Excel attempts to open the PDF with Excel itself, and the object will not open.

Solution: Set ObjectType to the real type of the embedded file. The common values are:

Embedded file ObjectType
Excel workbook OleObjectType.ExcelWorksheet
Word document OleObjectType.WordDocument
PowerPoint presentation OleObjectType.PowerPointSlide
PDF document OleObjectType.AdobeAcrobatDocument
// Declare the real file type so Excel opens it with the right program
oleObject.ObjectType = xlsModule.OleObjectType.AdobeAcrobatDocument;

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.

Published in Objects

In daily Excel document processing, textboxes are often used to add explanatory text, annotations, or tips to data — whether adding comments to reports or extracting annotation content from existing documents, the add/remove/modify operations on textboxes are essential. Spire.XLS for JavaScript completes these operations directly in the browser based on WebAssembly, managing input and output files through a virtual file system (VFS), with no backend service required.

This article covers three core features:

For installation and project configuration, refer to Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module is initialized.


Add TextBox

Adding textboxes to a worksheet provides supplementary explanations for data, such as operation guidance or notes. Spire.XLS for JavaScript inserts a textbox at a specified position with the Worksheet.TextBoxes.AddTextBox() method, after which you can set the text, alignment, font, and background color of the textbox, or fill it with a picture. The main steps are as follows:

  1. Create a Workbook object and use the LoadFromFile() method to load the Excel document.
  2. Use the Workbook.Worksheets.get() method to get a specific worksheet.
  3. Use the Worksheet.TextBoxes.AddTextBox() method to add the first textbox, and set its text, horizontal/vertical center alignment, font, and background color.
  4. Use the Worksheet.TextBoxes.AddTextBox() method to add a second textbox and fill it with a picture.
  5. Use the Workbook.SaveToFile() method to save the document to a specified path.

Here is a complete code example showing how to add two textboxes to a worksheet in React — one containing text and one filled with a picture:

function App() {
  const addTextBox = async () => {
    // Get the Spire.XLS WASM module
    const xlsModule = window.wasmModule?.spirexls;

    // Check whether the module is ready
    if (!xlsModule) {
      alert('Spire.Xls is not ready yet');
      return;
    }

    // Load the font, Excel file and picture into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'TextBox.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
    await window.spire.FetchFileToVFS('logo.png', '', `${process.env.PUBLIC_URL}data/`);

    // Load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });

    // Get the first worksheet
    const sheet = workbook.Worksheets.get(0);

    // Add the first textbox and set its position and size
    const textBox = sheet.TextBoxes.AddTextBox(3, 2, 50, 196);

    // Set the text in the textbox
    textBox.Text = 'Insert Excel TextBox';

    // Set the text to be centered horizontally and vertically
    textBox.HAlignment = xlsModule.CommentHAlignType.Center;
    textBox.VAlignment = xlsModule.CommentVAlignType.Center;

    // Set the font of the textbox (bold, white, 12pt)
    const font = workbook.CreateFont();
    font.FontName = 'Arial';
    font.Size = 12;
    font.IsBold = true;
    font.Color = xlsModule.Color.get_White();
    const rt = xlsModule.RichTextShape.Convert(textBox.RichText);
    rt.SetFont(0, textBox.Text.length - 1, font);

    // Set the background color of the textbox to blue-gray
    textBox.Fill.FillType = xlsModule.ShapeFillType.SolidColor;
    textBox.Fill.ForeKnownColor = xlsModule.ExcelColors.BlueGray;

    // Add the second textbox and set its position and size
    const textBox2 = sheet.TextBoxes.AddTextBox(6, 5, 90, 90);

    // Load a picture and fill the textbox with it
    textBox2.Fill.CustomPicture('logo.png');
    textBox2.Fill.FillType = xlsModule.ShapeFillType.Picture;

    // Set the border of the second textbox to 0
    textBox2.Line.Weight = 0;

    // Save the document
    const outputFileName = 'AddTextBox_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName });

    // Release resources
    workbook.Dispose();

    // Read the converted file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Add TextBox</h1>
      <button onClick={addTextBox}>
        Start
      </button>
    </div>
  );
}

export default App;

The result of adding the textboxes Add TextBox


Extract Text and Image from TextBox

When you need to aggregate or reuse annotation information in existing documents, you can iterate through the textboxes and extract their text content and fill images. Spire.XLS for JavaScript gets the number of textboxes with Worksheet.TextBoxes.Count and iterates over each textbox with the Worksheet.TextBoxes.get() method: it reads the Text property to obtain the text content, checks the fill type through Fill.FillType, and extracts the fill image through the Fill.Picture property, finally saving the results as a txt file and a png image file respectively. The main steps are as follows:

  1. Create a Workbook object and use the LoadFromFile() method to load the Excel document.
  2. Use the Workbook.Worksheets.get() method to get a specific worksheet.
  3. Iterate over each textbox in the TextBoxes collection with Worksheet.TextBoxes.Count and Worksheet.TextBoxes.get().
  4. Read the Text property of each textbox to collect the text content.
  5. For a textbox filled with a picture, get its fill image through the Fill.Picture property and save it as a png file.
  6. Write the collected text into a txt file.

Here is a complete code example showing how to extract text and images from a textbox in React:

function App() {
  const extractTextAndImage = async () => {
    // Get the Spire.XLS WASM module
    const xlsModule = window.wasmModule?.spirexls;

    // Check whether the module is ready
    if (!xlsModule) {
      alert('Spire.Xls is not ready yet');
      return;
    }

    // Load the font and Excel file into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'TextBox.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // Load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });

    // Get the first worksheet
    const sheet = workbook.Worksheets.get(0);

    // Iterate over all textboxes in the worksheet and extract text and pictures
    const textLines = [];
    const pictureFiles = [];
    for (let i = sheet.TextBoxes.Count - 1; i >= 0; i--) {
      const shape = sheet.TextBoxes.get(i);

      // Extract the text in the textbox
      if (shape.Text) {
        textLines.push(shape.Text);
      }

      // Extract the fill picture of the textbox
      if (shape.Fill.FillType === xlsModule.ShapeFillType.Picture) {
        const picture = shape.Fill.Picture;
        const imageFile = 'ExtractedImage' + i + '.png';
        picture.Save(imageFile);
        pictureFiles.push(imageFile);
      }
    }

    // Save the extracted text as a txt file
    const textFile = 'ExtractedText.txt';
    window.dotnetRuntime.Module.FS.writeFile(textFile, textLines.join('\r\n'));

    // Release resources
    workbook.Dispose();

    // Read the extracted txt file from the VFS and trigger the download
    const txtArray = window.dotnetRuntime.Module.FS.readFile(textFile);
    const txtBlob = new Blob([txtArray], { type: 'text/plain' });
    const txtUrl = URL.createObjectURL(txtBlob);
    const txtAnchor = document.createElement('a');
    txtAnchor.href = txtUrl;
    txtAnchor.download = textFile;
    txtAnchor.click();
    URL.revokeObjectURL(txtUrl);

    // Read the extracted picture files from the VFS and trigger the downloads
    for (const imageFile of pictureFiles) {
      const imageArray = window.dotnetRuntime.Module.FS.readFile(imageFile);
      const imageBlob = new Blob([imageArray], { type: 'application/png' });
      const imageUrl = URL.createObjectURL(imageBlob);
      const imageAnchor = document.createElement('a');
      imageAnchor.href = imageUrl;
      imageAnchor.download = imageFile;
      imageAnchor.click();
      URL.revokeObjectURL(imageUrl);
    }
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Extract Text And Image From TextBox</h1>
      <button onClick={extractTextAndImage}>
        Start
      </button>
    </div>
  );
}

export default App;

The result of extracting the text and image from the textbox Extract Text and Image from TextBox


Remove TextBox

When annotation information in a document is no longer needed, you can delete it to keep the worksheet tidy. Spire.XLS for JavaScript deletes a specified textbox by index with the Worksheet.TextBoxes.RemoveAt() method. The main steps are as follows:

  1. Create a Workbook object and use the LoadFromFile() method to load the Excel document.
  2. Use the Workbook.Worksheets.get() method to get a specific worksheet.
  3. Use the Worksheet.TextBoxes.RemoveAt() method to delete the textbox at a specified index.
  4. Use the Workbook.SaveToFile() method to save the document to a specified path.

Here is a complete code example showing how to remove a textbox from a worksheet in React:

function App() {
  const removeTextBox = async () => {
    // Get the Spire.XLS WASM module
    const xlsModule = window.wasmModule?.spirexls;

    // Check whether the module is ready
    if (!xlsModule) {
      alert('Spire.Xls is not ready yet');
      return;
    }

    // Load the font and Excel file into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'TextBox.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // Load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });

    // Get the first worksheet
    const sheet = workbook.Worksheets.get(0);

    // Remove the first textbox
    sheet.TextBoxes.RemoveAt(0);

    // Save the document
    const outputFileName = 'RemoveTextBox_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName });

    // Release resources
    workbook.Dispose();

    // Read the converted file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Remove TextBox</h1>
      <button onClick={removeTextBox}>
        Start
      </button>
    </div>
  );
}

export default App;

The result of removing the textbox Remove TextBox


Frequently Asked Questions

The added textbox does not display in the result document

Cause: The row and column coordinates specified in the AddTextBox() method are out of range, or the font file was not loaded into the VFS, so the text in the textbox cannot be rendered properly.

Solution: Make sure the row and column coordinates are within the worksheet range, and ensure the required font has been loaded via FetchFileToVFS() before use, for example:

await window.spire.FetchFileToVFS(
  'ARIAL.TTF', '/Library/Fonts/', '/'
);

An error occurs when extracting an image due to the fill type

Cause: Accessing the Fill.Picture property directly only works for textboxes filled with a picture. If no image fill is set on the textbox (for example, a solid-color fill), accessing this property throws an exception.

Solution: Check whether the Fill.FillType of the textbox is Picture before accessing Fill.Picture; only then get the picture and call the Save() method to save it.


Obtain 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.

Published in Objects

Shapes are graphic elements in Excel that enhance the visual appeal of a worksheet and convey information intuitively, such as arrows, rectangles, ovals, and stars. With shapes, you can add annotations, process-flow indicators, or decorative elements next to your data, making reports more vivid and easier to read. Spire.XLS for JavaScript runs entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage input and output files — no backend server required. It provides a complete API for adding shapes and customizing their appearance (such as fill, rotation angle, text, and shadow), reading text and images from shapes, and deleting specified or all shapes.

This article covers three core features:

For installation and project setup, refer to Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module is initialized.


Add Shapes to Excel

Adding shapes to Excel can highlight key data and beautify the layout of a worksheet. With Spire.XLS for JavaScript, you can add a shape and set its position (row, column) and size (width, height) at once using the PrstGeomShapes.AddPrstGeomShape() method, and then customize its appearance through the shape's properties — set solid, gradient, texture, or picture fill via Fill, add text via Text, set the rotation angle via Rotation, apply a shadow effect via Shadow, and control visibility via Visible. The steps are as follows:

  1. Create a Workbook object and get the default worksheet.
  2. Add shapes using PrstGeomShapes.AddPrstGeomShape(), setting the shape type, position, and size through the parameters.
  3. Set solid, gradient, texture, or picture fill for the shapes via the Fill property.
  4. Add text to a shape via the Text property, and set the rotation angle via the Rotation property.
  5. Set a shadow effect for a shape via the Shadow property.
  6. Save the workbook to an Excel file using SaveToFile().

Below is a complete code example demonstrating how to add and customize various shapes in React:

function App() {
  const addShapes = 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 picture into the virtual file system (VFS)
    await window.spire.FetchFileToVFS('SpireXls.png', '', `${process.env.PUBLIC_URL}/image/`);

    // Create a new workbook and get the default worksheet
    const workbook = new xlsModule.Workbook();
    let sheet = workbook.Worksheets.get(0);

    // Add a triangle shape and fill it with a solid color
    let triangle = sheet.PrstGeomShapes.AddPrstGeomShape(2, 2, 100, 100, xlsModule.PrstGeomShapeType.Triangle);
    triangle.Fill.ForeColor = xlsModule.Color.get_Yellow();
    triangle.Fill.FillType = xlsModule.ShapeFillType.SolidColor;
    // Add text to the triangle and set its rotation angle
    triangle.Text = 'Triangle';
    triangle.Rotation = 45;

    // Add a heart shape and fill it with a gradient color
    let heart = sheet.PrstGeomShapes.AddPrstGeomShape(2, 5, 100, 100, xlsModule.PrstGeomShapeType.Heart);
    heart.Fill.ForeColor = xlsModule.Color.get_Red();
    heart.Fill.FillType = xlsModule.ShapeFillType.Gradient;
    // Set the shadow style for the heart
    heart.Shadow.Angle = 90;
    heart.Shadow.Distance = 10;
    heart.Shadow.Size = 150;
    heart.Shadow.Color = xlsModule.Color.get_Gray();
    heart.Shadow.Blur = 30;
    heart.Shadow.Transparency = 1;
    heart.Shadow.HasCustomStyle = true;

    // Add an arrow shape
    let arrow = sheet.PrstGeomShapes.AddPrstGeomShape(10, 2, 100, 100, xlsModule.PrstGeomShapeType.CurvedRightArrow);

    // Add a cloud shape and fill it with a picture
    let cloud = sheet.PrstGeomShapes.AddPrstGeomShape(10, 5, 100, 100, xlsModule.PrstGeomShapeType.Cloud);
    cloud.Fill.CustomPicture({ im: new xlsModule.Stream('SpireXls.png'), name: 'SpireXls.png' });

    // Save the workbook
    const outputFileName = 'AddShapes.xlsx';
    workbook.SaveToFile(outputFileName);
    workbook.Dispose();

    // Read the file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Add Shapes</h1>
      <button onClick={addShapes}>
        Generate
      </button>
    </div>
  );
}

export default App;

Shapes added to Excel with Spire.XLS for JavaScript

Shapes added to Excel with Spire.XLS for JavaScript


Read Text and Images from Excel Shapes

Reading the text and images from shapes helps you extract the data inside shapes in batch, or reuse and archive shape resources. With Spire.XLS for JavaScript, you can load an Excel file containing shapes, get a specified shape by index via PrstGeomShapes.get(), then read its text content via the Text property and get its fill picture via Fill.Picture. The steps are as follows:

  1. Create a Workbook object and load an existing Excel file containing shapes.
  2. Get the worksheet via workbook.Worksheets.get().
  3. Get a specified shape by index using sheet.PrstGeomShapes.get().
  4. Read the text in the shape via the Text property.
  5. Read the fill picture in the shape via the Fill.Picture property.
  6. Save the read text and image as txt and png files.

Below is a complete code example demonstrating how to read text and images from shapes in React (the example loads the AddShapes.xlsx file generated in the previous section):

function App() {
  const readShapes = 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 sample file containing shapes into the virtual file system (VFS)
    let excelFileName = 'AddShapes.xlsx';
    await window.spire.FetchFileToVFS(excelFileName, '', `${process.env.PUBLIC_URL}data/`);

    // Create a workbook object and load the existing file
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: excelFileName });

    // Get the first worksheet
    let sheet = workbook.Worksheets.get(0);

    // Get the first shape (triangle) and read the text inside it
    let triangle = sheet.PrstGeomShapes.get(0);
    let text = triangle.Text;

    // Get the fourth shape (cloud) and read the picture inside it
    let cloud = sheet.PrstGeomShapes.get(3);
    let image = cloud.Fill.Picture;
    const imageFileName = 'ExtractImageFromShape.png';
    image.Save(imageFileName);

    workbook.Dispose();

    // Save the read text to a txt file and trigger download
    const textFileName = 'ExtractTextFromShape.txt';
    const textBlob = new Blob([`The text in the first shape is: ${text}`], { type: 'text/plain;charset=utf-8' });
    const textUrl = URL.createObjectURL(textBlob);
    const a1 = document.createElement('a');
    a1.href = textUrl;
    a1.download = textFileName;
    a1.click();
    URL.revokeObjectURL(textUrl);

    // Read the image file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(imageFileName);
    const blob = new Blob([fileArray], { type: 'application/png' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = imageFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Read Text and Image from Shapes</h1>
      <button onClick={readShapes}>
        Generate
      </button>
    </div>
  );
}

export default App;

Text and images read from Excel shapes with Spire.XLS for JavaScript

Text and images read from Excel shapes with Spire.XLS for JavaScript


Delete Shapes in Excel

When shapes are no longer needed, deleting them in time keeps the worksheet clean and reduces the file size. With Spire.XLS for JavaScript, you can delete a specified shape via the Remove() method, or iterate through the shape collection and call Remove() on each shape to clear all shapes in a worksheet. The steps are as follows:

  1. Create a Workbook object and load an existing Excel file containing shapes.
  2. Get the worksheet via workbook.Worksheets.get().
  3. Get a specified shape using sheet.PrstGeomShapes.get(), and call its Remove() method to delete the shape.
  4. Save the workbook to an Excel file using SaveToFile().

Below is a complete code example demonstrating how to delete shapes in React (the example loads the AddShapes.xlsx file generated in the previous section):

function App() {
  const deleteShapes = 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 sample file containing shapes into the virtual file system (VFS)
    let excelFileName = 'AddShapes.xlsx';
    await window.spire.FetchFileToVFS(excelFileName, '', `${process.env.PUBLIC_URL}data/`);

    // Create a workbook object and load the existing file
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: excelFileName });

    // Get the first worksheet
    let sheet = workbook.Worksheets.get(0);

    // Delete the first shape in the worksheet
    sheet.PrstGeomShapes.get(0).Remove();

    // Delete all the shapes in the worksheet
    // for (let i = sheet.PrstGeomShapes.Count - 1; i >= 0; i--) {
    //   sheet.PrstGeomShapes.get(i).Remove();
    // }

    // Save the workbook
    const outputFileName = 'DeleteShapes.xlsx';
    workbook.SaveToFile(outputFileName);
    workbook.Dispose();

    // Read the file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Delete Shapes</h1>
      <button onClick={deleteShapes}>
        Generate
      </button>
    </div>
  );
}

export default App;

Specified shape deleted from Excel with Spire.XLS for JavaScript

Specified shape deleted from Excel with Spire.XLS for JavaScript


FAQ

How to get the name and type of a shape?

Cause: When a worksheet contains many shapes, you may need to identify and locate shapes by their name or type rather than by index.

Solution: Read the Name and PrstShapeType properties of the shape to get its name and type:

// Get the worksheet
let sheet = workbook.Worksheets.get(0);
// Get the first shape
let shape = sheet.PrstGeomShapes.get(0);
// Get the name of the shape
let shapeName = shape.Name;
// Get the type of the shape
let shapeType = shape.PrstShapeType;

How to check whether a shape is currently visible?

Cause: After loading shapes from a file, you may need to determine whether a shape is hidden so that you can decide whether to process it further.

Solution: Read the Visible property of the shape to know its visibility state:

// Get the worksheet
let sheet = workbook.Worksheets.get(0);
// Get the first shape
let shape = sheet.PrstGeomShapes.get(0);
// Check whether the shape is visible
let isVisible = shape.Visible;

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.

Published in Objects