Claude Skill: gas-dev — Google Apps Script Development Assistant
Overview
A Claude skill that encodes the full Google Apps Script (GAS) development workflow — from project creation to deployment — so Claude can assist efficiently with any GAS project without re-learning the setup each time.
- Scope: Global (
~/.claude/skills/gas-dev/) - Trigger: Auto-triggered when working with Apps Script, clasp, Google Sheets apps, or web app deployment
- Location:
/Users/tommy/.claude/skills/gas-dev/
Framework Overview
┌─────────────────────────────────────────────────────────────────┐
│ GAS Development Framework │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ SETUP │───▶│ DEVELOP │───▶│ TEST │───▶│ DEPLOY │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ GCP Project Code.js Local Dev clasp push │
│ OAuth Creds index.html Mock Data clasp deploy │
│ clasp login styles.html http-server Same URL! │
│ accounts.json components Polyfill │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ INFRASTRUCTURE │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ accounts.json │ │
│ │ (Central Registry: projects, accounts, credentials) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ├── .accounts/<email>/ (per-account credentials) │
│ ├── tools/gws-auth/ (OAuth flow + API client) │
│ ├── tools/gws-client.js (Sheets/Drive API wrapper) │
│ └── tools/file-picker/ (Drive file whitelist UI) │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ARCHITECTURE (per project) │
│ │
│ ┌─ LOCAL ──────────────┐ ┌─ GOOGLE CLOUD ──────────────┐ │
│ │ │ │ │ │
│ │ ProjectName/ │ │ Google Drive/App/ │ │
│ │ ├── Code.js ─────┼──▶│ ├── ProjectName (GAS) │ │
│ │ ├── index.html │ │ ├── ProjectName (Sheet) │ │
│ │ ├── styles.html │ │ └── assets/ │ │
│ │ ├── components.html │ │ │ │
│ │ ├── server.js ───┐ │ │ Apps Script Runtime │ │
│ │ ├── mock-data.json│ │ │ ├── doGet() → HtmlService │ │
│ │ └── .clasp.json │ │ │ ├── Sheets API (database) │ │
│ │ │ │ │ ├── PropertiesService │ │
│ │ localhost:PORT ◀──┘ │ │ ├── MailApp (email) │ │
│ │ (dev server) │ │ └── DriveApp (files) │ │
│ └───────────────────────┘ └──────────────────────────────┘ │
│ │
│ clasp push ──────▶ sync code to GAS │
│ clasp deploy -i ─▶ update SAME web app URL │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ FRONTEND PATTERNS (iframe-compatible SPA) │
│ │
│ ┌─ Hash Router ───────────────────────────────────────────┐ │
│ │ #form ──▶ #success ──▶ #admin ──▶ #dashboard │ │
│ │ │ │ │ │ │
│ │ │ localStorage │ password │ Sheets data │ │
│ │ │ (persist state) │ check │ (cached) │ │
│ │ ▼ ▼ ▼ │ │
│ │ Form View Admin Login Dashboard View │ │
│ │ ├── Contact Info ├── Summary (charts) │ │
│ │ ├── Date Picker ├── Submissions │ │
│ │ ├── AI Questions └── Settings │ │
│ │ └── Submit → email │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ Back button ✓ Refresh ✓ iframe ✓ Mobile ✓ │
│ │
└─────────────────────────────────────────────────────────────────┘
MULTI-ACCOUNT FLOW
┌──────────────┐ ┌──────────────┐
│ Account A │ │ Account B │
│ user@co.com │ │ user@gmail │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
.accounts/user@co.com/ .accounts/user@gmail/
├── .clasprc.json ├── .clasprc.json
├── gws-credentials ├── gws-credentials
├── gws-token.json ├── gws-token.json
└── allowed-files.json └── allowed-files.json
│ │
└────────┬───────────────┘
▼
accounts.json
(central registry)
│
┌────────┴────────┐
▼ ▼
Project A Project B
(Account A) (Account B)
1. Prerequisites
1.1 CLI Tools
| Tool | Install | Purpose |
|---|---|---|
| Node.js (v18+) | brew install node |
Runtime for local dev servers and scripts |
| npm | Bundled with Node.js | Package management |
| npx | Bundled with npm | Run one-off packages (e.g. http-server) |
| clasp | npm install -g @google/clasp |
Google Apps Script CLI for push/deploy |
Note: gcloud CLI is NOT required. All GCP configuration is done via the web console.
1.2 Google Cloud Platform Setup (One-time, Manual)
All steps below are done in console.cloud.google.com — they cannot be automated via CLI.
Step 1: Create a GCP Project
- GCP Console → New Project → name it (e.g. “claude-integration”)
- Note the Project Number (used later to link Apps Script)
Step 2: Enable APIs
- GCP Console → APIs & Services → Library
- Search and enable each:
- Google Sheets API
- Google Drive API
- Apps Script API
- Gmail API (only if using MailApp with email aliases)
Step 3: Configure OAuth Consent Screen
- GCP Console → APIs & Services → OAuth consent screen
- Choose “External” (or “Internal” for Google Workspace orgs)
- Add required scopes:
spreadsheets,drive,script.projects - Add test users if in “Testing” mode
Step 4: Create OAuth Credentials
- GCP Console → APIs & Services → Credentials → Create Credentials → OAuth Client ID
| Type | Use | Save as |
|---|---|---|
| Desktop application | CLI/Node.js tools (clasp, gws-client) | gws-credentials.json |
| Web application | Browser-based file picker UI | gws-web-credentials.json |
- Download both JSON files and store in
.accounts/<email>/
Step 5: Link Apps Script to GCP (per project)
- Open Apps Script Editor for the project
- Project Settings → Google Cloud Platform (GCP) Project
- Enter the GCP Project Number
- This enables
clasp logsand advanced API access
1.3 Authentication & Multi-Account Management
Each Google account gets its own credential folder:
.accounts/
├── user1@gmail.com/
│ ├── .clasprc.json # clasp auth token
│ ├── gws-credentials.json # OAuth desktop credentials
│ ├── gws-web-credentials.json # OAuth web credentials
│ ├── gws-token.json # Access/refresh tokens (auto-generated)
│ └── allowed-files.json # Whitelisted Drive file IDs
├── user2@company.com/
│ └── ... (same structure)
Central registry: accounts.json at project root maps every project to its account, scriptId, sheetId, deploymentId, and webAppUrl.
Setup steps per account:
clasp login→ stores token in~/.clasprc.json- Copy to account folder:
cp ~/.clasprc.json .accounts/<email>/.clasprc.json - Run GWS auth:
cd tools/gws-auth && node auth.js <email>→ generatesgws-token.json - Run file picker:
cd tools/file-picker && npm start→ manageallowed-files.json
Switching accounts: cp .accounts/<email>/.clasprc.json ~/.clasprc.json
Multiple accounts can share the same GCP project for OAuth credentials.
2. Project Structure
2.1 Standard File Layout
Simple project (form, landing page):
ProjectName/
├── .clasp.json # scriptId, rootDir
├── .claspignore # exclude local dev files
├── appsscript.json # runtime config, webapp settings
├── Code.js # backend: doGet, CRUD, email
├── Index.html # main UI entry
└── styles.html # CSS (separate file)
Full-featured project (dashboard, SPA):
ProjectName/
├── .clasp.json
├── .claspignore
├── appsscript.json
├── Code.gs # backend
├── index.html # entry point with includes
├── styles.html # all CSS
├── header.html # nav/header component
├── forms.html # form components
├── dashboard.html # dashboard view
├── server.js # local dev server (NOT deployed)
├── package.json # local dev dependencies
├── mock-data.json # synced Sheet data for local testing
├── .settings.json # local settings cache
└── node_modules/ # NOT deployed
2.2 Mirrored Folder Structure (Local ↔ Google Drive)
Local project folders and Google Drive folders should mirror each other:
LOCAL: GOOGLE DRIVE:
~/Project/Appscript/ My Drive/App/
├── ProjectA/ ├── ProjectA/
│ ├── .clasp.json │ ├── ProjectA (Apps Script)
│ ├── Code.js │ ├── ProjectA (Google Sheet)
│ ├── Index.html │ └── (other assets)
│ └── ... │
├── ProjectB/ ├── ProjectB/
│ └── ... │ └── ...
├── .accounts/ │
├── accounts.json │
└── tools/ │
- Each project has a local folder AND a Drive folder with the same name
- The Drive folder contains: the Apps Script file, the Google Sheet, and any uploaded assets
- When creating a new project, create both the local folder and Drive folder
- Store the Drive folder ID as
driveFolderIdin accounts.json
2.3 .claspignore (always include)
node_modules/**
server.js
package.json
package-lock.json
mock-data.json
.settings.json
.clasp.json
.claspignore
2.3 accounts.json Structure
{
"projects": {
"ProjectName": {
"account": "user@domain.com",
"scriptId": "...",
"sheetId": "...",
"deploymentId": "...",
"webAppUrl": "https://script.google.com/macros/s/.../exec",
"webAppAccess": "Anyone_Anonymous",
"driveFolderId": "...",
"note": "Deploy: clasp push && clasp deploy -i <deploymentId>"
}
},
"accounts": {
"user@domain.com": {
"clasp": ".accounts/user@domain.com/.clasprc.json",
"gws": ".accounts/user@domain.com/gws-credentials.json",
"gcpProject": "project-name"
}
},
"default": "user@domain.com"
}
3. Development Workflow
3.1 Creating a New Project
- Create Google Sheet via Sheets API (using
gws-client.js) - Create Drive folder for the project
clasp create --title "ProjectName"in project folder- Move GAS file to Drive folder via Drive API
- Set up
appsscript.jsonwith timezone, webapp config - Register in
accounts.jsonwith all IDs - Create boilerplate: Code.js, Index.html, styles.html, .claspignore
- First push + deploy:
clasp push && clasp deploy - Save deploymentId to accounts.json
3.2 Local Development
For simple projects: Use npx http-server -p 3850 -c-1 to serve files locally with no caching.
For projects needing API access: Create server.js with:
- Express server on custom port
<?!= include('filename') ?>tag processing (reads local HTML files)google.script.runpolyfill using Proxy pattern → routes to/api/<functionName>endpoints- API endpoints that mirror Code.js functions using
gws-client.js - Template expression replacement (e.g.
<?= getAppIcon() ?>)
Mock data pattern:
tools/sync-mock-data.js— pulls live Sheet data intomock-data.json- Local server reads
mock-data.jsonfor instant responses - Run
node tools/sync-mock-data.jsto refresh local data
3.3 Deployment
CRITICAL RULE: Always deploy to the same URL.
# Switch account if needed
cp .accounts/<email>/.clasprc.json ~/.clasprc.json
# Push code then deploy to existing deployment
clasp push --force
clasp deploy -i "<deploymentId>" -d "Description"
- NEVER use
clasp deploywithout-iflag (creates new deployment = new URL) - ALWAYS use
clasp deploy -i <deploymentId>to update existing deployment - Store deploymentId in
accounts.jsonfor reference
4. Coding Preferences
4.1 File Organization
- Break down large HTML into modular includes:
styles.html,header.html, component files - Use
<?!= include('filename') ?>for server-side HTML includes - Keep each file focused — refactor when a file grows beyond manageable size
- CSS in separate
styles.html, not inline in the main page - One Code.js/Code.gs for backend (unless very large, then split by domain)
4.2 Backend Patterns (Code.js)
// Cached SpreadsheetApp
var _ss = null;
function SS() { if (!_ss) _ss = SpreadsheetApp.openById(SHEET_ID); return _ss; }
// doGet with page routing
function doGet(e) {
var page = e && e.parameter && e.parameter.page;
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('App Title')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
// Include helper
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
// Settings via PropertiesService
function getSetting(key) {
return PropertiesService.getScriptProperties().getProperty(key) || '';
}
// Date formatting (HKT)
var now = Utilities.formatDate(new Date(), 'Asia/Hong_Kong', 'yyyy-MM-dd HH:mm:ss');
4.3 Key Gotchas
| Issue | Cause | Fix |
|---|---|---|
google.script.run returns null |
Can’t serialize Date objects or complex objects in ANYONE_ANONYMOUS mode | Convert Dates to ISO strings; return simple strings (‘ok’) not objects |
alert() blocked |
iframe sandbox blocks modals | Use inline error messages instead |
Phone +852 shows #ERROR in Sheet |
+ interpreted as formula |
Strip non-digits in frontend, or prefix with ' in backend |
| Form hung on “Submitting…” | Return value can’t serialize | Return 'ok' string, not { success: true } |
| Timestamp wrong timezone | new Date() returns UTC in anonymous mode |
Use Utilities.formatDate(new Date(), 'Asia/Hong_Kong', ...) |
4.4 Frontend Patterns
Hash-based SPA routing (for iframe compatibility):
- Routes:
#form,#success,#admin,#dashboard hashchangeevent listener for navigationlocalStorageto persist current view across refresh- Success page: use
justSubmittedflag — don’t persist on refresh navigate()function handles same-hash re-routing
iframe considerations:
setXFrameOptionsMode(ALLOWALL)in doGet- No
alert()— use inline error messages localStoragefor state persistence across refresh- Hash routing creates browser history entries for back button
Responsive design:
- Raleway font + Material Icons Outlined
- Mobile breakpoints: 768px (tablet), 540px (mobile), 360px (small mobile)
inputmode="numeric"for phone fields (shows number keyboard on mobile)- Font size 16px on mobile inputs (prevents iOS zoom)
5. Skill File Structure
~/.claude/skills/gas-dev/
├── SKILL.md # Main skill (auto-triggered)
└── references/
├── project-setup.md # New project creation checklist
├── local-dev.md # Local server, polyfill, mock data
├── deployment.md # Clasp push/deploy, same-URL rule
├── frontend-patterns.md # SPA routing, iframe, localStorage, responsive
├── backend-patterns.md # Code.js patterns, Sheets CRUD, email, gotchas
├── gws-client.md # Google Workspace API usage from Node.js
└── accounts.md # Multi-account setup, accounts.json, credentials
SKILL.md Frontmatter
---
name: gas-dev
description: >
Google Apps Script development assistant. Handles project creation, local dev servers,
clasp deployment, Google Sheets as database, Script Properties for settings, and
web app frontend with iframe/SPA support. TRIGGER when: working in an Appscript
project directory, user mentions Apps Script / GAS / clasp / doGet / google.script.run,
creates Google Sheets apps, deploys web apps, builds forms or dashboards with
Google Sheets backend, or asks about Google Workspace API integration.
Even if user just says "create a form" or "deploy it" in an Appscript context,
use this skill.
---
SKILL.md Body Sections
- Quick Reference — most common commands (push, deploy, switch account, sync data)
- Project Structure — standard layout, accounts.json, .claspignore
- Account Management — switch accounts, credential paths
- Creating a New Project — step-by-step with API calls
- Local Development — server setup, mock data, polyfill
- Deployment Rules — ALWAYS same URL, push before deploy
- Frontend Patterns — routing, iframe, localStorage, responsive
- Backend Patterns — doGet, Sheets CRUD, email, settings
- Coding Preferences — modular HTML includes, separate styles, keep files short
- Common Gotchas — Date serialization, alert() blocked, return strings not objects
- Reference pointers — when to read each reference file
6. Verification
- Check skill appears in Claude Code’s available skills list (look for
gas-devin system reminder) - Test auto-triggering with prompts: “create a new Apps Script project”, “deploy my web app”, “add a Google Sheet as database”
- Verify reference files load when relevant (check if Claude reads them during complex tasks)
- End-to-end test: create project → local dev → push → deploy → verify same URL