Build a tiny HtmlService sidebar your teammates will actually click

TL;DR — Build a bootstrapper HtmlService sidebar: custom menu, one primary action, status text via includeHtml — a control surface non-technical teammates will click.

Non-technical teammates will not open Apps Script. They will click a menu item that opens a sidebar with one obvious button and a status line that speaks English. Here's a tiny HtmlService pattern — not a full app — that we reuse as a control surface on bootstrap sheets.

TL;DR

  • Custom menu → showSidebar() → HtmlService HTML file.
  • One primary button calling a .gs function via google.script.run.
  • Status text updated on success/failure (escape user/sheet strings).
  • Use includeHtml (or equivalent) to keep CSS/partials out of a 200-line soup.
  • Soft path: NitroGAS Co-Pilot when you want help polishing the HTML/CSS — the free extension still covers the Apps Script scaffolding.

Using the Apps Script editor a lot? NitroGAS drops free themes & snippets right into script.google.com — optional Co-Pilot when you want a boost.

Why a sidebar instead of Browser.msgBox?

Dialogs block; sidebars stay open while people glance at the sheet. One primary action beats a toolbar of mystery icons. Status text ("Queued 12 rows") beats Logger.log they will never see.

Minimal .gs wiring

function onOpen() { SpreadsheetApp.getUi() .createMenu('Bootstrap') .addItem('Open control panel', 'showSidebar') .addToUi(); } function showSidebar() { var html = HtmlService.createTemplateFromFile('Sidebar') .evaluate() .setTitle('Bootstrap') .setWidth(320); SpreadsheetApp.getUi().showSidebar(html); } function include(filename) { return HtmlService.createHtmlOutputFromFile(filename).getContent(); } function runPrimaryAction() { // real work — claim queue row, sync, etc. return { ok: true, message: 'Queued 12 rows for sync.' }; }

Sidebar.html sketch

<!DOCTYPE html> <html> <head> <base target="_top" /> <?!= include('SidebarStyles'); ?> </head> <body> <h1>Sync queue</h1> <p id="status">Ready.</p> <button id="go" type="button">Run sync</button> <script> function setStatus(text) { document.getElementById('status').textContent = text; } document.getElementById('go').addEventListener('click', function () { setStatus('Working…'); google.script.run .withSuccessHandler(function (res) { setStatus((res && res.message) || 'Done.'); }) .withFailureHandler(function (err) { setStatus('Error: ' + (err && err.message ? err.message : err)); }) .runPrimaryAction(); }); </script> </body> </html>

Escape anything from the sheet

If you inject names or messages into HTML (not just textContent), run them through escapeHtml first. Prefer textContent in client JS when you can — it stays text automatically.

Keep the surface tiny

  • One primary button. Secondary actions go in a details/advanced section or a second menu item.
  • Status line in plain language ("Paused — resume from the Bootstrap menu").
  • Don't build auth, routing, or a design system into v1.

Wrap-up

A sidebar teammates click is a feature. A 15-file SPA inside Apps Script is a second product. Start tiny; grow only when the button earns it.

NitroGAS includes HtmlService helpers like includeHtml; Co-Pilot is there if you want a hand on the markup polish.

Happy Coding!