Build a Live Row Viewer Sidebar with onSelectionChange

Updated February 22, 2026

Imagine clicking any row in your spreadsheet and instantly seeing all its details beautifully formatted in a sidebar. No more scrolling horizontally through dozens of columns or losing context when working with large datasets. In this tutorial, I will show you how to build a live Row Viewer sidebar that responds to your cell selections in real-time using the onSelectionChange simple trigger.

This type of automation is useful for building CRM sheets, inventory databases, or any spreadsheet where rows contain multiple fields that are difficult to view at once. Sales teams can quickly review client details, operations managers can inspect inventory records, and analysts can examine data points without constantly scrolling.

Screenshot of a spreadsheet application displaying client data with a side panel showing detailed information for a selected row.
Newsletter Subscriber Exclusive

Live Row Viewer Sidebar: Google Sheets Template

View row details instantly in a sidebar when you click any cell.

Preview

By subscribing, you agree to our Privacy Policy and Terms of Service

Already a subscriber? Log in

Prerequisites

This tutorial assumes that you're familiar with the following concepts:

5 steps to implement the Row Viewer sidebar

Step 1 — Understand the architecture

Before writing any code, I want to explain why this solution uses a specific pattern. The onSelectionChange trigger is a simple trigger that fires automatically whenever you select a different cell in your spreadsheet. However, simple triggers have an important limitation: they cannot directly interact with the user interface, including sidebars.

To work around this limitation, I will show you how to use a polling architecture that involves three components working together. First, the onSelectionChange function detects when you select a new row and stores that information in Document Properties, which acts as a shared data store accessible by both the trigger and the sidebar. Second, the sidebar runs a JavaScript polling loop that checks Document Properties every 500 milliseconds to see if the selection has changed. Third, if the selection has changed, the display is updated accordingly.

Diagram illustrating the polling mechanism of an Apps Script Row Viewer, showing data flow from Google Sheet selection to a client-side sidebar.

This approach works because Document Properties persist across function calls and are fast to read and write. The polling interval of 500 milliseconds provides a responsive feel without overwhelming the server with requests.

Step 2 — Create the onSelectionChange trigger

The onSelectionChange function is a special simple trigger that Apps Script recognizes by its exact name. When you select any cell in your spreadsheet, Apps Script automatically calls this function and passes an event object containing information about the selection.

In your Apps Script project, replace the contents of Code.gs with the following function:

function onSelectionChange(e) {
  try {
    var range = e.range;
    var row = range.getRow();
    var sheet = e.source.getActiveSheet();
    
    PropertiesService.getDocumentProperties().setProperties({
      'sel_row': String(row),
      'sel_sheet': sheet.getName(),
      'sel_time': String(Date.now())
    });
    
    console.log('onSelectionChange: Row ' + row + ' on ' + sheet.getName());
  } catch (err) {
    console.error('onSelectionChange error: ' + err);
  }
}

The function extracts three pieces of information from the event: the row number, the sheet name, and the current timestamp. I store all three in Document Properties using setProperties, which efficiently writes multiple values in a single call. The timestamp is particularly important because it allows the sidebar to detect when a new selection has occurred, even if you click the same row twice.

Step 3 — Build the data retrieval function

The sidebar needs a way to fetch the complete row data once it detects a selection change. I will create a function called getSelectedRowData that reads the stored selection information, retrieves the corresponding row from the spreadsheet, and returns it as a structured object.

Add this function to your Code.gs file:

function getSelectedRowData() {
  try {
    var props = PropertiesService.getDocumentProperties();
    var allProps = props.getProperties();
    
    var rowStr = allProps['sel_row'];
    var sheetName = allProps['sel_sheet'];
    var timeStr = allProps['sel_time'];
    
    if (!rowStr || rowStr === 'undefined' || rowStr === 'null') {
      return { 
        status: 'waiting', 
        message: 'Click any data row to begin'
      };
    }
    
    var row = parseInt(rowStr, 10);
    
    if (row < 2) {
      return { status: 'header', message: 'Select a data row (not the header)' };
    }
    
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    var sheet = ss.getSheetByName(sheetName);
    var lastCol = sheet.getLastColumn();
    
    var headers = sheet.getRange(1, 1, 1, lastCol).getValues()[0];
    var values = sheet.getRange(row, 1, 1, lastCol).getValues()[0];
    
    var data = {};
    var orderedHeaders = [];
    for (var i = 0; i < headers.length; i++) {
      if (headers[i]) {
        var val = values[i];
        if (val instanceof Date) {
          val = Utilities.formatDate(val, Session.getScriptTimeZone(), 'MMM d, yyyy');
        }
        data[headers[i]] = val;
        orderedHeaders.push(headers[i]);
      }
    }
    
    return {
      status: 'ok',
      row: row,
      sheet: sheetName,
      timestamp: parseInt(timeStr, 10) || 0,
      headers: orderedHeaders,
      data: data
    };
    
  } catch (err) {
    return { status: 'error', message: 'Server error: ' + String(err) };
  }
}

