appscript.dev
Automation Intermediate Sheets

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.html file in that project holding the step markup and navigation.
  • A Projects spreadsheet (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

  1. showWizard loads Wizard.html and opens it as a modal dialog sized 400 by 300 over the active Sheet.
  2. Wizard.html holds every step as a <div>. Step 1 is visible; later steps carry style="display:none" so they start hidden.
  3. Clicking Next calls next(s), which hides the current step and reveals the next by toggling display. No screens are unloaded — the inputs you have already filled stay in the DOM.
  4. Because earlier steps stay in the DOM, the final step can still read their values. submit() collects every field into one object.
  5. 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.
  6. createProject guards against empty input, then appends a row to the Projects sheet with a planning status 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:

ClientProjectStatusCreated
Acme StudiosSpring rebrandplanning2025-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 inside next() before advancing if a step is mandatory.
  • Steps are numbered step1, step2, … and next() assumes they are consecutive. Skipping a number breaks the advance.
  • google.script.run is asynchronous. Without withSuccessHandler the dialog would close before the row is written; keep the handler so the close waits for the server.
  • An error thrown in createProject does not surface in the dialog on its own. Add withFailureHandler to show the user what went wrong instead of a silent no-op.

Related