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
.gsfunction viagoogle.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!


