Structure a large Apps Script project
Files, modules, and naming for maintainability — when Northwind's script grows past 500 lines.
Published Jul 19, 2025
A script that starts as fifty lines in Code.gs rarely stays that way. New
triggers, new integrations, and new edge cases accumulate, and within a few
months the single file is a thousand lines deep and nobody can find anything.
The code still works — it is just painful to change.
The cure is structure: deliberate file boundaries, consistent naming, and a few habits that keep the project navigable as it grows. None of it is enforced by Apps Script, which is precisely why you have to impose it yourself. This guide covers the conventions that keep a large project maintainable.
File layout
Apps Script does not have real modules. Every .gs file in a project is loaded
into one shared global namespace, so functions and constants from any file are
visible everywhere. The file boundaries are organisational, not technical — they
exist to help you, not the runtime.
Because of that, split files by concern rather than by feature. Each file should own one kind of responsibility, so when you need to change how, say, logging works, there is exactly one file to open.
Code.gs — entry points only (triggers, menu items, doGet/doPost)
lib_sheet.gs — readSheet, writeSheet and other Sheets helpers
lib_claude.gs — callClaude, prompt construction and management
lib_log.gs — log, alert, and the safe-wrapper for error handling
sync_stripe.gs — Stripe-specific sync logic
sync_github.gs — GitHub-specific sync logic
Two patterns make this layout work:
Code.gsholds only entry points. Triggers and menu handlers live here and do nothing but call into the library files. Anyone opening the project sees every way it can be invoked in one place.lib_files hold reusable helpers;sync_files hold one integration each. A prefix groups related files together in the editor’s file list and signals intent at a glance.
Naming
Consistent names let you guess where a function lives without searching. Pick a convention and apply it everywhere.
- Entry points are verb phrases describing the action —
pullStripeCharges,sendWeeklyReport. Reading the function list reads like a list of jobs. - Helpers are namespaced by their module with a noun or verb —
sheetRead,slackPost,logError. The prefix tells you which file to open and prevents two helpers from colliding in the shared namespace. - Constants are
SCREAMING_SNAKE_CASEand defined at the top of the file that owns them, not scattered or duplicated.
Because everything shares one namespace, a collision is a real risk — two
functions named format in different files silently overwrite each other. The
module prefix on helpers is what prevents that.
Avoid
A few patterns reliably make a project harder to work with as it grows:
- One mega
Code.gs. A 2,000-line file forces constant scrolling and makes diffs unreadable. Split it before it gets there, not after. - Helpers scattered across every file. If
readSheetlives inCode.gs,writeSheetinsync_stripe.gs, and a third helper somewhere else, nobody can find them. Keep each kind of helper in its one home file. - Global mutable state. Variables that several files read and write turn every bug into a hunt across the whole project. Pass values into functions as arguments and return results instead.
Once a project is split into focused files, version control becomes far more valuable — each change touches one file and reads as a clean diff. Pairing this structure with Use clasp and Git for Apps Script gives you real diffs, branching, and code review on top of the layout described here.
Common mistakes
- Splitting by feature instead of by concern, so “Sheets code” ends up spread across five files. Split by responsibility — one file per kind of work.
- Assuming file boundaries create isolation. They do not; every file shares the same global namespace, so naming discipline is what actually prevents collisions.
- Letting
Code.gsaccumulate logic. Keep it to entry points so the project’s surface area stays obvious. - Inconsistent naming, so finding a function means a full-text search every time. A convention applied everywhere makes names predictable.
- Relying on global variables to share state between files, which makes changes unpredictable and bugs hard to trace.