appscript.dev
Automation Intermediate Sheets

Build a row-detail inspector sidebar

Show full Northwind record details on cell click — opens a sidebar with the row expanded.

Published Aug 4, 2025

Northwind’s Clients sheet has grown wide — contact details, account status, notes, renewal dates — and reading a single client now means scrolling sideways across a dozen columns or freezing panes and squinting. Wide sheets are hard to read one record at a time, which is exactly when you most need to.

This automation turns any row into a clean, vertical card. Click a cell in the Clients sheet and a sidebar opens showing that row’s full record as a tidy list of field-and-value pairs. There is nothing to run and no menu to click — selecting a cell is the whole interaction.

What you’ll need

  • A sheet named Clients with a header row in row 1. Every header becomes a field label in the sidebar, so name your columns clearly (name, email, status, and so on).
  • A second file in the Apps Script project named Inspector of type HTML (use File > New > HTML file and name it Inspector).
  • Nothing else — there are no API keys and no extra sheets to create.

The trigger

// The sheet the inspector watches. Selections on any other sheet
// are ignored.
const TARGET_SHEET = 'Clients';

/**
 * Simple trigger that fires whenever the user changes their selection.
 * If the selection is on the Clients sheet, it opens the inspector for
 * the selected row.
 *
 * @param {Object} e  The selection-change event object.
 */
function onSelectionChange(e) {
  // 1. Ignore selections on every sheet except the target.
  if (e.range.getSheet().getName() !== TARGET_SHEET) return;

  // 2. Open the inspector for the row the user just landed on.
  showInspector(e.range.getRow());
}

/**
 * Reads a single row, pairs each value with its header, and renders
 * the result into the sidebar.
 *
 * @param {number} row  The 1-based row number to inspect.
 */
function showInspector(row) {
  const sheet = SpreadsheetApp.getActive().getSheetByName(TARGET_SHEET);

  // 1. Skip the header row itself — there is nothing to inspect.
  if (row < 2) return;

  // 2. Read the header row and the selected row.
  const lastCol = sheet.getLastColumn();
  const [headers] = sheet.getRange(1, 1, 1, lastCol).getValues();
  const values = sheet.getRange(row, 1, 1, lastCol).getValues()[0];

  // 3. Zip headers and values into a single {field: value} object.
  const record = Object.fromEntries(headers.map((k, i) => [k, values[i]]));

  // 4. Render the Inspector template, passing the record into it.
  const template = HtmlService.createTemplateFromFile('Inspector');
  template.row = record;

  // 5. Show the evaluated HTML in a sidebar, titled with the record name.
  SpreadsheetApp.getUi()
    .showSidebar(template.evaluate().setTitle(record.name || 'Record'));
}

The HTML (Inspector.html)

<!-- Each header/value pair becomes a term/definition in a list.
     The <? ... ?> tags are Apps Script scriptlets, evaluated on the
     server before the HTML is sent to the sidebar. -->
<dl>
  <? for (const [field, value] of Object.entries(row)) { ?>
    <!-- <?= ?> prints an escaped value, so sheet content cannot
         break the page or inject markup. -->
    <dt><?= field ?></dt>
    <dd><?= value ?></dd>
  <? } ?>
</dl>

How it works

  1. onSelectionChange is a simple trigger that Apps Script runs every time the user moves their selection in the spreadsheet.
  2. It checks the sheet name and bails out immediately unless the selection is on the Clients sheet, so the inspector never pops up elsewhere.
  3. showInspector skips the header row, then reads two ranges: the header row and the selected row, each across every used column.
  4. It zips the headers and the row values together into one object — keys are column names, values are the cell contents for that row.
  5. It loads the Inspector HTML template, assigns the record object to template.row, and evaluates it. The scriptlet in the template loops over the object and emits a <dt>/<dd> pair for every field.
  6. The evaluated HTML is shown in a sidebar titled with the record’s name.

Example run

Say the Clients sheet has this row, spread across many columns:

nameemailstatusplanrenewalDateowner
Acme Co[email protected]activeStudio Pro2025-11-01Priya

Click any cell in that row and the sidebar opens showing the record stacked vertically, easy to read at a glance:

Acme Co
─────────────
name          Acme Co
email         [email protected]
status        active
plan          Studio Pro
renewalDate   2025-11-01
owner         Priya

Move the cursor to the next row and the sidebar refreshes instantly to that client — no clicking, no scrolling sideways.

Run it

There is nothing to schedule. onSelectionChange is a simple trigger that runs automatically.

  1. Save the project with both files: the script and the Inspector HTML file.
  2. Open the spreadsheet and click a cell in the Clients sheet.
  3. The first time, approve the authorisation prompt — after that the sidebar appears on every selection.

Watch out for

  • Simple triggers like onSelectionChange cannot call services that require authorisation in some contexts and have a tight 30-second runtime budget. Reading one row is well within that, but do not add slow work here.
  • The sidebar reopens on every selection change, which can feel busy if you are dragging across many cells. It is fine for click-to-inspect; it is not meant for rapid navigation.
  • onSelectionChange only fires for the user who has the sheet open in their browser — it does not run for collaborators viewing elsewhere, and it does not work in the mobile Sheets app.
  • Every value is rendered as text. Dates and numbers appear in their raw stored form, which may differ from the cell’s display formatting.
  • The sidebar title uses the name column. If your sheet has no name column it falls back to a generic title — rename the column or adjust the fallback in showInspector.

Related