Apps Script web apps can run as the accessing user so Session.getActiveUser().getEmail() returns a real identity.
This pattern looks up the email on a Users sheet for role and display name, then fills an HtmlService template.
Deploy with Execute as: User accessing the web app. If you execute as you (developer), getActiveUser often returns empty for visitors.
getServerTime shows a google.script.run endpoint that still sees the same active user after load.
Need this built? Hire a Google Apps Script developer →
Sheet / project setup
| Sheet / File | Field | Purpose |
|---|---|---|
| Users | A Email | Login key |
| Users | B Name | Display name |
| Users | C Role | viewer / editor / admin |
| Index.html | template | Uses <?= email ?> etc. |
| Deploy | Execute as user | Required for getActiveUser |
What this script does
doGet() reads the active user email, loads profile defaults, and evaluates Index.html with template fields.
Unknown emails still enter as role viewer so first-time users are not hard-blocked.
Prerequisites
HtmlService file Index.html and a Users sheet; web app deployed to users with Google accounts.
- Execute as: User accessing
- Access: your domain or anyone with Google
- Users sheet maintained
Walkthrough
Deploy, open the URL while signed in, confirm the email renders. Call getServerTime from client JS to verify identity on server calls.
Edge cases
Incognito without Google sign-in yields empty email — show a sign-in message as in the sample.
- getEffectiveUser differs when execute-as is developer
- Template escaping: use <?!= for HTML, <?= for escaped text
How to test
Add yourself to Users as admin; open as another account that is only viewer; confirm role text changes.
Hardening for production
Do not trust client-passed emails — always re-read Session on the server for mutations.
Variations
Combine with Admin Directory to auto-provision names from Workspace.
Full code: doGet() + getServerTime()
Add Index.html, deploy as a web app executing as the accessing user, then open the /exec URL signed in.
/**
* Web app that shows different HTML based on the signed-in Google user.
* Deploy: Execute as User accessing / Who has access: domain or anyone with Google.
*/
function doGet() {
const email = Session.getActiveUser().getEmail();
if (!email) {
return HtmlService.createHtmlOutput(
"<p>Sign in with Google to continue.</p>");
}
const profile = lookupProfile_(email);
const template = HtmlService.createTemplateFromFile("Index");
template.email = email;
template.role = profile.role;
template.displayName = profile.name;
return template.evaluate()
.setTitle("Workspace Portal")
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function lookupProfile_(email) {
const sheet = SpreadsheetApp.getActive().getSheetByName("Users");
const data = sheet.getDataRange().getValues();
for (let i = 1; i < data.length; i++) {
if (String(data[i][0]).toLowerCase() === email.toLowerCase()) {
return { email: data[i][0], name: data[i][1], role: data[i][2] };
}
}
return { email: email, name: email.split("@")[0], role: "viewer" };
}
function getServerTime() {
// callable from google.script.run after auth
return {
email: Session.getActiveUser().getEmail(),
now: new Date().toISOString(),
};
}- Line 6: Returns the visitor when Execute as is User accessing.
- Line 13: Loads Index.html as a template.
- Line 12: Maps email to role from the Users sheet.
- Line 30: Default for emails not yet provisioned.
- Line 17: Renders HTML for the response.
- Line 33: Example google.script.run server function.
- Line 37: Returns a timezone-safe timestamp to the client.
Deploy this example
- 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.
- 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.
- 03
Authorize once
Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.
- 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: auth web app
- 1Index.html exists in the project
- 2Users sheet has Email / Name / Role headers
- 3Deployment Execute as = User accessing the web app
- 4Test with two Google accounts
- 5Never authorize sensitive writes based on client input alone
- 6Confirm empty-email UX for signed-out visitors
Frequently asked questions
Usually the deployment executes as the developer. Switch to User accessing the web app and create a new version.
Effective user is whose authority runs the script. Active user is who is signed in. For user-auth UX you want active under execute-as-user.
Prefer getActiveUser().getEmail(). Older Session helpers are less explicit about active vs effective.
Yes if access is Anyone with Google Account. Domain-only deployments exclude consumer accounts.
Re-check role from Users (or Directory) inside every mutating server function.
Yes via separate OAuth2 library flows; this example only covers Google sign-in identity.
Only if embedding in an iframe. Tighten for standalone portals.