Tip
image credit for this post! https://www.pixiv.net/en/users/112940740
Important
you can find more info about formulary on the associated sheets.wiki article!
named functions in Google Sheets let you create custom, reusable formulas. but there’s a fundamental problem: no way to share them, version them, or manage dependencies between them. community attempts to solve this—like massive shared spreadsheets and function aggregators—suffer from the same limitations: tedious updates and no versioning.
formulary is a package manager for Google Sheets named functions. think npm or pip, but for spreadsheet formulas. since named functions have no API support, formulary uses browser automation (Playwright) to manipulate Google Sheets directly, maintaining a public registry on GitHub.
key features:
- semantic versioning with dependency resolution
- integrity checking via SHA-256 hashing
- deterministic installations with lockfiles
- multi-account support with profile switching
- private sheets (authentication stored locally)
architecture
formulary’s architecture is organized into four primary layers that orchestrate package management for Google Sheets:
key architectural patterns
service layer orchestration: services (Install, Publish, Upgrade, Remove) are high-level controllers that coordinate multiple components. for example, InstallService orchestrates dependency resolution via Resolver, package download via Registry, extraction via Packager, and sheet manipulation via SheetClient. this keeps complex workflows manageable and testable.
dependency resolution pipeline: Install → Resolver → Registry forms a critical path. the resolver uses resolvelib with a custom provider that queries the registry for available versions. this separation means the constraint-solving algorithm (resolvelib) is decoupled from the package source (GitHub registry), making it possible to support alternative registries in the future.
metadata management: MetadataManager wraps SheetClient to handle the special __GSPROJECT__ and __LOCK__ functions. these functions store JSON data using Google Sheets array literal syntax, which the metadata manager parses and serializes. this abstraction keeps services from dealing with formula parsing minutiae.
caching strategy: Registry → Cache ensures packages are downloaded once and reused. the cache is keyed by (package_name, version), stored in ~/.formulary/cache/. this enables offline installations and significantly speeds up repeat installs.
browser automation isolation: SheetClient → Playwright isolates all browser automation in a single component. SheetClient provides a clean async API (create_function, update_function, delete_function) that hides the complexity of DOM manipulation, wait strategies, and error detection. services call these methods without knowing they’re driving a headless browser.
browser automation
this is the cursed part. named functions have zero API surface—you can’t programmatically create, read, update, or delete them through the Google Sheets API or Apps Script. the only interface is the UI, so formulary drives a headless browser with Playwright to click through menus, fill forms, and extract function definitions.
the Google Sheets UI is a complex web application with dynamic content, timing issues, and error states. formulary handles this with careful wait strategies and robust error detection, but it’s inherently fragile since Google could change the DOM structure and break everything.
this approach comes with serious downsides:
- size: requires a full Chromium install (~200MB)
- speed: browser operations are slow compared to API calls
- fragility: dependent on Google’s UI staying stable
- complexity: managing async DOM updates and race conditions
but it’s the only way to build a real package manager for named functions.
package format
packages are distributed as .gspkg files, which are ZIP archives containing:
package-name-1.0.0.gspkg (zip)├── __GSPROJECT__.json├── __LOCK__.json└── functions.json__GSPROJECT__.json is the manifest (name, version, dependencies). functions.json bundles all function definitions and metadata in a single file. integrity is verified by hashing the entire .gspkg file (SHA-256) and storing that hash in the lockfile.
metadata storage
formulary stores package management state directly in your spreadsheet using two special named functions:
__GSPROJECT__: acts likepackage.json, storing your project’s name, version, and direct dependencies__LOCK__: acts likepackage-lock.json, storing exact resolved versions and integrity hashes
this means the state travels with the sheet. copy a sheet, and the manifest and lockfile come along automatically.
publishing workflow
i wanted publishing to be friction-free. formulary publish handles everything:
- validates metadata and extracts all named functions from your sheet
- filters out dependency functions (using the lockfile)
- analyzes dependencies by parsing formulas to detect function calls
- packages everything into a
.gspkgwith auto-generated metadata - forks the registry repo, adds the package, and opens a pull request
no manual file creation or registry management. just write functions and publish.
formulary vs. standard package managers
| Aspect | npm/pip | Formulary | Why Different |
|---|---|---|---|
| Installation Target | Local filesystem | Google Sheet via browser | No API exists for named functions |
| API Access | Direct file/network APIs | Playwright browser automation | Must simulate human interaction |
| Metadata Storage | Files in project directory | Named functions in the sheet | State lives inside the document |
| Registry | Dedicated servers (npm, PyPI) | GitHub repo with index.json | Simpler infrastructure |
| Package Format | .tar.gz, .whl | .gspkg (ZIP with JSON) | Simplified |
| Authentication | API tokens, config files | Browser profiles | Must authenticate as a user |
| Integrity | Per-file hashes, signatures | Whole-package SHA-256 | Simpler verification |
| Installation Speed | Milliseconds to seconds | Seconds to minutes | Browser automation overhead |
| Offline Support | Full (with cache) | Partial (cached packages only) | Can’t create functions offline |
formulary is open source and available for beta now. check out the github repo and the registry if you’re interested. contributions, whether to formulary or the registry, are welcome.