Build a Live Row Viewer Sidebar with onSelectionChange
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.

Live Row Viewer Sidebar: Google Sheets Template
View row details instantly in a sidebar when you click any cell.
Already a subscriber? Log in
Prerequisites
This tutorial assumes that you're familiar with the following concepts:
- You know how to create and run Google Apps Script code in a spreadsheet. If you're new to Apps Script, start with Getting started with Apps Script.
- You understand how to create custom menus using the
onOpentrigger. See Custom menus in Google Sheets for a refresher. - You're familiar with Triggers in Apps Script, particularly the onSelectionChange simple trigger.
- You're familiar with creating HTML sidebars using HtmlService. Review How to create a sidebar in Google Sheets if needed.
- You're familiar with caching data in Apps Script.
- You have basic knowledge of HTML, CSS, and JavaScript.
5 steps to implement the Row Viewer sidebar
- Step 1 — Understand the architecture
- Step 2 — Create the onSelectionChange trigger
- Step 3 — Build the data retrieval function
- Step 4 — Design the HTML sidebar
- Step 5 — Add the custom menu
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.

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:
This section is exclusively available to newsletter subscribers. Subscribe below to access it.
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:
This section is exclusively available to newsletter subscribers. Subscribe below to access it.
Already a subscriber? Log in
And here is the complete Sidebar.html file:
This section is exclusively available to newsletter subscribers. Subscribe below to access it.
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.
Thanks for your feedback!
Your input helps improve the content for everyone.