badges/shields · error · NotFound
tag not found
Error message
tag not found
What it means
In npm-base's fetchPackageData, when no explicit version is requested the service resolves the package via a dist-tag (default 'latest'). It reads json['dist-tags'][registryTag] and, when that lookup fails to yield a usable version, throws NotFound 'tag not found'. Note the try/catch here actually only catches exceptions; in practice a missing tag yields undefined and the error effectively fires when the tag is absent or the JSON shape is unexpected.
Source
Thrown at services/npm/npm-base.js:139
url = `${registryUrl}/${scoped}`
}
const json = await this._requestJson({
// We don't validate here because we need to pluck the desired subkey first.
schema: Joi.any(),
url,
httpErrors: { 404: 'package not found' },
})
let packageData
if (scope === undefined && tag === undefined) {
packageData = json
} else {
const registryTag = tag || 'latest'
let latestVersion
try {
latestVersion = json['dist-tags'][registryTag]
} catch (e) {
throw new NotFound({ prettyMessage: 'tag not found' })
}
try {
packageData = json.versions[latestVersion]
} catch (e) {
throw new InvalidResponse({ prettyMessage: 'invalid json response' })
}
}
return this.constructor._validate(packageData, packageDataSchema)
}
async fetch({
registryUrl,
scope,
packageName,
schema,
abbreviated = false,
}) {View on GitHub (pinned to 766fd8bc89)
Solutions
- List the package's actual tags (npm dist-tags ls <pkg> or the registry dist-tags endpoint) and use one that exists
- Omit the tag parameter to use the default 'latest' tag
- Create the missing tag with `npm dist-tag add <pkg>@<version> <tag>` if you own the package
- If you maintain the badge service, replace the try/catch with an explicit `if (!latestVersion) throw new NotFound(...)` check
Example fix
// before <service>/<pkg>/badge.svg?tag=canary // no 'canary' dist-tag // after <service>/<pkg>/badge.svg?tag=next // existing dist-tag, or omit ?tag
Defensive patterns
Strategy: type-guard
Validate before calling
const meta = await fetch(`${registry}/${pkg}`).then(r => r.json())
const tags = meta && meta['dist-tags'] ? Object.keys(meta['dist-tags']) : []
if (tag && !tags.includes(tag)) throw new Error(`tag '${tag}' not in [${tags.join(', ')}]`) Type guard
function hasDistTag(meta, tag) {
return Boolean(meta && meta['dist-tags'] && typeof meta['dist-tags'][tag || 'latest'] === 'string')
} Try / catch
try {
return await npmBadge({ pkg, tag })
} catch (e) {
if (e instanceof NotFound && e.message === 'tag not found') {
return npmBadge({ pkg }) // fall back to 'latest'
}
throw e
} Prevention
- Run `npm dist-tags ls <pkg>` before hardcoding a tag in a badge URL
- Default to omitting ?tag so 'latest' is used
- Never rely on try/catch around plain property access — missing keys return undefined, not throw
- Validate tag names against the registry's dist-tags response in your URL builder
When it happens
Trigger: Requesting a badge with ?tag=beta (or any tag) that the package's dist-tags object does not contain; a registry response missing the 'dist-tags' key entirely; typo'd tag names in the badge URL.
Common situations: Packages that never published a 'next'/'beta' tag being queried with those tags; tags deleted after a publish was undone; third-party registries returning non-standard metadata without dist-tags.
Related errors
AI-assisted analysis of badges/shields@766fd8bc89 (2026-08-30).
Data as JSON: /api/errors/645b6030ba0e14f0.
Report an issue: GitHub.