This function performs several validation checks before returning data. It verifies that a selection exists, ensures the selected row is not the header row, and confirms the sheet still exists. The function returns different status codes (waiting, header, error, ok) so the sidebar can display appropriate messages for each situation.

I also preserve the column order by maintaining a separate orderedHeaders array. JavaScript objects do not guarantee property order, but by returning both the headers array and the data object, the sidebar can display fields in the same order they appear in the spreadsheet.

Step 4 — Design the HTML sidebar

The sidebar is an HTML file that displays the row data in a clean, card-based layout. Create a new file in your Apps Script project by clicking the plus icon next to Files, selecting HTML, and naming it Sidebar.

The HTML file contains three main sections: styles for visual formatting, the content container, and JavaScript for polling and rendering. Here is the complete sidebar code:

Newsletter Subscriber Exclusive

This section is exclusively available to newsletter subscribers. Subscribe below to access it.

By subscribing, you agree to our Privacy Policy and Terms of Service

Already a subscriber? Log in

The key element is the google.script.run API, which allows client-side JavaScript to call server-side Apps Script functions. The withSuccessHandler method specifies what function to call when the server responds. I use setInterval to call the poll function every 500 milliseconds, creating a responsive experience without requiring manual refresh.

The timestamp comparison in handleData is key for performance. By checking whether the timestamp has changed before updating the DOM, the sidebar avoids unnecessary re-renders when you click within the same row or when no selection has occurred.

Step 5 — Add the custom menu

Finally, I need a way for users to open the sidebar. I will create a custom menu that appears when the spreadsheet opens using the onOpen simple trigger.

Add these two functions to your Code.gs file:

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Row Viewer')
    .addItem('Open Sidebar', 'showSidebar')
    .addToUi();
}

function showSidebar() {
  var html = HtmlService.createHtmlOutputFromFile('Sidebar')
    .setTitle('Row Details');
  SpreadsheetApp.getUi().showSidebar(html);
}

The onOpen function creates a menu called Row Viewer with a single item that calls showSidebar when clicked. The showSidebar function loads the HTML file and displays it as a sidebar with the title Row Details.

To test your implementation, save all files, refresh your Google Sheet, and click Row Viewer followed by Open Sidebar in the menu bar. Click any data row in your spreadsheet, and you should see the sidebar update to display that row's information.

Full code

Here is the complete Code.gs file for easy copy-paste:

Newsletter Subscriber Exclusive

This section is exclusively available to newsletter subscribers. Subscribe below to access it.

By subscribing, you agree to our Privacy Policy and Terms of Service

Already a subscriber? Log in

And here is the complete Sidebar.html file:

Newsletter Subscriber Exclusive

This section is exclusively available to newsletter subscribers. Subscribe below to access it.

By subscribing, you agree to our Privacy Policy and Terms of Service

Already a subscriber? Log in

Conclusion

In this tutorial, you learned how to build a live Row Viewer sidebar that responds to cell selections using the onSelectionChange simple trigger. You discovered how to work around the limitations of simple triggers by using Document Properties as a communication bridge and implementing a polling pattern in the sidebar.

The key concepts you learned include using PropertiesService to store and retrieve data, creating HTML sidebars with custom styling, using google.script.run for server communication, and implementing efficient polling with timestamp comparison. These patterns are foundational for building many types of interactive Apps Script applications.

Thank you for following along. I hope this Row Viewer makes your spreadsheet work more efficient and enjoyable.

Future work

Here are some ideas to extend this project:

  • Modify the sidebar to include input fields that allow users to edit the row data directly. You would need to create a saveRowData function that writes changes back to the spreadsheet.
  • Add a dropdown in the sidebar that lets users choose which columns to display. Store the preference in User Properties so it persists across sessions.
  • Add Previous and Next buttons to the sidebar that programmatically change the selection using sheet.setActiveRange, allowing users to browse records without clicking in the spreadsheet.
  • Extend the solution to handle sheets with different structures by detecting the header row dynamically or allowing users to configure which row contains headers.

DISCLAIMER: This content is provided for educational purposes only. All code, templates, and information should be thoroughly reviewed and tested before use. Use at your own risk. Full Terms of Service apply.