avatarsniff

Sniff out generic default avatars, straight from the pixels.

A lot of users never set a profile picture, so providers serve a boring auto-generated default — an initial on a coloured square (Google), the Gravatar mystery-person, a solid placeholder. avatarsniff detects those from the image pixels so you can replace them with something better instead of showing the generic one. Decodes PNG/JPEG/GIF/WEBP/SVG up to 10MB, framework-agnostic, zero dependencies, server and client.

Install

pnpm add avatarsniffcopy

Try it

These run entirely in your browser via detectFromImageData. The samples are drawn on a canvas and classified live — drop your own below.

Google default
Default (green)
Real photo
Logo on white
Solid colour

Browser

import { detectFromImageData } from "avatarsniff";

const ctx = canvas.getContext("2d");
ctx.drawImage(avatarImg, 0, 0, 64, 64);

const { isDefault, reason } = detectFromImageData(
  ctx.getImageData(0, 0, 64, 64)
);

if (isDefault) {
  // generic provider default — swap in your own avatar
}

Server (Node)

Batteries included and still zero-dependency (every decoder is bundled into the build): PNG, GIF and JPEG decode in pure JS on every runtime. For WEBP and SVG in plain Node, opt into a subpath — import "avatarsniff/webp" or import "avatarsniff/svg" — so their wasm only loads when you need it. Inputs over 10MB are rejected before decoding.

import { detectDefaultAvatar, detectDefaultAvatarFromUrl } from "avatarsniff";

// batteries included — decodes PNG/JPEG/GIF/WEBP/SVG, up to 10MB, zero deps
const result = await detectDefaultAvatar(bytes);

const fromUrl = await detectDefaultAvatarFromUrl(user.photoUrl);
// null if the URL is missing or the fetch fails

How it decides

It keys on structure, never a hard-coded palette (so it keeps working as providers add colours). An image is a default when the dominant background is a flat colour that is neither near-white nor near-black, there is a small near-white glyph (the initial), and there is almost no other coloured content.