Apps Script example · 9 min read

Domain Restrict Web App: Copy-Paste Apps Script Pattern

Working domain restrict web app example in Apps Script—copy-paste code, common mistakes, and when to get it built professionally.

HtmlServiceSecurityDomain allowlist

Deployment access settings are coarse. Adding an allowlist of email domains in doGet gives a second gate for internal tools.

isAllowedDomain_ compares the substring after @ against ALLOWED_DOMAINS case-insensitively.

saveNote calls requireDomain_ before appendRow so google.script.run cannot bypass the HTML gate.

Combine with Execute as: User accessing and domain-only deployment when everyone is on Google Workspace.

Need this built? Hire a Google Apps Script developer →

Sheet / project setup

ItemValuePurpose
ALLOWED_DOMAINSexample.com, example.orgPermit list
App.htmlUIOnly rendered for allowed users
NotesA–CTimestamp, email, note text
Deploy accessAnyone in domain / GoogleFirst gate

What this script does

doGet shows Access denied for wrong domains; allowed users receive App.html.

requireDomain_ centralizes the check for every write endpoint.

Prerequisites

Web app deployment and an App.html file; Users signed into Google.

  • Correct ALLOWED_DOMAINS
  • Execute as user accessing
  • Notes sheet for the sample write

Walkthrough

Open the URL with a company account and a personal Gmail; confirm deny vs App.html. Submit a note and verify Notes rows.

Edge cases

Subdomains like mail.example.com are different domains — list them explicitly or parse carefully.

  • Empty email when signed out
  • Aliases: still same domain string after @

How to test

Use two test accounts on different domains; attempt saveNote from the browser console on a denied session.

Hardening for production

Also check group membership via Admin Directory for finer roles.

Variations

Allowlist exact emails instead of domains for vendor contractors.

Full code: doGet() + saveNote()

Set ALLOWED_DOMAINS, add App.html, deploy as user-accessing, then verify deny/allow with two accounts.

/**
 * Reject web app access unless the active user's email domain matches.
 */
const ALLOWED_DOMAINS = ["example.com", "example.org"];

function doGet(e) {
  const email = Session.getActiveUser().getEmail();
  if (!email || !isAllowedDomain_(email)) {
    return HtmlService.createHtmlOutput(
      "<h1>Access denied</h1><p>Use your company Google account.</p>");
  }
  const html = HtmlService.createHtmlOutputFromFile("App");
  html.setTitle("Internal tools");
  return html;
}

function isAllowedDomain_(email) {
  const domain = String(email).split("@")[1] || "";
  return ALLOWED_DOMAINS.some(function (d) {
    return d.toLowerCase() === domain.toLowerCase();
  });
}

function requireDomain_() {
  const email = Session.getActiveUser().getEmail();
  if (!isAllowedDomain_(email)) {
    throw new Error("Forbidden domain: " + email);
  }
  return email;
}

/** Example mutating endpoint */
function saveNote(text) {
  requireDomain_();
  const sheet = SpreadsheetApp.getActive().getSheetByName("Notes");
  sheet.appendRow([new Date(), Session.getActiveUser().getEmail(), text]);
  return { ok: true };
}
  1. Line 4: Company domains permitted to load the app.
  2. Line 8: Compares email domain case-insensitively.
  3. Line 10: HTML returned to blocked visitors.
  4. Line 24: Shared guard for mutating server calls.
  5. Line 33: Example write path that re-checks domain.
  6. Line 36: Stores note only after the domain guard passes.

Deploy this example

  1. 01

    Open Apps Script

    In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.

  2. 02

    Paste and save

    Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.

  3. 03

    Authorize once

    Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.

  4. 04

    Add the trigger

    Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.

Before you run: domain gate

  • 1ALLOWED_DOMAINS lists every corporate domain you use
  • 2App.html present
  • 3Deployment uses User accessing
  • 4Denied UX tested with an outside account
  • 5All google.script.run write methods call requireDomain_
  • 6Notes sheet exists for the sample

Frequently asked questions

It helps, but server-side checks stop mistaken Anyone access and protect API-like endpoints.

Add both domains to ALLOWED_DOMAINS as in the sample.

Treat them as distinct strings unless you normalize consumer aliases deliberately.

Not for Session.getActiveUser — that is server-side. Still never trust a client-supplied email parameter.

getActiveUser may be blank for visitors. Domain gating needs execute-as-user.

Append to an AccessLog sheet inside the deny branch with timestamp and email (if any).

Yes — after domain checks, call Admin Directory Groups.members to require group membership.

Related examples

Want this wired into your real workflow?

I adapt these patterns to your Sheet structure, APIs, and triggers — deployed in your Google account. Fixed-scope quotes from $500 · free 30-min consult · quote within 24 hours.