Two files in the same Apps Script project each declared a list called the same thing. The values matched, so everything worked — until the day someone edited one copy and not the other.
.gs file in a Google Apps Script project shares a single global scope, as if all the files were concatenated. So if two files each declare a top-level var or function with the same name, they collide — and the file that loads last silently wins for the whole project. The trap: the collision is invisible while the duplicated values agree, and surfaces only when someone edits one copy, after which a function quietly uses the other file’s version depending on load order. The fix is modularity the language will not enforce: put anything shared in one file, give every other top-level name an owner prefix, and add a no-op check you run first to confirm the pieces resolve before anything creates or sends. A shared global scope turns “I have two copies” into “I have one copy and a coin flip.”Global scope collision is when two separately-authored files in one runtime define the same top-level name, so only one definition survives and which one is not something you chose.
Table of Contents
Why separate files feel safe and are not
Splitting a project into several files reads like separation of concerns — one file per form, one per helper, one for configuration. In most languages that split also creates separate namespaces, so a name in one file cannot clash with a name in another unless you deliberately export and import it.
Apps Script does not work that way. The files in a project are a presentation convenience; at runtime they are one program in one global scope. A var CONFIG in file A and a var CONFIG in file B are the same CONFIG, and the second one the runtime evaluates overwrites the first. Nothing warns you. The mental model of “separate files, separate namespaces” is exactly the assumption that makes the collision invisible.
In Apps Script, a file boundary is a label on one shared program, not a wall between two.
The bug that waited for an edit
The failure is quiet because duplication that agrees looks identical to no duplication at all.
Picture two files that each declare the same list — a set of options, say — with the same contents, because each was written to be self-contained. Every function that reads the list gets the right values, whichever definition wins, because both definitions are equal. The project runs correctly and the duplication sits there, harmless and invisible, for as long as the two copies stay in sync.
Then someone adds an item to the list in one file and not the other. Now the two definitions disagree, and which one any given function uses depends on the file load order — something you never set and cannot see. A builder in one file may quietly produce output using another file’s stale list. There is no error, just wrong data, and the cause is a duplicate you forgot existed.
Duplicated values that agree are not “working” — they are a bug whose trigger has not been pulled yet.
Fix one: put shared things in exactly one file
The direct remedy is ordinary modularity, applied by hand because the language will not apply it for you. Everything used by more than one file — configuration, option lists, shared helpers — moves into a single shared file that owns it. Every other file references those names and declares none of them.
The moment a value has one home, it cannot disagree with itself. A change is a one-line edit in the shared file that every consumer picks up, and there is no second copy to drift. This is the same discipline as a single mapping file feeding every consumer rather than a copy per caller — one source of truth, no reconciliation.
Fix two: prefix every name that is genuinely local
Not everything should be shared; a file often has names that are legitimately its own. For those, prevent collision by construction: give each a prefix that names its owner, so two files’ local values cannot occupy the same identifier.
A generic TITLE and ROLES in two builder files are a collision waiting to happen; DOCTOR_TITLE and NURSE_ROLES cannot clash no matter the load order. Reserve the short, unprefixed names for the shared file, and let every file-local name carry its origin. It reads slightly heavier and it removes an entire class of silent failure.
Fix three: a no-op check you run before anything real
Because the collision is invisible at author time, add a way to make it visible on demand: a small function that only reads the shared names and reports them — how many options in each list, whether each expected function exists, whether a pattern compiles — and creates nothing, sends nothing, writes nothing.
Run it first, after any change to the file set. If a shared list has the wrong count, or an expected function reports missing, you have found a collision or a missing file before it could produce wrong output or fire a real action. It costs seconds and it converts “did my files load the way I think?” from a hope into a check — the same reason careful automation confirms state before it acts rather than assuming.
What these fixes cannot do
Consolidation and prefixes remove collisions you can foresee; they do not make the runtime warn you about a new one — Apps Script will still silently let a fresh duplicate win, so the discipline has to be maintained, not installed once. The no-op check only catches what it thinks to look at: it confirms the names it names, so a collision in something it does not inspect still passes. And none of this changes the underlying model — the scope is shared whether or not you respect it. These are habits that make a permissive runtime behave like a stricter one; they are not a compiler, and they lapse the moment someone adds an unprefixed name in a hurry.
The short version
Every .gs file in an Apps Script project shares one global scope, so two files declaring the same top-level name collide and the last-loaded file wins — invisibly, as long as the duplicated values agree, and destructively the moment someone edits one copy. Fix it with modularity the language will not enforce: put everything shared in a single file that owns it, give every genuinely-local name an owner prefix so two files cannot occupy one identifier, and add a no-op check function you run first to confirm the shared names resolve before anything creates or sends. Duplicated values that agree are not working code; they are a coin flip waiting for an edit.
Frequently Asked Questions
Do multiple files in an Apps Script project share variables?
Yes. All .gs files in one project execute in a single shared global scope, as though concatenated. A top-level var or function declared in one file is visible to every other file, and two files declaring the same top-level name collide rather than staying separate. File boundaries organize the editor, not the runtime namespace.
What happens when two files declare the same variable name?
One definition overwrites the other, and the survivor is the one whose file the runtime evaluates last. There is no error or warning. While both copies hold the same value the collision is invisible; once they differ, every reference resolves to whichever won by load order, so behavior depends on a sequence you did not set and cannot easily see.
How do I avoid name collisions across Apps Script files?
Two habits. Put everything used by more than one file into a single shared file that solely owns those names, so there is only ever one definition. For names that are genuinely local to a file, give each an owner-specific prefix so two files cannot land on the same identifier. Reserve short, unprefixed names for the shared file alone.
Can I use modules or imports in Apps Script to get separate scopes?
Apps Script does not provide file-level module scoping the way an import/export system does; top-level declarations are global across the project. You can approximate encapsulation by wrapping a file’s internals in a single namespace object or an immediately-invoked function and exposing only what you intend, but the simplest reliable practice is one-owner-per-name plus prefixes.
How can I detect a collision before it causes wrong output?
Write a no-op check function that reads the shared names and reports them — counts of list contents, existence of expected functions, whether patterns compile — and creates or sends nothing. Run it after any change to the file set. A wrong count or a missing function surfaces a collision or an absent file immediately, before it can silently feed wrong data into something that acts.
Why did my project work for months and then suddenly break?
Because the collision was dormant while the duplicated definitions agreed. Everything resolved to equal values regardless of load order, so the project ran correctly. The break came when one copy was edited and the two definitions diverged; from that point, load order decided which value each reference used, and the result was wrong output with no error pointing at the forgotten duplicate.