“Execute as me” vs “as the user” — pick the web app identity that won’t haunt you

TL;DR — Apps Script web app Execute as me vs as the user: who the active user is in doGet/doPost, when writes land under the wrong account, and a simple decision tree for bootstrappers.

You deploy a doGet / doPost web app, share the URL, and suddenly writes land under the wrong account — or the script can’t see the sheet at all. The Execute as setting is the quiet culprit. Here’s how we pick it for bootstrapper tools.

TL;DR

  • Execute as: Me — the script always runs as the deploying account. Good for internal webhooks, shared ops sheets the owner can write, and APIs that shouldn’t depend on who clicked the link.
  • Execute as: User accessing the web app — the script runs as whoever opened the URL (after they authorize). Good for teammate-facing forms that must write their Drive/Sheets.
  • Inside the handler, Session.getActiveUser() / getEffectiveUser() tell you who Google thinks is acting — log them once while you’re debugging auth.
  • Who has access (Anyone / Anyone with Google account / domain) is a separate dial from Execute as.
  • Soft tip: when the auth model feels fuzzy, Co-Pilot can leave a plain-English note next to the deploy settings.

Using the Apps Script editor a lot? NitroGAS drops free themes & snippets right into script.google.com — optional Co-Pilot when you want a boost.

The two identities

Setting Runs as Typical fit
Me The account that deployed Nightly webhook, Zapier in, shared ops sheet owned by you
User accessing the web app The person who hits the URL “Submit my row” forms, per-user Drive folders

Wrong combo symptoms:

  • Sheet writes appear under your account when a teammate submitted the form (you wanted User).
  • SpreadsheetApp.openById fails for callers who don’t have access (you chose User but the sheet isn’t shared to them — or you needed Me with a service sheet).
  • “Authorization required” loops for anonymous callers (access is too tight for the audience).
function doPost(e) { var active = Session.getActiveUser().getEmail(); var effective = Session.getEffectiveUser().getEmail(); console.log('active=', active, 'effective=', effective); // With Execute as Me: effective is usually the deployer. // With Execute as User: both trend toward the caller (after they authorize). try { var body = e && e.postData && e.postData.contents ? JSON.parse(e.postData.contents) : {}; // ... write using the identity you intended ... return jsonOk({ active: active, effective: effective }); } catch (err) { return jsonErr(err.message || String(err), 500); } }

(jsonOk / jsonErr are small ContentService helpers — grab them from the code snippets library.)

Decision tree for bootstrappers

  1. Is this an unattended inbound webhook? (Stripe, GitHub, a cron elsewhere) → Execute as Me, restrict the URL (secret token in the path/header), keep the sheet owned by the deployer.
  2. Must each human write into their own files? → Execute as User, access = Google account (or domain), and document the one-time authorize click.
  3. Shared team sheet, humans click a link? → Usually Me so one service account of record owns the data; gate the UI with a simple shared secret or Apps Script-side allowlist of emails if needed.
  4. Still unsure? Deploy a doGet that only returns jsonOk({ active, effective }), hit it while logged in as you and as a teammate, and read the emails. That five-minute experiment beats guessing.

Access vs Execute as

  • Execute as = whose permissions the code uses.
  • Who has access = who is allowed to invoke the URL.
  • Anyone + Execute as Me is powerful (and easy to leak). Prefer a token check inside doPost even when the link is “Anyone.”

Deploy checklist

  • Execute as chosen deliberately (table above)
  • Access matches the audience
  • New deployment (Manage deployments → Edit → New version) after code changes that matter
  • One smoke doGet that logs active/effective
  • Sheet/Drive sharing matches the identity (Me needs deployer access; User needs each caller’s access)

Soft CTA

When you’re explaining this to a non-technical teammate, NitroGAS Co-Pilot can leave a plain-English comment above doGet/doPost that says which Execute-as you picked and why. Happy Coding.