- 2026.07.29 What Breaks When Apps Script Projects Sprawl
- 2026.07.16 Why a Daily Apps Script Trigger Suddenly Stops
- 2026.07.15 What Breaks When Customer Data Lives in Browser Storage
- 2026.07.07 When the Browser Throws Your Data Away: The Green Checkmark Lies
- 2026.07.07 Installed ≠ Working: the Backup Trigger Never Ran Once
- 2026.07.07 GAS Has No Gradual Rollout: One 'Correct' Check Locks Out Every User
- 2026.07.07 Sheets Turns Your Text Into a Date: When '2026-06' Blocks Completion Forever
- 2026.06.18 An App Where Many Write to One Sheet: Concurrency, Duplicates, and Export Design
- 2026.06.12 Don't Read Cells One at a Time: Service Calls Are the Real Cost
- 2026.06.11 Stuffing JSON or Images into a Sheet Cell Will Break It
- 2026.06.10 One Deploy Setting Decides Your Security Model: Execute as / Access
- 2026.06.09 The 6-Minute GAS Limit: Chunk and Resume Before It Kills You
- 2026.06.08 PropertiesService Is Not a Database: the 9KB Wall You are here
- 2026.06.07 Why Saving a Screen as an Image Freezes on Mobile
- 2026.06.06 Concurrent Writes to the Same Sheet Break: LockService and the 30-Run Ceiling
- 2026.06.05 Your /exec URL Must Not Change on Every Redeploy
- 2026.06.04 External Calls Fail Sometimes: Exponential Backoff and a Retry Budget
- 2026.06.03 HtmlService Apps: Shell First, Then Async Load and Mobile Back
- 2026.06.02 When to Graduate from GAS: Limit Signals and Moving to an External DB
- 2026.06.01 Adding a Brain and an Engine Room to GAS: Vertex AI and Cloud Run
PropertiesService Is Not a Database: the 9KB Wall
A Properties value caps near 9KB. Cache only data you can lose. Big state goes to Sheet, Drive, or an external DB. Plain JSON loses types.
PropertiesService is a small store, not a database. Each value caps near 9KB, and the whole store is limited too. Keep a cursor, small config, or an idempotency key there; send anything bigger elsewhere.
Why it matters
Start with “store state as JSON in Properties” and one day, when the data grows, it silently fails at 9KB. And state kept only in cache disappears when the TTL expires. Putting data you can’t lose into volatile storage is where the accident begins.
Storage choice rule
| Store | Use | Limit |
|---|---|---|
| CacheService | Disposable acceleration cache | 100KB/key, TTL-based |
| PropertiesService | Cursors, config, flags | ~9KB/value |
| Sheet | User-visible tabular state | 50,000 chars/cell |
| Drive JSON | Larger opaque state (manage the file) | File size |
| External DB | Multi-user, query-heavy, high-volume | — |
The key question: can the data be lost? That answer splits CacheService from the rest.
Plain JSON loses types
JSON.stringify()/parse() alone won’t safely restore Date, Map, Set, undefined, or NaN.
// store Date as an ISO string, restore on read
const replacer = (k, v) => (v instanceof Date ? { __d: v.toISOString() } : v);
const reviver = (k, v) => (v && v.__d ? new Date(v.__d) : v);
props.setProperty("job", JSON.stringify(state, replacer));
const state = JSON.parse(props.getProperty("job") || "{}", reviver);
Deeper: chunk caches over 100KB
One CacheService key caps at 100KB. For larger values, split into chunks across keys with a reassembly key. And a cache must always have a recompute path.
One line to keep: small → Properties, disposable → Cache, big → Sheet/Drive/DB. Protect types with a replacer/reviver.
Frequently asked questions
- Why does storing JSON state in PropertiesService silently fail?
- Each value caps near 9KB, so as data grows it fails without a clear error. Keep only a cursor, small config, or an idempotency key there; send larger state to Sheet, Drive, or an external DB.
- When should I use CacheService versus PropertiesService?
- CacheService (100KB per key, TTL-based) is for disposable acceleration data you can afford to lose. PropertiesService (about 9KB per value) is for durable cursors, config, and flags.
- Which JavaScript types does plain JSON serialization fail to restore safely?
- Date, Map, Set, undefined, and NaN are not safely restored by JSON.stringify/parse alone. Use an explicit replacer and reviver to protect those types.
- 2026.07.29 What Breaks When Apps Script Projects Sprawl
- 2026.07.16 Why a Daily Apps Script Trigger Suddenly Stops
- 2026.07.15 What Breaks When Customer Data Lives in Browser Storage
- 2026.07.07 When the Browser Throws Your Data Away: The Green Checkmark Lies
- 2026.07.07 Installed ≠ Working: the Backup Trigger Never Ran Once
- 2026.07.07 GAS Has No Gradual Rollout: One 'Correct' Check Locks Out Every User
- 2026.07.07 Sheets Turns Your Text Into a Date: When '2026-06' Blocks Completion Forever
- 2026.06.18 An App Where Many Write to One Sheet: Concurrency, Duplicates, and Export Design
- 2026.06.12 Don't Read Cells One at a Time: Service Calls Are the Real Cost
- 2026.06.11 Stuffing JSON or Images into a Sheet Cell Will Break It
- 2026.06.10 One Deploy Setting Decides Your Security Model: Execute as / Access
- 2026.06.09 The 6-Minute GAS Limit: Chunk and Resume Before It Kills You
- 2026.06.08 PropertiesService Is Not a Database: the 9KB Wall You are here
- 2026.06.07 Why Saving a Screen as an Image Freezes on Mobile
- 2026.06.06 Concurrent Writes to the Same Sheet Break: LockService and the 30-Run Ceiling
- 2026.06.05 Your /exec URL Must Not Change on Every Redeploy
- 2026.06.04 External Calls Fail Sometimes: Exponential Backoff and a Retry Budget
- 2026.06.03 HtmlService Apps: Shell First, Then Async Load and Mobile Back
- 2026.06.02 When to Graduate from GAS: Limit Signals and Moving to an External DB
- 2026.06.01 Adding a Brain and an Engine Room to GAS: Vertex AI and Cloud Run