Run Google Apps Script projects locally with a sandboxed spreadsheet environment, mock HTTP services, and XLSX export.
- Sandbox execution — Run
.gsfunctions in an isolated Node.js VM with fullSpreadsheetApp,UrlFetchApp,PropertiesService,ScriptApp, andUtilitiesshims. - Mock HTTP responses — Define canned responses for
UrlFetchApp.fetch()calls so your scripts never hit real endpoints during testing. - HTTP capture — Record live API responses and export them as mock files with built-in PII masking.
- JSON / XLSX export — Export the in-memory spreadsheet to JSON or
.xlsxfiles. - .env support — Loads
.envand.env.localfiles soPropertiesServicereturns your local config values. - Test helpers —
createTestRunner,buildSheetData,assertSheet, and more for writing tests against your GAS project.
npm install gas-sandboxOr run directly:
npx gas-sandbox -p ./my-gas-project -r mainimport { GASRunner } from 'gas-sandbox';
const runner = new GASRunner({
httpMode: 'mock',
skipSleep: true,
mockResponses: {
'api.example.com/data': { statusCode: 200, body: { items: [1, 2, 3] } }
}
});
runner.loadProject('./my-gas-project');
runner.run('processData');
runner.exportXlsx('output.xlsx');gas-sandbox --project <dir> --run <function> [options]
| Flag | Short | Description |
|---|---|---|
--project <dir> |
-p |
Directory containing .gs files (required) |
--run <function> |
-r |
Function to execute |
--data <file> |
-d |
Load spreadsheet data from JSON |
--output <file> |
-o |
Export spreadsheet data after execution (JSON) |
--output-xlsx <file> |
Export spreadsheet data after execution (XLSX) | |
--env <file> |
-e |
Path to .env file (default: project dir) |
--mock |
Use mock HTTP — no real API calls | |
--mock-file <file> |
Load mock responses from JSON file | |
--capture-mocks <file> |
Capture live HTTP responses to a mock file | |
--mask-emails |
Mask email addresses in captured data | |
--mask-ids |
Mask UUIDs and hex IDs in captured data | |
--mask-fields <list> |
Comma-separated field names to mask | |
--mask-pattern <regex> |
Regex pattern to replace with [MASKED] |
|
--skip-sleep |
Skip Utilities.sleep() calls |
|
--list |
List available functions and exit | |
--help |
-h |
Show help |
# List all functions in a project
gas-sandbox -p ./my-gas-project --list
# Run with mock HTTP and export to XLSX
gas-sandbox -p ./my-gas-project -r processData --mock-file mocks.json --output-xlsx report.xlsx
# Run live and export JSON + XLSX
gas-sandbox -p ./my-gas-project -r fetchReport --skip-sleep -o data.json --output-xlsx report.xlsx
# Capture live responses with PII masking
gas-sandbox -p ./my-gas-project -r fetchData --capture-mocks mocks.json --mask-emails --mask-fields name,userIdimport { GASRunner } from 'gas-sandbox';interface GASRunnerOptions {
httpMode?: 'mock' | 'live' | 'capture';
skipSleep?: boolean;
mockResponses?: Record<string, MockResponse>;
envPath?: string;
projectDir?: string;
}| Method | Description |
|---|---|
loadProject(dir: string) |
Load all .gs files from the given directory |
loadData(pathOrObj: string | SpreadsheetJSON) |
Load spreadsheet data from JSON file or object |
run(fn: string, ...args) |
Execute a top-level GAS function by name |
listFunctions(): string[] |
List all available function names |
getSpreadsheet(): Spreadsheet |
Access the in-memory spreadsheet |
getSheetData(name: string) |
Get raw 2D array of a sheet's data |
exportData(path: string) |
Write spreadsheet state to a JSON file |
exportXlsx(path: string) |
Write spreadsheet state to an .xlsx file |
exportMocks(path: string, mask?: MaskOptions) |
Export captured HTTP responses (capture mode) |
getCapturedResponses() |
Get the Map of captured HTTP responses |
interface MockResponse {
statusCode?: number;
body?: unknown;
}
interface SpreadsheetJSON {
sheets: Record<string, { data: CellValue[][] }>;
}const runner = new GASRunner({
httpMode: 'mock',
mockResponses: {
'api.example.com/users': {
statusCode: 200,
body: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
}
}
});URL matching is substring-based — 'api.example.com/users' matches any URL containing that string.
Create a mocks.json:
{
"api.example.com/users": {
"statusCode": 200,
"body": [{ "id": 1, "name": "Alice" }]
}
}gas-sandbox -p ./my-gas-project -r fetchUsers --mock-file mocks.jsonRecord live API responses during execution, then replay them offline. Masking options sanitize PII before saving.
# 1. Record real API responses with PII masked
gas-sandbox -p ./my-gas-project -r fetchData \
--capture-mocks mocks.json \
--mask-emails --mask-fields name,userId
# 2. Replay offline in tests — no API calls
gas-sandbox -p ./my-gas-project -r fetchData \
--mock-file mocks.json --skip-sleep| Option | Effect | Example |
|---|---|---|
--mask-emails |
alice@corp.com → user-1@example.com |
Deterministic — same email always maps to same fake |
--mask-ids |
UUIDs/hex IDs → id-0001 |
Matches UUIDs and 12+ char hex strings |
--mask-fields name,userId |
Field values → name-1, userId-1 |
Deterministic per-field counters |
--mask-pattern <regex> |
Regex matches → [MASKED] |
Custom pattern replacement |
import { GASRunner, DataMasker } from 'gas-sandbox';
const runner = new GASRunner({ httpMode: 'capture', skipSleep: true });
runner.loadProject('./my-gas-project');
runner.run('fetchData');
runner.exportMocks('mocks.json', {
emails: true,
fields: ['name', 'userId'],
});gas-sandbox -p ./my-gas-project -r generateReport --output-xlsx report.xlsxrunner.loadProject('./my-gas-project');
runner.run('buildReport');
runner.exportXlsx('report.xlsx');The exported file contains one worksheet per in-memory sheet, preserving sheet names and cell values.
gas-sandbox loads environment variables in this order (later files override earlier ones):
- System environment variables
.envin the project directory.env.localin the project directory- Custom path via
--env <path>flag
Variables are available through PropertiesService.getScriptProperties():
// In your .gs code
var token = PropertiesService.getScriptProperties().getProperty('API_TOKEN');gas-sandbox exports utilities for writing tests against your GAS project:
import {
createTestRunner,
buildSheetData,
assertSheet,
assertCell,
getSheetAsRecords,
runAndNormalize,
cleanupTestEnv,
} from 'gas-sandbox';| Helper | Description |
|---|---|
createTestRunner(opts) |
Create a GASRunner with mock/skipSleep defaults, load data and project in one call |
buildSheetData(sheets) |
Build SpreadsheetJSON from { headers, rows } format |
assertSheet(runner, name) |
Assert a sheet exists and return it (throws if missing) |
assertCell(runner, sheet, row, col, expected) |
Assert a cell value (1-indexed) |
getSheetAsRecords(runner, name) |
Read sheet as array of { header: value } objects |
getDataRowCount(runner, name) |
Get row count excluding header |
runAndNormalize(runner, fn, ...args) |
Run function + JSON round-trip to normalize cross-VM objects |
cleanupTestEnv(keys) |
Delete env vars set during testing |
import { describe, it, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import { createTestRunner, buildSheetData, runAndNormalize, cleanupTestEnv } from 'gas-sandbox';
describe('My GAS project', () => {
afterEach(() => cleanupTestEnv(['API_TOKEN']));
it('processes data correctly', () => {
const runner = createTestRunner({
projectDir: './my-gas-project',
data: buildSheetData({
'Raw Data': {
headers: ['Name', 'Score'],
rows: [['Alice', 95], ['Bob', 87]],
}
}),
mockResponses: {
'api.example.com/config': { statusCode: 200, body: { threshold: 90 } }
},
env: { API_TOKEN: 'test-token' },
});
const result = runAndNormalize(runner, 'processScores');
assert.equal(result.aboveThreshold, 1);
});
});-
Install gas-sandbox:
npm init -y && npm install -D gas-sandbox tsx @types/node -
Create
.env.localnext to your.gsfiles with API keys:API_TOKEN=Bearer your-token-here -
Capture mock data from a live run:
npx gas-sandbox -p . -r myFunction --capture-mocks mocks.json --mask-emails --mask-fields name -
Write tests at
test/my-project.test.tsusing the captured mocks. -
Add scripts to
package.json:{ "scripts": { "test": "tsx --test test/*.test.ts", "run": "gas-sandbox -p . -r myFunction --skip-sleep", "run:export": "gas-sandbox -p . -r myFunction --skip-sleep --output-xlsx report.xlsx", "run:mock": "gas-sandbox -p . -r myFunction --mock-file mocks.json --skip-sleep", "capture": "gas-sandbox -p . -r myFunction --capture-mocks mocks.json --mask-emails --mask-fields name" } } -
Run:
npm test # Run tests with mocked data npm run run:export # Live run with XLSX export npm run run:mock # Offline run with captured mocks npm run capture # Re-capture fresh mock data