Build a multi-step modal dialog wizard
Walk Northwind users through a complex task — collect inputs across screens.
Published Jul 11, 2025
Setting up a new project at Northwind takes a fair few details — client, project name, budget, owner. Pile them all into one modal dialog and you get an intimidating wall of fields that people rush through or abandon.
A wizard breaks that wall into steps. Each screen asks for one thing, a Next button moves forward, and only the final screen submits everything at once. This example keeps two steps to show the pattern cleanly — client, then project — but the same structure scales to as many screens as a task needs. The modal lives inside a Sheet, and finishing the wizard appends a row.
What you’ll need
- A Google Sheet to host the wizard, with a bound Apps Script project.
- A
Wizard.htmlfile in that project holding the step markup and navigation. - A
Projectsspreadsheet (or tab) where finished projects are appended.
The HTML (Wizard.html)
<!-- Step 1 is visible on load; later steps start hidden. -->
<div id="step1">
<h2>Step 1: Client</h2>
<input id="client" placeholder="Client name">
<button onclick="next(1)">Next</button>
</div>
<div id="step2" style="display:none">
<h2>Step 2: Project</h2>
<input id="project" placeholder="Project name">
<button onclick="submit()">Finish</button>
</div>
<script>
/**
* Hides the current step and shows the next one.
* @param {number} s - The step number to advance from.
*/
function next(s) {
document.getElementById('step' + s).style.display = 'none';
document.getElementById('step' + (s + 1)).style.display = '';
}
/**
* Gathers every field and sends it to the server in one call,
* closing the dialog when the row has been written.
*/
function submit() {
google.script.run
.withSuccessHandler(google.script.host.close)
.createProject({
client: document.getElementById('client').value,
project: document.getElementById('project').value,
});
}
</script>
The Apps Script
// Spreadsheet that collects finished projects.
const PROJECTS_SHEET_ID = '1abcProjectsId';
/**
* Opens the wizard as a modal dialog over the active spreadsheet.
*/
function showWizard() {
const html = HtmlService.createHtmlOutputFromFile('Wizard')
.setWidth(400)
.setHeight(300);
SpreadsheetApp.getUi()
.showModalDialog(html, 'New Northwind project');
}
/**
* Receives the wizard's collected data and appends a project row.
* Called from the client by google.script.run.
*
* @param {{client: string, project: string}} data - Wizard inputs.
*/
function createProject(data) {
// Guard against an empty submission slipping through.
if (!data.client || !data.project) {
throw new Error('Client and project name are both required.');
}
SpreadsheetApp.openById(PROJECTS_SHEET_ID).getSheets()[0]
.appendRow([data.client, data.project, 'planning', new Date()]);
}
How it works
showWizardloadsWizard.htmland opens it as a modal dialog sized 400 by 300 over the active Sheet.Wizard.htmlholds every step as a<div>. Step 1 is visible; later steps carrystyle="display:none"so they start hidden.- Clicking Next calls
next(s), which hides the current step and reveals the next by togglingdisplay. No screens are unloaded — the inputs you have already filled stay in the DOM. - Because earlier steps stay in the DOM, the final step can still read their
values.
submit()collects every field into one object. google.script.run.createProject(data)sends that object to the server in a single call.withSuccessHandler(google.script.host.close)closes the dialog once the server confirms.createProjectguards against empty input, then appends a row to theProjectssheet with aplanningstatus and a timestamp.
Example run
A user opens the wizard and works through it:
- Step 1 — enters
Acme Studios, clicks Next. - Step 2 — enters
Spring rebrand, clicks Finish.
The dialog closes and a row lands in the Projects sheet:
| Client | Project | Status | Created |
|---|---|---|---|
| Acme Studios | Spring rebrand | planning | 2025-07-11 09:42 |
Run it
The wizard is on-demand. Add a menu so anyone using the Sheet can open it:
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Projects')
.addItem('New project…', 'showWizard')
.addToUi();
}
Reload the spreadsheet, then choose Projects > New project… to launch the wizard.
Watch out for
- The wizard collects nothing until Finish. Closing the dialog mid-way loses every entry — there is no draft saved. For a long wizard, post each step to the server as you go.
next()does no validation. A user can click Next with an empty field. Add a check insidenext()before advancing if a step is mandatory.- Steps are numbered
step1,step2, … andnext()assumes they are consecutive. Skipping a number breaks the advance. google.script.runis asynchronous. WithoutwithSuccessHandlerthe dialog would close before the row is written; keep the handler so the close waits for the server.- An error thrown in
createProjectdoes not surface in the dialog on its own. AddwithFailureHandlerto show the user what went wrong instead of a silent no-op.
Related
Build a branded approval interface
Approve Northwind requests through a custom UI — clients click, decision is logged.
Updated Nov 8, 2025
Build an interactive quiz or assessment app
Run Northwind tests with scoring and feedback — questions in a Sheet, results in another.
Updated Nov 4, 2025
Build a multi-page web app with routing
Structure a real Northwind app across views — query-param routing, shared layout.
Updated Oct 31, 2025
Build a form-to-PDF web service
Convert Northwind form submissions to PDFs on the fly — POST in, PDF out.
Updated Oct 27, 2025
Build an expiring secure-download generator
Issue time-limited Northwind links via a web app — token in URL, server-side check.
Updated Oct 23, 2025