Skip to content

Get Non-Contiguous Selections in Word with Office.js

A developer on a Microsoft Q&A forum recently asked: “I am building a Word Add-in where users select several paragraphs using the Ctrl key (non-contiguous selection). When I call context.document.getSelection(), I only get the very last paragraph selected. Is there a way to retrieve the entire collection of non-contiguous ranges?”

This is a common point of frustration for those transitioning from Word VBA to the modern Office.js API. In the legacy COM/VBA world, the Selection object could occasionally be coerced into handling multiple panes, but the JavaScript API handles things differently.

As of 2024, the Office.js Word API does not support retrieving non-contiguous selections via the getSelection() method. When a user selects multiple pieces of text using Ctrl + Click, the API only recognizes the last range selected as the “active” selection.

To solve this for your users, you must use a “Marker” or “Collection” workflow.

Section titled “The Recommended Workaround: The “Highlight and Extract” Method”

Since you cannot grab multiple selections at once, the most professional workaround is to ask your users to “mark” their selections (e.g., using a specific Highlight color or a temporary Character Style) and then use the body.search() or split() methods to programmatically retrieve those ranges.

This script searches the document for all text highlighted in yellow (simulating a “multi-selection”) and processes them as an array of ranges.

Tested on Word for Microsoft 365 (Version 2402) as of 2024.

async function getMarkedSelections() {
await Word.run(async (context) => {
// 1. Target the document body
const body = context.document.body;
// 2. Search for all instances of a specific format
// (In this case, we use highlighting as a proxy for selection)
const searchResults = body.search("*", { matchWildcards: true });
// Load the font property so we can check highlighting
searchResults.load("font");
await context.sync();
const multiSelectedRanges = [];
// 3. Iterate through found ranges and filter for yellow highlight
for (let i = 0; i < searchResults.items.length; i++) {
// 'Yellow' is a common highlight, but you can use custom Styles too
if (searchResults.items[i].font.highlightColor === "Yellow") {
multiSelectedRanges.push(searchResults.items[i]);
// Optional: Do something with each range
console.log("Found text: " + searchResults.items[i].text);
}
}
// 4. Final Sync to apply any changes
await context.sync();
});
}

Comparison of Methods for Handling Multiple Ranges

Section titled “Comparison of Methods for Handling Multiple Ranges”
Method Supports Non-Contiguous? Strategy Best For
getSelection() No Returns only the most recent/last range. Single-item actions.
body.search() Yes Search by text string or wildcards. Finding specific keywords globally.
Formatting Filter Yes Loop through ranges and check .font properties. “Simulated” multi-selection (the workaround).
Paragraph.items Yes Iterate through all paragraphs in the document. Processing every block regardless of selection.

  1. Instruct the User: In your Add-in UI, tell the user: “Highlight the sections you want to process in Yellow, then click ‘Process All’.”
  2. Load the Body: Use context.document.body to define the scope of your search.
  3. Search with Wildcards: Using body.search("*", { matchWildcards: true }) captures every fragment of text that can be independently formatted.
  4. Filter by Property: Inside the loop, check the highlightColor or style. This creates your own “collection” of ranges that mimics a multi-select.
  5. Clear Markers (Optional): Once your logic is complete, you can programmatically remove the highlighting to clean up the document: range.font.highlightColor = null;

  • Performance Issues: Searching a 500-page document for every single character (*) to check for highlighting will be slow. If the document is large, try narrowing the search scope to context.document.getSelection() (if they selected a large block containing the sub-selections) or use a specific Style instead of a highlight color.
  • The “Last Selection” Confusion: If you call getSelection() while three things are highlighted, Word will return a single Range object. If those three things were selected with Ctrl, it only returns the third one. If they were selected by dragging, it returns everything from the start of the first to the end of the last (including the unselected text in between).
  • Word Online vs. Desktop: The behavior of getSelection() is consistent across platforms, but the speed of context.sync() is significantly slower on Word Online. Always load only the properties you need (like font or text) to keep the add-in snappy.

What if I need to apply this to specific paragraphs only? Instead of context.document.body.search(), you can call .search() on a specific Paragraph object or a ContentControl. This limits the “scan” to a smaller area of the document.

Can I automate the “Selection” process with a button? Yes. Many productivity add-ins use a “Collect” button. The user selects text, clicks “Collect,” and the add-in stores that Range in an array in the task pane’s memory. Once the user has “collected” all their snippets, they click a “Process” button to handle all stored ranges at once.