Version and roll back a web app safely
Manage Northwind web-app releases without breaking users — deploy slots, blue/green.
Published Sep 17, 2025
When a Northwind web app starts taking real traffic — the registration form for a launch event, the public status page, the webhook receiver behind a payment flow — “edit and re-deploy” stops being safe. A typo at four o’clock on a Friday should not be visible to every visitor a second later. You need a versioning pattern that lets you ship a new build, sanity-check it, and back out fast if something is wrong.
Apps Script has the primitives for this built in — every deployment has its own version number and its own URL. The trick is to treat them like blue/green slots: keep the live one alone, deploy alongside it, swap, and keep the old one warm in case you need to flip back. Below is the pattern, the small helper that makes the swap mechanical, and the things that bite you the first time.
What you’ll need
- An Apps Script web app already deployed once (see Deploy Apps Script as a public web app).
- A way to point users at one URL or another — usually a short DNS record, a redirect on your marketing site, or a constant in another script that calls this web app.
- Editor access to the script so you can create and archive deployments.
The script
// One Script Property holds the current live deployment URL. Anything
// that needs to link to the web app reads from here instead of hard-coding
// the URL — that is what makes the swap a one-line change.
const LIVE_URL_KEY = 'NORTHWIND_WEBAPP_LIVE_URL';
// Two named slots — blue and green — so a person can read at a glance
// which one is currently serving traffic.
const SLOT_KEYS = {
blue: 'NORTHWIND_WEBAPP_BLUE_URL',
green: 'NORTHWIND_WEBAPP_GREEN_URL',
};
/**
* Stores the URL of a freshly deployed version under its slot name.
* Call this once after each new versioned deployment, passing the
* /exec URL the editor gives you.
*
* @param {'blue'|'green'} slot Which slot this build occupies.
* @param {string} url The /exec URL of the new deployment.
*/
function recordDeployment(slot, url) {
if (!SLOT_KEYS[slot]) throw new Error('Unknown slot: ' + slot);
if (!/^https:\/\/script\.google\.com\/.+\/exec$/.test(url)) {
throw new Error('That does not look like an /exec URL.');
}
PropertiesService.getScriptProperties().setProperty(SLOT_KEYS[slot], url);
Logger.log('Recorded ' + slot + ' = ' + url);
}
/**
* Promotes one slot to live. Anything that reads getLiveUrl() will pick
* up the new URL immediately. Logs the previous URL so you can paste it
* back if you need to roll forward later.
*
* @param {'blue'|'green'} slot The slot to make live.
*/
function promote(slot) {
const props = PropertiesService.getScriptProperties();
const next = props.getProperty(SLOT_KEYS[slot]);
if (!next) throw new Error('Slot ' + slot + ' has no URL yet.');
const previous = props.getProperty(LIVE_URL_KEY);
props.setProperty(LIVE_URL_KEY, next);
Logger.log('Promoted ' + slot + '. Previous live URL was: ' + previous);
}
/**
* Returns whichever URL is currently live. Use this from other scripts
* that need to link to the web app — never hard-code the /exec URL.
*
* @return {string} The current live /exec URL.
*/
function getLiveUrl() {
return PropertiesService.getScriptProperties().getProperty(LIVE_URL_KEY);
}
How it works
- The pattern uses two named slots — blue and green. Live traffic always points at one; the other is free for the next build.
recordDeploymentstores the/execURL of a new versioned deployment under its slot name in Script Properties. The regex guards against pasting a/devURL by mistake.promotereads the slot’s URL and writes it to the singleLIVE_URL_KEY. Any caller that usesgetLiveUrl()picks up the swap instantly. The previous URL is logged, not deleted — that is the rollback breadcrumb.getLiveUrlis the seam other code reads from. The whole point of the pattern is that nothing ever embeds a literal/execURL.
Example run
Say live traffic is currently on the blue slot:
| Property | Value |
|---|---|
NORTHWIND_WEBAPP_BLUE_URL | https://script.google.com/.../AKfyV1/exec |
NORTHWIND_WEBAPP_GREEN_URL | (unset) |
NORTHWIND_WEBAPP_LIVE_URL | https://script.google.com/.../AKfyV1/exec |
You ship V2 as a new versioned deployment, then run
recordDeployment('green', 'https://script.google.com/.../AKfyV2/exec')
and promote('green'). Now:
| Property | Value |
|---|---|
NORTHWIND_WEBAPP_BLUE_URL | https://script.google.com/.../AKfyV1/exec |
NORTHWIND_WEBAPP_GREEN_URL | https://script.google.com/.../AKfyV2/exec |
NORTHWIND_WEBAPP_LIVE_URL | https://script.google.com/.../AKfyV2/exec |
Visitors now hit V2. If V2 misbehaves, run promote('blue') and you are back
on V1 without touching the deployment dialog — the swap is a single property
write.
Run it
The promotion flow is deliberately manual — versioning is exactly the kind of thing you do not want to trigger by accident.
- Make your edits in the head version.
- Deploy → Manage deployments → pencil → New version. Copy the
/execURL the dialog shows you. - In the editor, run
recordDeployment('green', 'PASTED_URL')(or'blue', whichever slot is free) — pass the URL as the argument. - Visit the new URL in an incognito window and verify it works.
- Run
promote('green')to cut live traffic over. - Watch the logs and any downstream alerts for a few minutes. If anything is
off, run
promote('blue')to roll back.
Watch out for
- Apps Script will not auto-upgrade users on a deployment. Once they have an
/execURL, they stay on that exact version until you publish a new one and point them at the new URL. That is what makes blue/green possible — and also why hard-coded URLs are a footgun. - The
/devURL is always head, always editor-only. Never share it with users and never paste it intorecordDeployment— the regex guard catches the obvious mistake, but not every variant. - Archive old deployments only after you have been on the new one for a week. Two slots is enough for blue/green; keeping more is just clutter.
- For internal scripts where you control every caller, prefer a feature flag inside one deployment over juggling URLs. The blue/green pattern earns its complexity when external users hold the URL — public pages, webhooks, partner integrations.
- If a
doPostwebhook is involved, remember that the sender (Stripe, GitHub, whoever) has the old URL configured on their side. Change the URL in their dashboard during the cutover, not just here.
Related
Build a branded approval interface
Approve Northwind requests through a custom UI — clients click, decision is logged.
Updated Nov 8, 2025
Build an interactive quiz or assessment app
Run Northwind tests with scoring and feedback — questions in a Sheet, results in another.
Updated Nov 4, 2025
Build a multi-page web app with routing
Structure a real Northwind app across views — query-param routing, shared layout.
Updated Oct 31, 2025
Build a form-to-PDF web service
Convert Northwind form submissions to PDFs on the fly — POST in, PDF out.
Updated Oct 27, 2025
Build an expiring secure-download generator
Issue time-limited Northwind links via a web app — token in URL, server-side check.
Updated Oct 23, 2025