appscript.dev
Automation Beginner

Deploy Apps Script as a public web app

Serve a page anyone can visit — Northwind's public status page or microsite from one script.

Published Jun 25, 2025

Northwind needs a public page that lives outside the marketing site — a status board during an outage, a small microsite for an event, a one-off landing page for a partner. Spinning up a server, a domain, and a deploy pipeline for that is overkill. Apps Script can serve HTML at a stable Google URL, with no hosting bill and no infrastructure to mind.

This script is the smallest possible public web app — a single doGet that returns a page. Once you understand the deploy steps, the same pattern carries the bigger web-apps in this hub: quizzes, registration forms, webhook receivers. Get this one live first; the rest is just more HTML.

What you’ll need

  • A Google account that can create Apps Script projects.
  • Two minutes for the first deploy (most of it is clicking through the authorisation prompt).
  • Nothing else — no Sheets, no API keys, no external services.

The script

// The page title shown in the browser tab. Pull values like this out of
// the HTML so the markup stays clean and the config sits in one place.
const PAGE_TITLE = 'Northwind Studios';

// The body markup. For anything longer than a paragraph, switch to
// HtmlService.createHtmlOutputFromFile and put it in a .html file.
const PAGE_BODY =
  '<h1>Northwind Studios</h1>' +
  '<p>We make things. Come back soon for the new site.</p>';

/**
 * Entry point for the public web app. Apps Script calls doGet whenever
 * someone visits the deployment URL — the return value is the response.
 *
 * @param {GoogleAppsScript.Events.DoGet} _e Query parameters, ignored here.
 * @return {GoogleAppsScript.HTML.HtmlOutput} The page to serve.
 */
function doGet(_e) {
  // HtmlOutput is Apps Script's response object for HTML. setTitle controls
  // the browser tab; the body is whatever string you pass to createHtmlOutput.
  return HtmlService.createHtmlOutput(PAGE_BODY)
    .setTitle(PAGE_TITLE)
    .addMetaTag('viewport', 'width=device-width, initial-scale=1');
}

How it works

  1. doGet is the magic name. When the web app is deployed and someone visits the /exec URL, Apps Script invokes this function and returns whatever it produces.
  2. HtmlService.createHtmlOutput wraps a string of HTML in an HtmlOutput object — the type Apps Script knows how to send as a response.
  3. setTitle sets the <title> element shown in the browser tab; the meta viewport tag makes the page render sensibly on phones.
  4. The _e parameter holds query-string values like ?name=ada. We ignore it here because this page does not vary. The webhook and quiz articles in this hub show what to do with it.

Example run

Deploy the script and visit the /exec URL. The browser shows:

ElementValue
Tab titleNorthwind Studios
HeadingNorthwind Studios
ParagraphWe make things. Come back soon for the new site.
URL shapehttps://script.google.com/macros/s/AKfy.../exec

The URL is long but stable. Bookmark it, share it, redirect a friendly DNS name to it — it stays the same across re-deploys.

Deploy it

  1. In the Apps Script editor, click DeployNew deployment.
  2. Click the gear icon and choose Web app as the type.
  3. Set Execute as to Me so the script runs under your account.
  4. Set Who has access to Anyone for a fully public page, or Anyone with a Google account if you want a sign-in wall.
  5. Click Deploy, approve the authorisation prompt, and copy the Web app URL ending in /exec. That is your public address.
  6. For development, use the DeployTest deployments dialog and copy the /dev URL — it always serves the head version, so you can refresh after every edit without re-deploying.

Watch out for

  • The /exec URL serves the deployed version, not the head version. Edits in the editor do not appear publicly until you run DeployManage deployments → pencil icon → New version. The /dev URL is the opposite: always head, always private to editors.
  • Anyone-access still goes through script.google.com. That is fine for most uses; if you need a clean custom domain, put a redirect or reverse proxy in front of it.
  • HTML strings inside .js files get unwieldy fast. For more than a few lines, move the markup into a .html file and use HtmlService.createHtmlOutputFromFile. The quiz and registration articles in this hub take that approach.
  • Web apps cannot read request headers or cookies the way a normal server can. If you need session state, store it keyed by a token in Script Properties or a Sheet — the expiring-download article shows the pattern.
  • The first deploy asks for a long list of scopes — granting them is a one-off. Subsequent re-deploys only re-prompt if you have changed which services the script touches (added Gmail, added Drive). If a re-prompt appears unexpectedly, check the imports — something has been pulled in by accident.
  • The Apps Script editor distinguishes between head version (whatever you see in the editor) and deployed version (whatever was live as of the last deploy). When debugging, always confirm which one you are testing. Visiting the /exec URL but expecting head-version behaviour is the most common cause of “my change isn’t showing up”.
  • Once you have a real release, see Version and roll back a web app safely before you publish a second version on top of the first.

Related