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

@single-studio/plugin-sheets

v0.8.0

Published

Google Sheets for Single Studio: a shared spreadsheet as a data source, with an API key and no backend.

Readme

@single-studio/plugin-sheets

A Google Sheet as a data source for Single Studio — a standings table, a roster, a running order — with an API key and no backend.

npm i @single-studio/plugin-sheets
import { sheets, GoogleSheetsHandler } from '@single-studio/plugin-sheets'

// The range is yours. The id is a default the operator may replace — see below.
const SHEET = { id: '1AbC…', range: 'Standings!A1:D20' }

class MyShow extends GoogleSheetsHandler {
  onRows({ rows }) {
    this.mutate('replace', { prefix: 'standings', members: rows })
  }
}

createVelcroHost({ name: STUDIO_ID, mutations, plugins: [sheets(MyShow, SHEET)] })

The first row becomes column names by default, so a heading of Team Name reaches your graphics as teamName.

What the operator sets, and what you set

The operator's board asks for which sheet, an API key and how often to read.

The range is yours and cannot be overridden. A range is not a preference: your graphics have code expecting columns in an order, and a range that does not match does not show a different view of the same data — it shows the wrong column, confidently, with no error anywhere.

The id is a default the operator may replace. That is a different question from the range, and it took a broadcaster asking to see why: the range is the shape of a sheet, the id is which copy of that shape. A team running two setups off duplicates of one layout needs each pointed at its own, and the graphics cannot tell the difference — which is exactly what makes it safe where a range is not.

Leaving the field blank uses the id you shipped, so changing it in a later release reaches everyone who never overrode it. An operator may paste the sheet's whole address rather than its id; that works, because it is what people actually copy.

The key

Share the sheet as anyone with the link can view, then create an API key with the Sheets API enabled.

type: 'secret' masks the field on the board. It does not hide the key at runtime — the request is made from the browser, so it is in the network tab of the machine running the board. The protection is restriction, not concealment: limit the key to the Sheets API in the Cloud console, and add an HTTP referrer restriction where the studio is served from a known origin.

What it does solve is the failure that actually happens — a key compiled into a bundle and sitting on a public gh-pages branch for a year.

Reading

Only one machine in a room polls, and an unchanged answer says nothing at all — so a read that finds no change costs one request and no document write. Five seconds is the floor, whatever is typed; Google allows sixty reads a minute.

A refusal stops rather than retrying, because a private sheet stays private however many times it is asked. A dropped network retries, because that is what backoff is for.

Licence

MIT.