Authorize and scope a script correctly
Understand OAuth scopes and consent screens for Northwind's Apps Script projects.
Published Jul 31, 2025
Every Apps Script that touches a Google service runs on someone’s behalf, and
that someone has to grant permission first. The permissions a script can use
are described by OAuth scopes — short URLs like
https://www.googleapis.com/auth/spreadsheets that name exactly one capability.
The consent screen a user sees on first run is just a human-readable list of
those scopes.
Most developers never think about scopes until something goes wrong: a script suddenly asks for far more access than it seems to need, a Workspace admin blocks it, or a public web app gets stuck in Google’s verification queue. All of those problems trace back to the same root cause — the script is requesting broader scopes than the job actually requires. Getting scopes right keeps users trusting, admins happy, and reviews fast.
Scopes are inferred
You do not declare scopes by hand in normal development. Apps Script scans your code before each authorisation, works out which services you call, and assembles the scope list automatically.
- Calling
SpreadsheetAppadds the Sheets scope. - Calling
GmailApp.sendEmailadds the Gmail send scope. - Calling
GmailApp.searchadds the much broader Gmail read scope.
The inference is conservative — it picks the scope that covers everything the class might do, not the narrowest one for the specific method. That is why a single innocent-looking line can widen your consent screen dramatically.
See what was inferred
You cannot fix what you cannot see, so make the manifest visible first.
- Editor → Project Settings → tick Show “appsscript.json” manifest file in editor → reload the editor.
- Open
appsscript.json. TheoauthScopesarray is the exact list Apps Script inferred from your current code.
Check this array whenever you add a new API call. If a scope appears that you do not recognise, trace it back to the line that introduced it before you ship.
Pin scopes for clarity
Once you know the scopes you actually need, you can list them explicitly in the manifest. This pins the consent screen to a known set instead of letting it drift as the code changes.
{
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/gmail.send"
]
}
Pinning has two benefits. It makes the consent screen predictable for users and reviewers, and it turns an accidental scope expansion into a visible error — if your code starts needing a scope you did not list, the project fails to authorise instead of silently widening access.
Choose the narrowest scope
Many services offer several scopes of increasing power. Reach for the smallest one that does the job.
| Goal | Narrow scope | Broad scope to avoid |
|---|---|---|
| Send mail only | gmail.send | gmail.modify, full Gmail |
| Read one known file | drive.file | drive.readonly, full Drive |
| Edit the bound spreadsheet | spreadsheets.currentonly | spreadsheets |
| Read a public calendar | calendar.readonly | calendar |
drive.file is especially useful — it grants access only to files the script
itself created or the user explicitly picked, so it never triggers a full-Drive
consent prompt.
Why narrow scopes matter
The cost of an over-broad scope is not theoretical. It shows up in three places.
- User trust. A script that asks to “see, edit, and delete all your Drive files” gets abandoned at the consent screen. One that asks for a single file does not.
- Admin policy. Workspace admins can allow-list scopes for the whole domain. A script requesting a blocked broad scope simply will not install.
- Verification. Public web apps and add-ons using sensitive or restricted scopes must pass Google’s OAuth verification. Fewer and narrower scopes mean a shorter, cheaper review — broad scopes can require a security assessment.
Common mistakes
- Calling a one-off convenience API that pulls in
drive.readonly, then needing full-Drive consent on every install for the rest of the project’s life. - Using
GmailApp.searchto find one message when a saved query andgmail.sendwould do — search forces the full read scope. - Leaving an old API call in the codebase after a refactor. The scope it required stays inferred until the dead line is deleted.
- Hand-editing
oauthScopesto remove a scope your code still needs. The project will fail to authorise; remove the offending call instead. - Forgetting that adding any new scope re-prompts every existing user for consent — plan scope changes, do not let them happen by accident.