You paste a script for a client. They hit Run. Google shows “See, edit, create, and delete all of your Google Sheets spreadsheets.” They bounce. Not because your code is wrong — because the authorize screen looks like a blank check.
Fewer scopes → fewer scary sentences → more people clicking Allow.
TL;DR
- Prefer container-bound scripts when the workbook is the product
- Declare only the scopes you need in
appsscript.json→oauthScopes - Drop “all spreadsheets / all Drive files” if you only touch this file
- Re-auth after trimming; stale grants don’t shrink themselves
- Treat the authorize screen as part of the handoff, not an afterthought
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.
Bound vs standalone (why it matters)
| Bound to the Sheet | Standalone / library | |
|---|---|---|
| Default mental model | “This file’s tools” | “A project that can roam” |
| Typical scopes | Often narrower in practice | Easy to accumulate Drive/Sheets-wide |
| Client fear | Lower | Higher when copy-pasting |
Bound scripts still request scopes — but when the UI says the project lives in this spreadsheet, the ask feels local. Standalone projects that open arbitrary spreadsheet IDs often pull spreadsheets or drive scopes that read like full account access.
If the job only ever needs one workbook, ship it bound (or open by id with the tightest scope that still works).
Why “see and manage all your spreadsheets” scares people
That phrase is Google translating a broad Sheets scope into human. Clients hear:
- Can this read every budget sheet I own?
- Can it delete Finance?
Even when your code only calls SpreadsheetApp.getActive(). The authorize screen doesn’t show your intentions — it shows the maximum the granted scope allows.
Bootstrapper empathy: your reputation rides on that dialog as much as on your onOpen menu.
What not to do
- Don’t enable every Advanced Service “just in case”
- Don’t copy an old
appsscript.jsonfrom a different product - Don’t ask for full Drive when
drive.file(files the app creates/opens) is enough - Don’t tell clients “just click Allow” without showing them the screen first
Trim with appsscript.json
In the Apps Script editor: Project Settings → Show appsscript.json manifest.
Example — a bound sheet tool that reads/writes the active spreadsheet and sends one email:
{
"timeZone": "America/New_York",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets.currentonly",
"https://www.googleapis.com/auth/script.send_mail"
]
}
Notes:
spreadsheets.currentonly— active/bound spreadsheet only (when it fits your calls)- Prefer it over full
https://www.googleapis.com/auth/spreadsheetsif you never open other IDs - Add Drive scopes only when you truly touch Drive files; prefer the narrowest Drive variant that matches your API usage
- Gmail vs
script.send_mail— pick the one your send path actually needs
After editing the manifest, run a function that exercises each API. Google will re-prompt if the grant set changed.
Detect what you’re actually using
function probeScopes_() {
// Touch only what production touches — each call teaches the auth screen.
var ss = SpreadsheetApp.getActive();
ss.getName();
// If you don't need DriveApp, don't call it here "for testing."
}
If a call fails with an auth error after a trim, you cut too far — add back one scope, not the kitchen sink.
Authorize-screen hygiene for handoffs
- Run it yourself in an account that hasn’t authorized the project yet (incognito / alt user).
- Screenshot the dialog and put it in the handoff note: “You’ll see X and Y — that’s because we write this sheet and email the digest.”
- Name the project something the client recognizes (
Acme Intake Bot), notUntitled project. - One project per job when possible — shared libraries with max scopes poison every consumer.
- Document why each scope exists in a one-line README tab.
Scope Why
spreadsheets.currentonly Read/write this workbook
script.send_mail Nightly exception digest
Failure modes worth naming
- Scope creep from a library — pinning a library that requested Drive-wide access upgrades the host project’s ask
- UrlFetch to Google APIs — may need explicit scopes even if SpreadsheetApp “already worked”
- Old deployments — Web apps keep the scopes from the deployed version; redeploy after trims
- Test users / unverified apps — scary screens get scarier; fewer scopes won’t skip verification, but they reduce bounce rate while you sort branding
Practical before/after
Before (copied from an old standalone bot):
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/script.external_request"
]
After (bound digest that only touches this file + sends mail):
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets.currentonly",
"https://www.googleapis.com/auth/script.send_mail"
]
Same job, calmer Allow screen. If you later open another spreadsheet by id, you’ll feel the auth error — that’s the signal to widen one scope, intentionally.
Soft Co-Pilot note
NitroGAS stays free for everyday editing; Co-Pilot can help draft a minimal oauthScopes list from “what APIs does this file call?” — you still verify the authorize screen with a clean account before the client sees it. Optional passes when you want the assist.
Closing checklist
- Manifest visible;
oauthScopesexplicit - No Drive/Sheets-wide scope unless the product truly needs it
- Bound script when the workbook is the boundary
- Fresh-account dry-run of the Allow screen
- Handoff note names each scope in one line
Happy Coding!


