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

@broberg/gravatar

v0.2.1

Published

Isomorphic Gravatar helper (SHA-256) + initials fallback. Headless core + React and Preact adapter components with 3-tier fallback: uploaded picture → Gravatar → generated initials.

Readme

@broberg/gravatar

Gravatar URLs and avatar initials for the fleet. Zero runtime dependencies, isomorphic.

npm i @broberg/gravatar
import { gravatarUrl, gravatarLookup, getInitials } from "@broberg/gravatar";

await gravatarUrl("[email protected]", { size: 80, default: "404" });
await gravatarLookup("[email protected]");   // 'yes' | 'no' | 'unknown'
getInitials("Christian Broberg");          // 'CB'

Also ships @broberg/gravatar/react and @broberg/gravatar/preact.

gravatarLookup() — three outcomes, because there are three

const presence = await gravatarLookup("[email protected]");
if (presence !== "unknown") cache.set(email, presence);   // never cache a guess

| Response | Result | Meaning | |---|---|---| | 404 | no | genuinely no avatar | | 200 | yes | | | 5xx · a throw · a timeout · a surprise status | unknown | do not cache this as a no |

Why it exists (v0.2.0). The old boolean collapsed three facts into two: a 503 from Automattic and a dropped connection both came back false, which reads as "there is no picture".

And you cache that answer — you have to, or you ask Automattic on every page render. So a ten-second hiccup cost a user their avatar until the cache expired, with nothing at the call-site able to tell. Found by a consumer on the day they adopted this package.

gravatarExists() is unchanged for existing callers and still cannot tell those two apart — both are false. If you store its result, you are storing a guess on every failure. Use gravatarLookup for anything you cache.

getInitials() — letters, not characters

getInitials("Lens (verifikation)");   // 'LV'    (was 'L(' before v0.2.0)
getInitials(null, "[email protected]");   // 'X'     (was 'X@')
getInitials("  ");                    // '??'    (was two spaces)
getInitials("李 明");                  // '李明'
getInitials("José");                  // 'JO'

Splits on non-letter/non-digit, unicode-aware — so CJK and accented Latin work, which an ASCII-only test suite would never have caught.

The email branch takes the prefix before @. That is what its documentation always claimed; the code read the whole address and only looked right because most addresses happen to begin with two letters.

An email in the name slot works (v0.2.1). getInitials("[email protected]") → CB, identical to getInitials(null, "[email protected]") — because navn = bruger.navn || bruger.email is an ordinary call-site and the two arguments must agree. Detection is conservative: exactly one @, non-empty and whitespace-free on both sides, so "Anne @ Hansen" stays a name.

0.2.0 got this wrong and shipped it: every address in the name slot came out ending in the TLD's first letter ([email protected] → CD). The acceptance criterion that should have caught it asserted the same string in the email argument only — the claim was about the common case, the test was about one argument position, and both were green.

Known limitation, deliberate. getInitials("Jens Hansen, direktør") returns JD — it takes the title as the surname. Truncating at the first comma would fix the Danish "Name, Title" form and break the equally common "Surname, Firstname" export. Two conventions, opposite fixes, and nothing in the string to tell them apart. Left alone rather than guessed at.

The core is async, on purpose

gravatarHash and gravatarUrl return promises because they use crypto.subtle — the one hashing API that exists in Node, Bun, Deno, workers and the browser.

A Node-only consumer expects a synchronous createHash and will be surprised. That is the isomorphism tax, and it is deliberate: the alternative is two code paths and a bundler condition on every consumer.

API

| Export | | |---|---| | gravatarHash(email) | SHA-256 of the trimmed, lowercased address | | gravatarUrl(email, { size?, default? }) | the full image URL | | gravatarLookup(email, size?) | 'yes' \| 'no' \| 'unknown' | | gravatarExists(email, size?) | boolean — lossy; see above | | getInitials(name?, email?) | up to two uppercase characters, or '??' |