Logo
Overview
making a package manager for google sheets

making a package manager for google sheets

December 1, 2025
5 min read
index
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:

Sheet Integration

Core Components

Service Layer

CLI Layer

Command Interface

Install Service

Publish Service

Upgrade Service

Remove Service

Registry Client

Dependency Resolver

Packager

Local Cache

Sheet Client

Metadata Manager

Playwright

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: InstallResolverRegistry 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: RegistryCache 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: SheetClientPlaywright 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 like package.json, storing your project’s name, version, and direct dependencies
  • __LOCK__: acts like package-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:

  1. validates metadata and extracts all named functions from your sheet
  2. filters out dependency functions (using the lockfile)
  3. analyzes dependencies by parsing formulas to detect function calls
  4. packages everything into a .gspkg with auto-generated metadata
  5. 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

Aspectnpm/pipFormularyWhy Different
Installation TargetLocal filesystemGoogle Sheet via browserNo API exists for named functions
API AccessDirect file/network APIsPlaywright browser automationMust simulate human interaction
Metadata StorageFiles in project directoryNamed functions in the sheetState lives inside the document
RegistryDedicated servers (npm, PyPI)GitHub repo with index.jsonSimpler infrastructure
Package Format.tar.gz, .whl.gspkg (ZIP with JSON)Simplified
AuthenticationAPI tokens, config filesBrowser profilesMust authenticate as a user
IntegrityPer-file hashes, signaturesWhole-package SHA-256Simpler verification
Installation SpeedMilliseconds to secondsSeconds to minutesBrowser automation overhead
Offline SupportFull (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.