npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

betterspread

v0.0.3

Published

TypeScript Google Sheets wrapper with typed rows, cell formatting, and Zod schema validation

Downloads

300

Readme

betterspread

TypeScript Google Sheets wrapper with typed rows, cell formatting, and Zod schema validation.

Documentation

Install

bun add betterspread

Quick Start

import { Connection, Sheet } from "betterspread"

const conn = new Connection({ credentialsPath: "./credentials.json" })
const sheet = new Sheet("My Spreadsheet", conn)
await sheet.open()
const tab = await sheet.getTab("Sheet1")

// Read all values
const rows = await tab.values()
console.log(rows[0][0].value) // first cell of first row

// Read with format data
const rowsWithFormat = await tab.values()
rowsWithFormat[0][0].format // Format instance or null

// Read a specific row
const row = await tab.getRow({ serialNo: 1 })

// Read a specific cell
const cell = await tab.getCell({ cellName: "B3" })

// Append rows
await tab.append({ values: ["Alice", "[email protected]", "30"] })
await tab.append({ values: { Name: "Bob", Email: "[email protected]" } })

// Delete rows
await tab.delRow({ start: 3 })
await tab.delRow({ start: 3, end: 5 })

// Delete cells with shift
await tab.delCell({ start: "B2" })
await tab.delCell({ start: "A1", end: "C3", shift: "left" })

Rows

const row = await tab.getRow({ serialNo: 1 })

// Update cells
await row.update({ values: ["x", "y", "z"] })
await row.update({ values: { Name: "Alice", Age: "25" } })
await row.update({ values: ["x", "y"], inputFormat: "USER_ENTERED" })

// Style entire row
await row.style(new Format({ bgColor: "#ff0000" }))

// Append cell to end of row
await row.appendCell("new-value")
await row.appendCell(["x", "y", "z"])

// Clear row contents
await row.clear()

// Refresh from API
await row.refetch()

// Delete the row
await row.delete()

// Access cells by column label
row.get("A") // Cell
row.get("B") // Cell

Cells

const cell = await tab.getCell({ cellName: "B3" })

// Read
console.log(cell.value) // "cell value"
console.log(cell.header) // column header or ""

// Update
await cell.update({ value: "new-value" })
await cell.update({ value: "new-value", format: new Format({ bold: true }) })
await cell.update({ value: "=SUM(A1:A10)", inputFormat: "USER_ENTERED" })

// Style
await cell.style(new Format({ bgColor: "#00ff00", italic: true }))

// Clear
await cell.clear()

// Delete (shift left or up)
await cell.delete("left")
await cell.delete("up")

Zod Schema Validation

import { z } from "zod"

const UserSchema = z.object({
  Name: z.string(),
  Email: z.string().email(),
  Age: z.number(),
})

tab.setSchema(UserSchema)

// Validates on append
await tab.append({ values: { Name: "Alice", Email: "[email protected]", Age: 30 } })
// Throws ValidationError
await tab.append({ values: { Name: "Bob", Email: "bob", Age: "not-a-number" } })

// Also validates array append and row updates
await tab.append({ values: ["Alice", "[email protected]", "30"] }) // Age is string, schema expects number

Cell Formatting

import { Format } from "betterspread"

const fmt = new Format({
  bgColor: "#ff0000",
  bold: true,
  italic: true,
  fontSize: 14,
  horizontalAlign: "center",
  borders: {
    top: { style: "solid", color: { red: 0, green: 0, blue: 0 } },
    bottom: { style: "dashed" },
  },
})

await cell.style(fmt)

// Extend existing format
const base = new Format({ bgColor: "#ff0000", bold: true })
const extended = base.extend({ bgColor: "#00ff00", italic: true })

Formula Support

// Write formulas
await cell.update({ value: "=SUM(A1:A10)", inputFormat: "USER_ENTERED" })
await tab.append({ values: ["=B1*C1"], inputFormat: "USER_ENTERED" })

// Read formulas (returns formula string, not computed value)
await tab.values({ valueRenderOption: "FORMULA" })

Value Render Options

// FORMULA: returns formula strings instead of computed values
await tab.values({ valueRenderOption: "FORMULA" })
await tab.getRow({ serialNo: 1, valueRenderOption: "FORMULA" })
await tab.getCell({ cellName: "A1", valueRenderOption: "FORMULA" })

License

MIT