Every example in node-google-spreadsheet stops mid identifier, and the auth import is not a declared dependency
Google Sheets API wrapper for Javascript / Typescript
At a glance
- What is it?
- node-google-spreadsheet wraps the Google Sheets API for Node with parallel row and cell interfaces and automatic retries. Its front page documents the flows a user needs most, then cuts each of them at the point where a name would appear, and its first example imports a package the manifest does not list.
- Who is it for?
- This wrapper earns its place for teams doing ordinary read, write and share work against a sheet, and its own page says plainly that it trades away the parallel request batching you would want for throughput. Before you adopt it, confirm that google-auth-library is installed in your project, because the first example imports it and the manifest declares only es-toolkit and ky.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 37 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Every example that ends a file also ends inside a token
Three separate listings stop part way through an identifier. The basics listing ends on an unclosed string:
const doc = new GoogleSpreadsheet('<the sheet ID fromThe cells listing ends on `await sheet.saveUpdatedCel`, and the sharing listing ends on `await newDoc.setPublicAcces`. The page itself then ends at a heading reading `### Co`, which follows a Sponsors section whose last complete line is `None yet - get in touch!`. What gets cut is not incidental. Naming a document, persisting edited cells, and opening a document to the public are the three calls where the argument matters most, and each one stops at the character before the name would appear. Everything before that point, imports, auth construction, scope arrays and reads, is intact and copyable.
The auth example imports a package the manifest does not declare
The basics listing opens with `import { JWT } from 'google-auth-library'`, and the feature list credits google-auth-library for the multiple auth options: service account, OAuth, API key, ADC. The manifest lists exactly two runtime dependencies, es-toolkit at `^1.47.0` and ky at `^2.0.2`. google-auth-library is not one of them, so the package the first example needs must be installed by whoever runs it, and neither the install line (`npm i google-spreadsheet`, with pnpm, yarn and bun named as alternatives) nor any prerequisite list mentions it. Credentials in that example arrive from the environment as `process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL` and `process.env.GOOGLE_PRIVATE_KEY`. The repository takes that seriously enough to carry an `.env.schema` file and a `.bumpy` directory at the root, with `@varlock/bumpy` in the development dependencies, and the page opens with two identical empty links to varlock.dev.
loadCells fills a cache and returns nothing
The cell interface is two steps, and the comment on the first states it in capitals:
await sheet.loadCells('A1:E10'); // loads range of cells into local cache - DOES NOT RETURN THE CELLS
console.log(sheet.cellStats); // total cells, loaded, how many non-empty
const a1 = sheet.getCell(0, 0); // access cells using a zero-based index
const c6 = sheet.getCellByA1('C6'); // or A1 style notationAfter the load, `cellStats` reports totals, how many were loaded and how many are non-empty, and only then does addressing a cell return anything. Each cell exposes `value`, `formula` and `formattedValue`, and takes writes to `textFormat` and `note`. The practical consequence is that the range you request is the range you pay for, and a blank result after a load is a question about what was loaded rather than about what the sheet holds. The listing also shows the two notations side by side, zero-based coordinates and A1 style, and does not say how they interact.
Rows carry a type parameter, and the cell listing shows no equivalent
The row interface is the one documented with a generic:
type UsersRowData = {
name: string;
email: string;
type?: 'admin' | 'user';
};
const userRows = await sheet.getRows<UsersRowData>();
userRows[0].get('name'); // <- TS is happy, knows it will be a string
userRows[0].get('badColumn'); // <- will throw a type errorReading and writing goes through `get` and `set` on the row object, and rows are created with `addRow` or `addRows` from objects keyed by header name, where a new sheet can be created with `headerValues` such as `['name', 'email']`. `getRows` accepts `{ limit, offset }`, so paging is a parameter of the same call. Nothing equivalent is shown for cells, where the type story is left to whatever the cell object happens to expose. Two interfaces over one sheet is a real design choice here rather than an accident of the docs.
Sharing needs a second scope on top of spreadsheets
The first example asks for one scope. The sharing example asks for two, and carries an inline note saying that sharing-related calls require the google drive scope:
scopes: [
'https://www.googleapis.com/auth/spreadsheets',
// note that sharing-related calls require the google drive scope
'https://www.googleapis.com/auth/drive.file',
],So the jump from reading and writing cells to inviting anyone is also a jump from one API to two, and it is where a service account that could edit a spreadsheet perfectly well turns out to be unable to grant access to it. Both scopes are declared in the JWT constructor, which means the extra permission is something you arrange at setup rather than something you add later, and the visible listing shows no way to widen it after the client exists. The calls on the other side of that boundary are sharing with one address, sharing with a domain, and the call that makes a document public.
The published tarball ships source beside the build
The `files` array is the first thing worth reading if you want to know what lands in a consumer's node_modules:
"files": [
"dist",
"src/index.ts",
"src/lib"
],Alongside the compiled `dist` the package publishes its TypeScript entry point and its lib directory. The `exports` map points the root at a types file, an ES module at `./dist/index.js` and a CommonJS build at `./dist/index.cjs`, and `main`, `module` and `types` are repeated at the top level for older resolvers, with `"type": "module"` setting the package as ESM by default. The build script is tsdown, with a watch mode beside it. Shipping source means the wrapper sitting between you and the API is readable in your own dependency tree, which is a fair trade for a package that hides a lot of request shaping.
npm leads the install line, bun leads the scripts
Installation is `npm i google-spreadsheet`, with pnpm, yarn and bun named as alternatives. The repository itself is built around bun: `bun.lock` and `bunfig.toml` at the root, a `lint:fix` script written as `bun run lint --fix`, and a `.husky` directory for commit hooks. Linting itself is eslint with an explicit `.ts` extension and an `.eslintrc.cjs` at the root, alongside a `.eslintignore`. A `.node-version` file pins a node version for contributors, and one script in the manifest does nothing but print `node -v`. The mixed signals are harmless for a consumer, but they do mean the maintainer's own commands will not transfer verbatim to a project that never installed bun.
The docs site README is generated and says not to edit it
The documentation is served by docsify out of the docs directory, at the GitHub Pages address the homepage field points to, and the README on that site is a copy rather than the original. The `readme:copy` script prepends a banner to the root README and writes the result to `docs/README.md`, and the banner is explicit: `<!-- DO NOT EDIT THIS FILE, EDIT MAIN README.md AND RUN bun readme:copy instead -->`, followed by a line introducing the document as a welcome to the docs site. The docs copy is therefore one command behind the repository front page unless somebody remembers to run it, and the workflow the banner names is a bun command. The Support section on the same page states that the module was written and is actively maintained by Theo Ephraim, asks commercial users to buy him a beer, and lists no sponsors.
Editorial conclusion
This wrapper earns its place for teams doing ordinary read, write and share work against a sheet, and its own page says plainly that it trades away the parallel request batching you would want for throughput. Before you adopt it, confirm that google-auth-library is installed in your project, because the first example imports it and the manifest declares only es-toolkit and ky. Read the two scope examples together, since sharing needs drive.file on top of spreadsheets and that has to be set at construction. Treat the front page as partial: the calls that name a document, save updated cells and make a document public are all cut mid token, and the docs site copy of the README is produced by a script that has to be run by hand. The maintenance picture is a single author, no sponsors listed, and a commit line that most recently moved on 2026-09-04.
Frequently asked questions
How do I install node-google-spreadsheet?
The documented line is npm i google-spreadsheet, with pnpm, yarn and bun named as equally acceptable. The package publishes two runtime dependencies, es-toolkit and ky, and its manifest also declares an ES module build, a CommonJS build and a types file under a single exports entry.
How does node-google-spreadsheet authenticate with Google?
Through google-auth-library, which the page credits for the multiple auth options: service account, OAuth, API key and ADC. The basics example constructs a JWT with an email, a private key and a scope, reading the first two from GOOGLE_SERVICE_ACCOUNT_EMAIL and GOOGLE_PRIVATE_KEY in the environment. Note that google-auth-library is not among the two runtime dependencies the manifest declares, so it has to be installed alongside.
Does node-google-spreadsheet retry failed requests?
Yes. The feature list credits automatic retries with exponential backoff for failed or rate-limited requests, powered by ky, and links the constructor as the place where that behaviour is customizable. A configurable constructor means the retry policy is a decision left to the caller rather than a fixed constant.
When should I use the Google Sheets API directly instead of this wrapper?
The project's own answer is that it makes trade-offs for simplicity of the interface, so if speed and efficiency are extremely important you may want the API directly, because it provides a mechanism to make many requests in parallel. The same section notes that many lesser-used features of the API are not implemented in the wrapper yet.
What scope do I need to share a Google spreadsheet from Node?
Two, and they are set in the auth constructor. Reading and writing cells uses https://www.googleapis.com/auth/spreadsheets, while the sharing calls additionally require the drive scope, https://www.googleapis.com/auth/drive.file. The page notes this inline in the sharing example rather than in the basics one.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/theoephraim-node-google-spreadsheet)