Concept
The problem
const API_KEY = 'sk_live_9f8a…'; // ❌ visible to anyone who can open the script
const ALERT_EMAIL = 'me@example.com'; // ❌ must edit code to change it
Anyone with edit access to the sheet can open Extensions → Apps Script and read the key. If you share a copy of the file, the key goes with it. Settings like email addresses also shouldn't need a code edit.
Three kinds of properties
| Store | Shared with | Use for |
|---|---|---|
getScriptProperties() |
Everyone running this script | API keys, webhook tokens, global settings, counters |
getUserProperties() |
Only the current user | Each user's own preferences |
getDocumentProperties() |
Everyone using this file (bound scripts / add-ons) | Settings for one particular spreadsheet |
Script properties are the ones you'll use most.
Setting values in the UI
- Editor → Project Settings (gear icon).
- Scroll to Script Properties → Add script property.
- Property:
API_KEY, Value: your key. Save.
This keeps the key out of your code. It's still visible to people with edit access to the script project, so share edit access only with people you trust.
Reading and writing from code
const props = PropertiesService.getScriptProperties();
// Read
const apiKey = props.getProperty('API_KEY');
if (!apiKey) throw new Error('API_KEY is not set in Script Properties');
// Write one
props.setProperty('ALERT_EMAIL', 'store@example.com');
// Write several
props.setProperties({ GST_RATE: '0.03', CURRENCY: 'INR' });
// Read all as an object
const all = props.getProperties();
console.log(all.GST_RATE);
// Delete
props.deleteProperty('OLD_SETTING');
All values are stored as strings. Convert when reading: Number(props.getProperty('GST_RATE')), or for objects:
props.setProperty('CONFIG', JSON.stringify({ reorderDays: 7, ccManager: true }));
const config = JSON.parse(props.getProperty('CONFIG') || '{}');
A config helper
Put all settings in one place with defaults:
function getConfig() {
const p = PropertiesService.getScriptProperties().getProperties();
return {
alertEmail: p.ALERT_EMAIL || Session.getEffectiveUser().getEmail(),
gstRate: Number(p.GST_RATE || 0.03),
apiKey: p.API_KEY || ''
};
}
Limits and what not to store
- Each value: about 9 KB. Total per store: about 500 KB.
- Properties are for small settings, keys and counters — not for data tables. Data belongs in a sheet.
- Frequently changing values (like a progress pointer) are fine, but reading/writing hundreds of times a second isn't — use
CacheServicefor short-lived data.
Key points
- Keep secrets and settings out of code: Script Properties.
- Values are strings — convert numbers and JSON when reading.
- Fail clearly if a required property is missing.