apps-script-clasp-workflow

Documents the clasp CLI workflow for Google Apps Script projects — login, project creation/cloning, push vs deploy, versioning, and multi-environment (dev/staging/prod) setups. Use when setting up CI/

SKILL.md

GitHub

Apps Script clasp Workflow

Auth

clasp login             # interactive, opens a browser
clasp login --no-localhost   # headless/CI — prints a URL to visit, then paste the resulting code back

Produces ~/.clasprc.json. In CI, inject this file from a secret — never commit it; it's a live OAuth credential.

Project setup

clasp create --type standalone --title "My Project"   # new project
clasp clone <scriptId>                                  # existing project

.clasp.json fields that matter:

Field Purpose
scriptId Target Apps Script project — the single most important field to get right per environment (see Multi-environment below)
rootDir Directory clasp pushes from, e.g. "./dist/appsscript" — point it at your build output, not source, if you bundle
filePushOrder Files pushed first, before the rest — appsscript.json is commonly listed here since manifest changes should land before code that depends on new scopes/services

Push vs. deploy — the distinction that causes silent "my fix isn't live" bugs

  • clasp push updates the project's HEAD (the editor/dev version). Anyone opening the script editor sees it immediately. It does not affect any existing versioned deployment.
  • clasp deploy creates or updates a versioned deployment with a stable URL/ID. A web app or add-on installed by end users runs whatever version its deployment points at — pushing new code does nothing for them until you deploy.
clasp push -f                          # force push, skip the interactive diff/overwrite prompt — needed for CI
clasp push --watch                     # dev loop: push on every local file change
clasp deploy -i <deploymentId> -d "Fix widget sorting"   # update an existing deployment
clasp deployments                      # list deployment IDs
clasp versions                         # list version history

Forgetting the deploy step after a push is the single most common "I fixed it but users still see the bug" report — always pair a fix with a deploy when the project has a live deployment.

Multi-environment (dev/staging/prod)

clasp has no native named-environment concept — one .clasp.json points at one scriptId. The standard workaround is one config file per environment, copied into place before pushing:

.clasp.dev.json
.clasp.staging.json
.clasp.prod.json
{
  "scripts": {
    "push:dev": "cp .clasp.dev.json .clasp.json && clasp push -f",
    "push:staging": "cp .clasp.staging.json .clasp.json && clasp push -f",
    "deploy:prod": "cp .clasp.prod.json .clasp.json && clasp push -f && clasp deploy -d \"$npm_config_message\""
  }
}

Never commit the active .clasp.json if it varies per developer machine/environment — commit the per-environment copies instead and gitignore the working one, or keep .clasp.json itself checked in only for single-environment projects.

Manifest essentials worth setting explicitly

Don't leave appsscript.json at its scaffolded defaults:

  • runtimeVersion: "V8" — required for modern syntax and decorators (see the bootgs-quickstart skill if the project uses bootgs).
  • oauthScopes — list only what's actually called. GAS shows the full list on the consent screen; an over-broad list (a common copy-paste mistake) increases user friction and review scrutiny for public add-ons without adding capability.
  • webapp.executeAs/webapp.access — only relevant if doGet/doPost are deployed as a web app.
  • exceptionLogging: "STACKDRIVER" — otherwise uncaught errors only appear in the legacy execution log tied to the script's lifetime.

Verification

  • clasp push -f used in CI (interactive prompts hang non-interactively without -f).
  • A deploy step (clasp deploy) runs after push whenever the project has a live deployment end users depend on — push alone is not a release.
  • .clasprc.json is never committed; CI injects it from a secret.
  • oauthScopes reviewed against what the code actually calls before each release.