Hardcoding an API base URL, a retry count, or a notification email directly into a function means every environment change requires editing and redeploying code, which is unnecessary risk for values that are really just configuration rather than logic.
This tutorial centralizes those values behind loadAppConfig and saveAppConfig functions backed by PropertiesService, so the rest of the script reads configuration through one consistent interface instead of scattered constants.
Script Properties are the right property store for values shared across every user of a script, as opposed to User Properties, which are scoped per user, or Document Properties, which travel with a specific bound spreadsheet.
Because loadAppConfig falls back to sensible defaults when a property is missing, the same script can run immediately after being copied into a new project and only needs saveAppConfig called once to move from those defaults to real values.
Need this built? Hire a Google Apps Script developer →
Sheet / project setup
| Property key | Example | Used by |
|---|---|---|
| API_BASE_URL | https://api.example.com | getConfig() |
| API_KEY | **** | Authorization header |
| DEFAULT_SHEET | Configured Data | Target tab name |
What it does
saveAppConfig writes three configuration values as script properties in one call, and loadAppConfig reads them back with defaults applied for any that have not been set yet.
Prerequisites
properties.setProperties accepts an object of key-value pairs and writes all of them in a single call, which is both simpler and faster than calling setProperty repeatedly for each individual key.
Walkthrough
No external authorization is required beyond the script's own permissions; call saveAppConfig once with real values, or set the properties manually from Project Settings in the Apps Script editor.
Edge cases
runSyncWithConfig demonstrates the pattern in practice: it loads the configuration once at the top of the function, then uses apiBaseUrl and maxRetries from that config object throughout the retry loop instead of referencing PropertiesService directly in multiple places.
Testing
Because Script Properties only store strings, loadAppConfig explicitly converts MAX_RETRIES back to a Number, and a missing or corrupted numeric property safely falls back to the default of three rather than becoming NaN and breaking the retry loop's comparison.
Hardening
Call loadAppConfig from the editor and log the returned object to confirm every field resolves to the expected value before relying on it inside a function like runSyncWithConfig that has side effects.
Variations
Moving a script from a sandbox to production should only require calling saveAppConfig with the production values once, which is the entire point of centralizing configuration instead of leaving values embedded in function bodies across the file.
Full code: saveAppConfig(), loadAppConfig(), and runSyncWithConfig()
Call saveAppConfig once with real values, confirm loadAppConfig returns them correctly, then call runSyncWithConfig to see the configuration used in practice.
function saveAppConfig(config) {
var properties = PropertiesService.getScriptProperties();
properties.setProperties({
API_BASE_URL: config.apiBaseUrl,
MAX_RETRIES: String(config.maxRetries),
NOTIFY_EMAIL: config.notifyEmail
});
}
function loadAppConfig() {
var properties = PropertiesService.getScriptProperties();
return {
apiBaseUrl: properties.getProperty('API_BASE_URL') || 'https://example.com/api',
maxRetries: Number(properties.getProperty('MAX_RETRIES')) || 3,
notifyEmail: properties.getProperty('NOTIFY_EMAIL') || ''
};
}
function runSyncWithConfig() {
var config = loadAppConfig();
var attempt = 0;
var success = false;
while (attempt < config.maxRetries && !success) {
attempt++;
var response = UrlFetchApp.fetch(config.apiBaseUrl + '/status', { muteHttpExceptions: true });
if (response.getResponseCode() === 200) {
success = true;
}
}
if (!success && config.notifyEmail) {
MailApp.sendEmail(config.notifyEmail, 'Sync failed', 'runSyncWithConfig failed after ' + attempt + ' attempts.');
}
return success;
}- Line 3: Writes every configuration value in one setProperties call.
- Line 4: Stores the numeric retry count as a string, as Properties requires.
- Line 13: Converts the stored string back to a Number when reading.
- Line 14: Falls back to a default of three retries if the property is missing.
- Line 19: Loads the configuration once at the top of the function.
- Line 29: Sends a failure notification only if a notify email is configured.
Deploy this example
- 01
Open Apps Script
In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.
- 02
Paste and save
Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.
- 03
Authorize once
Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.
- 04
Add the trigger
Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.
Before you run: load app configuration
- 1saveAppConfig called once with real configuration values
- 2Default values in loadAppConfig reviewed as safe fallbacks
- 3Numeric properties explicitly converted with Number, not left as strings
- 4Any function needing configuration calls loadAppConfig rather than PropertiesService directly
- 5Notify email property set if failure alerts are expected
- 6Configuration confirmed correct with a manual loadAppConfig log before production use
- 7Sensitive values kept out of the properties object if they belong in a separate secrets flow
Frequently asked questions
Script Properties are shared by every user running the script, User Properties are scoped to whichever individual account is executing the code, and Document Properties are tied to one specific bound document regardless of who opens it.
PropertiesService stores every value as a string, so a numeric configuration value has to be explicitly parsed back into a number before it can be used in arithmetic or comparisons like the retry loop's condition.
Every property lookup returns null, so each field in the returned object falls back to its documented default instead of throwing or returning undefined.
They can technically be stored the same way, but treating high-sensitivity secrets separately, and being deliberate about who has edit access to the script, reduces the blast radius if the properties are ever exposed.
Yes, Apps Script limits each property value to 9KB and the total properties store to 500KB, which is far more than typical configuration values need but worth knowing before storing large JSON blobs there.
Yes, the Apps Script editor's Project Settings page has a Script Properties section where you can add, edit, or delete key-value pairs directly, which is convenient for one-off changes.