Local Apps Script development with clasp
Set up a local Apps Script project with clasp 3.4.1 and Git, snapshot a bound script, review the upload list, and push a reviewed revision.
Assemble a local bound-script project, predict which changes reach Apps Script, review and upload an approved revision, and recover without overwriting another maintainer's work.
Develop the request summary on your computer, record a reviewable revision in Git, and upload only its Apps Script source and manifest to a disposable test project. Then edit the local code and the spreadsheet separately to see how each change reaches the script.
The summary reads two fictional requests and logs counts. It does not write cells, send messages, install triggers, or need a deployment. Use this short program to check which files you upload and which project receives them. A correct count does not prove that you uploaded the intended files to the intended project.
Know which copy you are changing
A local folder is not an Apps Script runtime. Node.js runs clasp on your computer, and Google runs the uploaded .gs code on Apps Script. Installing Node.js and clasp does not make SpreadsheetApp available on your computer.
| State | What changes it | What does not change it |
|---|---|---|
| Local working files | Your editor, a file copy, or a clasp pull into that directory | Editing cells in Sheets |
| Local Git history | An explicit Git commit | Saving a file or running clasp |
| Remote project head | Saving in the Apps Script editor or a clasp push | A local commit, branch, or tag |
| Numbered Apps Script version | Creating an immutable snapshot of remote head | Editing local files after the snapshot |
| Versioned deployment | Creating a deployment or updating its selected version | A later push to head by itself |
| Spreadsheet contents | A person's cell edit or code that writes cells | Committing or uploading this read-only summary |
Remote head matters even without a public deployment. Google's updateContent reference says head is used by editor executions and triggers, among other development contexts. A push can therefore change the next scheduled execution of an existing project. Use a new practice spreadsheet with no triggers.
A Git branch does not select a Google account, create a test project, or isolate a remote branch. Two local branches whose .clasp.json files contain the same Script ID still upload to the same project.
Prepare the fictional spreadsheet and its bound script
You need an account authorized to create a practice spreadsheet and edit its bound script, a local editor, Git, and a maintained Node.js release with npm. Use Node.js 22 or 24 for this exercise. The pinned clasp package requires Node.js 20 or newer, but do not stay on a Node.js release that is no longer supported. The terminal examples use a POSIX-style shell, such as macOS Terminal or Git Bash on Windows.
Before you log in with clasp, confirm that the intended account can enable the Google Apps Script API and authorize clasp. On a Google Workspace domain, an administrator can restrict the Apps Script API or third-party OAuth applications even when you can open the Apps Script editor.
Create a new blank Google spreadsheet named
Local request summary - test. Do not copy a production spreadsheet with hidden data or existing automation.Rename its first sheet to
Requests.Download
requests.csv. Paste its contents intoA1, select column A, and choose Data > Split text to columns with Comma as the separator. Verify the four headers inA1:D1and the two request rows inA2:D3.From that spreadsheet, open Extensions > Apps Script. Name the project
Local request summary - test. Leave its default source unchanged for now.Open Project Settings and copy the Script ID into a private local note. Check the browser account and spreadsheet title before copying it. Do not put the ID into the source code or a Git commit.
Request,Owner,Status,Notes
Confirm room booking,Alex,Open,Check accessibility requirements
Prepare agenda,Sam,In progress,Collect topics before Friday
Request, Owner, Status, and Notes are the column names. Alex and Sam are fictional. The script reads the Requests sheet. The CSV only supplies the starting data so that you can recreate the sheet. Uploading a CSV would not import it into Sheets.
Keep these identifiers distinct:
| Identifier | Where you obtain it | What it selects |
|---|---|---|
| Spreadsheet file ID | The spreadsheet URL, between /d/ and /edit | The containing Drive file |
| Script ID | The bound project's Project Settings | The script project clasp reads or replaces |
| Deployment ID | Deploy > Manage deployments, when a deployment exists | A deployment within a script project |
| Google Cloud project ID or number | The associated Cloud project's settings | Cloud configuration, not the spreadsheet or script |
The Script ID is not an access token, but it identifies a real project, so keep it private. Keep real target IDs out of this sample repository. Changing a local scriptId does not rebind a script to another spreadsheet. Instead, it makes clasp address another script.
Creating the bound script through Extensions > Apps Script also attaches it to the right spreadsheet. Do not substitute an old clasp create example with --type sheets --parentId .... In clasp 3.4.1's creation command, the sheets branch creates a new spreadsheet rather than selecting the supplied parent. This chapter uses the already-created bound project.
Assemble the local project before connecting it
Create a fresh directory outside any existing repository or Apps Script checkout. Do not reuse an existing folder:
mkdir local-request-summary
cd local-request-summary
mkdir src fixtures remote-snapshots Download the complete Code.gs, appsscript.json, and requests.csv files from the request-summary example. Save them as src/Code.gs, src/appsscript.json, and fixtures/requests.csv. Download the four local-development configuration files into the new directory's root, preserving their names, including the leading dots. If a browser adds .txt to a download, remove that suffix when saving it.
The two example groups have different jobs. local-request-summary supplies the complete Apps Script program and input. local-development contains only local tooling files. Nothing in it is uploaded to Apps Script.
Your files should be:
local-request-summary/
.claspignore
.gitignore
clasp.example.json
package.json
src/
Code.gs
appsscript.json
fixtures/
requests.csv
remote-snapshots/ The source reads displayed cell values, skips the header, keeps rows with a nonempty first column, and counts the exact status Open. It does not trim or normalize statuses. Use the supplied spelling and two sample rows before experimenting with other inputs.
function logRequestSummary() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Requests');
if (!sheet) throw new Error('Create a sheet named Requests before running this function.');
const values = sheet.getDataRange().getDisplayValues();
if (values.length < 2) throw new Error('Add a header row and at least one request row.');
const rows = values.slice(1).filter((row) => row[0]);
const openCount = rows.filter((row) => row[2] === 'Open').length;
Logger.log('Requests: ' + rows.length + ', open: ' + openCount + '.');
}
{
"timeZone": "Etc/UTC",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets.currentonly"
]
}
Keep the manifest's V8 runtime, Etc/UTC time zone, and spreadsheets.currentonly scope. That scope limits spreadsheet access to the current file, but it still allows writes. The example is read-only because its code makes no write calls. The manifest does not grant clasp permission to manage source files.
Pin the clasp version
{
"name": "book-local-request-summary",
"version": "1.0.0",
"private": true,
"engines": {
"node": ">=22"
},
"scripts": {
"clasp": "clasp --user book-test",
"target": "clasp --user book-test --project .clasp.json --ignore .claspignore"
},
"devDependencies": {
"@google/clasp": "3.4.1"
}
}
This package pins @google/clasp to 3.4.1, so the commands do not depend on whichever global clasp happens to be installed. That version and its Node.js requirement come from the published npm metadata, and the command examples follow its published source revision. Recheck the command help and release notes before upgrading the pin.
npm run clasp -- ... selects the named clasp account book-test. npm run target -- ... also selects the local .clasp.json and .claspignore. The name book-test is a local name for stored credentials. It does not limit what the account can do. The two scripts have no automatic push or deployment hooks.
Before installing, review the package file. Installation downloads dependencies and writes node_modules and a lockfile on your computer. It does not connect to Apps Script. Run these commands from the new directory:
node --version
npm --version
git --version
npm install --include=dev --ignore-scripts
npm run clasp -- --version The expected clasp version is 3.4.1. Stop if the command is missing or reports another version. Do not substitute an unpinned npx download or a global executable. --ignore-scripts skips dependency lifecycle scripts during installation. It does not prevent a later, explicit npm run clasp from running clasp.
The first installation resolves transitive dependencies and creates package-lock.json. The direct clasp pin alone does not freeze that entire dependency graph. Review and commit the generated lockfile. On a later clean checkout, use npm ci --include=dev --ignore-scripts with that committed lockfile. npm documents that ci requires a matching lockfile, does not update it, and replaces an existing node_modules directory.
Separate uploads from version control
**/**
!Code.gs
!appsscript.json
clasp evaluates these patterns relative to rootDir, which .clasp.json sets to src. The first pattern excludes every file, and the two negated patterns admit only Code.gs and appsscript.json. Do not change them to !src/Code.gs, because src is already the upload root. The CSV, npm files, credentials, and snapshots stay outside it.
This file lists what clasp uploads. Git does not read it. If you later add Menu.gs or an HTML file, clasp will not upload it until you update and review the allowlist. Conversely, adding a file to Git does not make clasp upload it.
# Dependencies and generated or temporary output.
node_modules/
/build/
/dist/
/coverage/
/remote-snapshots/
/tmp/
*.log
*.tmp
*.tsbuildinfo
.DS_Store
# Live targets and account credentials are local, not source.
.clasp.json
.clasp.*.json
.clasprc.json
.clasprc.*.json
.env
.env.*
.npmrc
credentials.json
credentials-*.json
client_secret*.json
service-account*.json
*.pem
*.key
The Git rules exclude live target configurations, common credential filenames, local environment files, npm credentials, downloaded dependencies, snapshots, and temporary output. clasp.example.json is a template with a placeholder ID that you can commit, whereas .clasp.json is the ignored live target.
Ignore patterns do not inspect file contents and cannot recognize every possible credential filename. Never copy tokens or production data into an allowed file. Git also does not stop tracking an already-tracked file because you add an ignore rule later. This exercise starts with a clean repository. It is not a procedure for cleaning secrets from an existing history.
Initialize Git and review what will be committed:
git init
git check-ignore -v .clasp.json .clasprc.json credentials.json remote-snapshots/probe.json node_modules/probe.js
git status --short
git add .gitignore .claspignore clasp.example.json package.json package-lock.json src/Code.gs src/appsscript.json fixtures/requests.csv
git diff --cached --stat
git diff --cached git check-ignore should show the matching rule for each of those paths. The probe files need not exist. The staged diff should contain only the listed source, CSV, configuration template, ignore files, and package files. Review the lockfile too. It should not contain private registry credentials or a private filesystem dependency. Do not use git add . to bypass that review.
Once that diff is correct, record the local baseline:
git commit -m "Add read-only request summary baseline"
git tag lesson-baseline This commit and tag affect only local Git history. They do not change anything in Apps Script. No Git remote or repository publication is needed for this exercise.
Select the account and the exact test target
Using your test account, open Apps Script user settings (script.google.com/home/usersettings) and enable Google Apps Script API. Google's API authorization guide explains that this account-level setting permits separately authorized applications to manage script projects. It applies to every such application, not only this example. Each application still needs authorization.
Next, authorize clasp and inspect the selected account:
npm run clasp -- login
npm run clasp -- show-authorized-user The login opens an OAuth flow. Select the account that owns the practice spreadsheet and review the requested permissions before consenting. clasp's project-management permissions are broader than the summary's current-spreadsheet runtime scope.
show-authorized-user reports the selected account and OAuth client information. Check the email locally. Do not paste account output or credential files into a public issue.
The default credential store is ~/.clasprc.json, outside this repository. It contains tokens. Do not copy it into source control. The sample also ignores common in-repository credential names as a second precaution. It does not require a custom OAuth client, service-account key, or a project-specific credential file.
Create the live configuration from the template:
{
"scriptId": "REPLACE_WITH_TEST_SCRIPT_ID",
"rootDir": "src",
"scriptExtensions": [".gs"]
}
cp clasp.example.json .clasp.json In your editor, replace only REPLACE_WITH_TEST_SCRIPT_ID in .clasp.json with the Script ID from the bound test project. Leave clasp.example.json unchanged. Keep rootDir as src. scriptExtensions makes downloaded server files use .gs and limits this local project to that script extension.
Compare the saved ID with Project Settings again. Open the script from the practice spreadsheet to confirm the binding, account, and project title. show-authorized-user shows which account clasp uses, and .clasp.json sets which project it changes. Check both.
The target npm script selects the configuration and ignore file on each command. You can still target another project by editing that ignored configuration or overriding command options. Keep this checkout for the test project rather than swapping test and production IDs.
Save remote head without overwriting local work
Before the first push, save the current remote files in a new, ignored snapshot directory. A clasp pull overwrites local files. It does not merge and does not check Git for uncommitted work. Do not pull straight into the populated src directory.
From the repository root:
mkdir remote-snapshots/before-first-push
cp .clasp.json remote-snapshots/before-first-push/.clasp.json Open the copied configuration and change its rootDir from src to .. Keep its Script ID and scriptExtensions unchanged. Do not edit the root configuration. The copied configuration now makes this new snapshot directory the download destination.
npm run clasp -- --project remote-snapshots/before-first-push/.clasp.json pull This command reads the selected remote head and writes its files into the snapshot directory. It does not upload local source or run the script. The pull implementation can also delete local files when given --deleteUnusedFiles. Do not add that flag or --force here.
Inspect every downloaded file in your editor. For the new project, confirm that you are replacing only its known default source and manifest. If you find another person's code, additional files, or unfamiliar configuration, stop before pushing. Find out whether you selected the wrong project or someone else is working in it. An upload allowlist would not preserve those remote files.
For the default Code.gs and manifest, compare the snapshot with the local program:
git diff --no-index -- remote-snapshots/before-first-push/Code.gs src/Code.gs
git diff --no-index -- remote-snapshots/before-first-push/appsscript.json src/appsscript.json These comparisons do not stage or upload anything. git diff --no-index returns exit status 1 when it finds differences, which is expected here. If the remote project has different filenames, inspect them too, because these two paths may not cover the whole project.
A snapshot copies only the code and its manifest. It does not back up spreadsheet contents, properties, installed triggers, permissions, or deployments. Keep it private. Its copied configuration still points at the same remote project, so never push from a snapshot directory as a shortcut to recovery.
When you actually need clone
clasp clone-script copies an existing script's source into a local directory and writes target configuration. It does not copy the spreadsheet, create a new remote script, or remove other editors' access.
If you start from an existing test script instead of the sample source, clone into a separate empty directory. To inspect that operation without overwriting the exercise checkout, replace TEST_SCRIPT_ID with the verified ID and run this optional command form from the repository root:
mkdir remote-snapshots/clone-study &&
(
cd remote-snapshots/clone-study &&
node ../../node_modules/@google/clasp/build/src/index.js --user book-test --project . clone-script TEST_SCRIPT_ID --rootDir src
) This calls the installed 3.4.1 package's executable. The explicit --project . selects the new directory instead of searching upward for the exercise's existing configuration. The parentheses keep the directory change inside a subshell, so your terminal remains at the repository root afterward. The leading mkdir must succeed, so do not reuse an existing snapshot directory.
The 3.4.1 clone command downloads source into that directory's src and writes its .clasp.json. It does not replace the exercise's working files. Inspect the result without pushing from this snapshot.
With default settings, cloned server files use .js. Review the generated configuration and downloaded filenames together before adopting this chapter's .gs-only allowlist. Do not leave both Code.js and Code.gs representing the same remote file. Clone is a different way to start a project. Skip it for this exercise.
Review the upload and run the baseline
Return to the root configuration and inspect the upload candidates:
npm run target -- show-file-status
git status --short
git diff -- src/Code.gs src/appsscript.json .claspignore The clasp upload list must contain exactly src/Code.gs and src/appsscript.json. Order and display formatting do not matter. fixtures/requests.csv, package.json, node_modules, .clasp.json, credentials, and snapshots must not be upload candidates. show-file-status uses "tracked" for files in clasp's upload list, independently of whether Git tracks them.
The status command lists local files only. It does not check who owns the remote project, approve manifest scopes, confirm that the remote files match, or stop a collaborator from editing it.
Confirm that nobody is editing the test project. When clasp uploads, it replaces all of the remote files. It does not merge your files with files someone else added remotely. --force only skips the manifest confirmation and does not resolve conflicts. Do not use --force or --watch for this push.
npm run target -- push If clasp prompts to overwrite the manifest, compare the already-reviewed manifest and confirm only that intended change. A skipped prompt or an "already up to date" message does not show that the uploaded code runs. In particular, do not rely on the command's change detection to prove that unexpected remote-only files are absent.
After the upload, reopen the Apps Script editor from the practice spreadsheet and inspect Code.gs. In Project Settings, enable Show "appsscript.json" manifest file in editor, return to Editor, and inspect the uploaded manifest. Do not edit the project in the Apps Script editor while you are checking the upload.
Select logRequestSummary and click Run. On the first run, review and approve the current-spreadsheet authorization using the intended account. This runtime authorization is separate from clasp login. Inspect the Execution log, and use Executions if you need the run's status and error details.
For the unchanged sample data, expect:
Requests: 2, open: 1. The script leaves A1:D3 unchanged. No clasp run-function, API executable, web endpoint, or trigger is required. The editor supplies the bound-spreadsheet context this function uses.
Predict, change, and observe two different states
First compare an edit to the local CSV with an edit to the spreadsheet:
Predict whether changing
fixtures/requests.csvwould change the next editor run without importing it. It would not, because the function reads the sheet. Leave the committed CSV unchanged.In the practice spreadsheet only, change
C3fromIn progresstoOpen. Do not edit source or push. Run the same function in the editor. ExpectRequests: 2, open: 2..Restore
C3toIn progress, rerun, and expect the baseline count again. Git history and remote source have not changed during these cell edits.
Next, change the source code. Start with the restored sheet and a clean working tree:
git switch -c count-active In local src/Code.gs, change the status predicate from row[2] === 'Open' to row[2] === 'Open' || row[2] === 'In progress'. Rename openCount to activeCount at both occurrences, and change the log label from , open: to , active: . Do not change the function name, sheet access, manifest, or row filter.
Predict the observations before uploading:
An editor run now should still log
Requests: 2, open: 1.. Saving and committing the local file do not change remote head.After the reviewed upload, the same sheet data should log
Requests: 2, active: 2.because both statuses meet the revised condition.After that upload, changing only
C3toClosedshould giveRequests: 2, active: 1.. RestoreIn progressafterward. This checks that the new condition still excludes other statuses, so more than the log label changed.
Review and commit the revision locally:
git diff -- src/Code.gs
git add src/Code.gs
git diff --cached
git commit -m "Count open and in-progress requests" Do not push as soon as Git is clean. Repeat the remote snapshot procedure with a new directory, such as remote-snapshots/before-active-count, and inspect remote head. It should still match the baseline. If another person has changed it, do not overwrite it. Follow the next section.
When the account, target, remote baseline, local diff, and two-file upload list match, run npm run target -- push and perform the predicted editor runs.
Pull and recover without discarding another person's work
For ongoing work, choose one source-editing workflow and an upload owner for a given remote project. Prefer local Git-reviewed changes. If someone must edit in the Apps Script editor, stop uploads until those changes have been recovered and reviewed. There is no Git-style remote branch or automatic three-way merge inside clasp.
To recover an editor change, use a new snapshot directory and copied configuration as above, with rootDir set to . in that copy. A plain pull downloads current head. Compare all downloaded files with your working source, identify who made the differences, and incorporate the agreed changes into your local branch. Review manifest changes and newly added files as carefully as code.
Do not exclude a collaborator's new file and assume clasp will leave it on the server. Either bring the file into the local project and extend the upload allowlist, or agree that its removal is intended. Preserve both versions while resolving a conflict. Do not resolve it with push --force, an in-place pull, or a broad Git reset.
A fresh snapshot reduces uncertainty but does not prevent an edit between inspection and upload. Coordinate an editing pause with the other maintainer.
Restore this exercise's local baseline
After the exercise revision has been committed, check that there are no new uncommitted changes to src/Code.gs. The next command replaces only that local file with the tagged baseline. It does not erase the committed revision:
git status --short
git restore --source=lesson-baseline -- src/Code.gs
git diff -- src/Code.gs
git add src/Code.gs
git diff --cached
git commit -m "Restore the request summary baseline" Remote head still has the active-count revision until you upload the restoration. Repeat the account, target, snapshot, diff, and upload review before that push, then run logRequestSummary against the restored sheet and check for Requests: 2, open: 1.. Restoring code does not restore cells that someone changed.
For recovery from an existing numbered Apps Script version, first use npm run target -- list-versions to identify the version. In a new snapshot directory with its own copied configuration, the download command form is:
npm run clasp -- --project remote-snapshots/recovery/.clasp.json pull --versionNumber KNOWN_VERSION_NUMBER Create remote-snapshots/recovery and its configuration using the same procedure before running that command, and replace KNOWN_VERSION_NUMBER with the verified numeric version. The command downloads historical code into the snapshot. It does not roll back remote head or a deployment. Review and integrate the desired source into the working project before uploading.
If a push times out or the terminal loses its response, the remote outcome may be unknown. Do not assume that no files changed because you did not receive a success response. Obtain a new remote snapshot, compare it with the intended upload, and check actual executions before deciding whether another upload is needed.
Keep versions separate from deployments
A remote Apps Script project has one head deployment that follows its most recently saved code. An Apps Script version is an immutable snapshot of that remote project's code. A versioned deployment selects a version to expose as an application. Google's deployment guide distinguishes head deployments from versioned deployments.
This read-only summary needs neither a numbered version nor a versioned deployment to run in its bound editor. Do not add a web-app or API manifest configuration only to make a deployment command succeed. In particular, a web app or Apps Script API execution does not supply the same bound-container methods as an editor run.
When you maintain a deployable test application, follow this release sequence:
Select that application's test account and target, review the complete source and manifest, coordinate remote ownership, and upload the reviewed code.
Verify the intended execution path. A successful editor run does not prove web-app identity, access settings, event behavior, or external-client access.
Create a numbered version of that verified remote head.
Review the deployment type, execution identity, audience, scopes, ownership, and rollback choice. Then create a deployment, or update an existing one to the selected number.
The following are clasp 3.4.1 command forms. Do not run them against this summary project. Use that other application's equivalent explicit-target npm script. Replace the uppercase tokens with verified values from that project, and do not guess a version or deployment ID:
npm run target -- create-version "Reviewed test release"
npm run target -- list-versions
npm run target -- list-deployments create-version adds to the remote version history. It does not upload pending local edits. Record the returned version number alongside the reviewed Git commit. A Git hash and an Apps Script version number identify different snapshots. Check what was uploaded before treating them as the same code.
For a new deployment:
npm run target -- create-deployment --versionNumber APPROVED_VERSION_NUMBER --description "Approved test release" For an existing deployment:
npm run target -- update-deployment EXISTING_DEPLOYMENT_ID --versionNumber APPROVED_VERSION_NUMBER --description "Approved test revision"
npm run target -- list-deployments Creating a deployment creates a new deployment ID, whereas updating one changes its selected version while retaining its ID. Pass an explicit number. When no number is supplied, the 3.4.1 deployment implementation creates another version implicitly. Inspect the resulting deployment and exercise its actual consumer path before calling the release successful.
A rollback updates that same deployment to a previously verified compatible version:
npm run target -- update-deployment EXISTING_DEPLOYMENT_ID --versionNumber PREVIOUS_VERIFIED_VERSION_NUMBER --description "Approved rollback" A rollback does not restore remote head, spreadsheet data, properties, permissions, or external effects. Older code may not work with the current data. Confirm that compatibility, and confirm the deployment's selected version before and after the change. If the update response is lost, inspect the deployment before you change it again.
Add TypeScript or libraries only when needed
This project has no compilation step. Apps Script's V8 runtime is neither Node.js nor a browser. It does not supply Node module loading, browser window, or browser fetch. clasp runs locally on Node.js, but that does not make Node dependencies available inside uploaded code.
If a larger project uses TypeScript, supply Apps Script declarations, check the authored source, and configure a compiler or bundler to emit compatible JavaScript. clasp 3.x does not transpile TypeScript. Keep source separate from generated output. Point rootDir at the reviewed output and update the upload allowlist to match. Inspect emitted files for unresolved imports and Node or browser assumptions before uploading. Do not apply those changes to this no-build sample without also changing its instructions.
An Apps Script library is a script project with an identifier, access requirements, and a selected version. It is unrelated to npm packages. Use one when several projects need a maintained interface, and include its version, scopes, shared resources, and owner in review. Libraries add overhead, so a tiny single-project helper need not become a separate release process.
Neither local typing nor a library changes Apps Script execution limits. Review current Apps Script quotas and any external provider's limits before scheduling larger workloads. Use the execution and recovery techniques in debugging and quotas. Do not treat a successful upload as an operational test.
Troubleshoot by locating the wrong state
| What you observe | Check before changing anything else |
|---|---|
| clasp reports an unknown command or version | Confirm the local package and npm run clasp -- --version. Read the pinned command's --help, and do not mix older aliases or positional arguments into a different release. |
| Login succeeds but project access fails | Confirm the selected account, account-level Apps Script API access, project edit rights, and organizational policy. Runtime spreadsheet authorization is a separate step. |
| clasp selects the wrong project | Stop before any write. Compare .clasp.json with the bound test project's Script ID, not the spreadsheet ID or title alone. |
| The upload list is empty or has an extra file | Check rootDir, exact filenames, extensions, and root-relative .claspignore patterns. Do not widen the allowlist until the intended complete remote content is known. |
| A manifest push is skipped | Inspect the intended scope or configuration change and the interactive confirmation. Do not add --force to hide an unexplained refusal. |
| The log still shows the old label | Distinguish a local-only edit from uploaded head. Inspect the actual remote source, function selection, account, and execution record. |
The script cannot find Requests or an active spreadsheet | Confirm the sheet name and bound editor context. Do not run this function through Node.js or assume an API execution has an active container. |
| An edit to the local CSV has no effect | The script reads spreadsheet cells, not the CSV. Import only fictional data into the intended test file. |
| A collaborator's file is missing locally | Pull into a fresh snapshot and compare before uploading. Committing locally does not mean the remote file can be removed. |
| Git shows a credential or live target as tracked | Stop before committing or sharing. Ignore rules are not retroactive. If a credential was exposed, revoke or rotate it and follow the repository's incident procedure. Deleting it from the latest file is not enough. |
Leave the exercise in a known state
Restore the test sheet's original C3 status and keep any observations you need without copying tokens or real data into the repository.
Keep the source, lockfile, and Git history if you plan to return. Close terminals before removing disposable local dependencies or snapshot directories through your file manager. Do not delete the shared ~/.clasprc.json file, which may hold other clasp accounts.
If the book-test credential is no longer needed, first confirm its identity, then remove that named local credential:
npm run clasp -- show-authorized-user
npm run clasp -- logout In this clasp release, logout removes the stored credential for that label. It does not revoke Google-side OAuth access. Review the Google account's third-party access separately if you want to revoke it. Turning off account-level Apps Script API access can affect other authorized tools, so leave it enabled if other tools use it.
Removing the local folder does not remove the remote script or spreadsheet. Trash the disposable practice spreadsheet when you no longer need it.
Next, Test Apps Script code locally and in Sheets. Keep Reference, glossary, and troubleshooting bookmarked for everyday lookup.
Project files
Complete source files for this chapter's examples.
Log a request summary from a local Apps Script project
Code.gs
Download filefunction logRequestSummary() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Requests');
if (!sheet) throw new Error('Create a sheet named Requests before running this function.');
const values = sheet.getDataRange().getDisplayValues();
if (values.length < 2) throw new Error('Add a header row and at least one request row.');
const rows = values.slice(1).filter((row) => row[0]);
const openCount = rows.filter((row) => row[2] === 'Open').length;
Logger.log('Requests: ' + rows.length + ', open: ' + openCount + '.');
}
appsscript.json
Download file{
"timeZone": "Etc/UTC",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets.currentonly"
]
}
requests.csv
Download fileRequest,Owner,Status,Notes
Confirm room booking,Alex,Open,Check accessibility requirements
Prepare agenda,Sam,In progress,Collect topics before Friday
Pinned clasp and Git configuration for the request summary
package.json
Download file{
"name": "book-local-request-summary",
"version": "1.0.0",
"private": true,
"engines": {
"node": ">=22"
},
"scripts": {
"clasp": "clasp --user book-test",
"target": "clasp --user book-test --project .clasp.json --ignore .claspignore"
},
"devDependencies": {
"@google/clasp": "3.4.1"
}
}
clasp.example.json
Download file{
"scriptId": "REPLACE_WITH_TEST_SCRIPT_ID",
"rootDir": "src",
"scriptExtensions": [".gs"]
}
.claspignore
Download file**/**
!Code.gs
!appsscript.json
.gitignore
Download file# Dependencies and generated or temporary output.
node_modules/
/build/
/dist/
/coverage/
/remote-snapshots/
/tmp/
*.log
*.tmp
*.tsbuildinfo
.DS_Store
# Live targets and account credentials are local, not source.
.clasp.json
.clasp.*.json
.clasprc.json
.clasprc.*.json
.env
.env.*
.npmrc
credentials.json
credentials-*.json
client_secret*.json
service-account*.json
*.pem
*.key