badges/shields · error · NotFound

tag not found

Error message

tag not found

What it means

The npm version service fetches a package's dist-tags document from `${registryUrl}/-/package/${slug}/dist-tags` and throws NotFound 'tag not found' when a tag parameter was supplied but is not a key in that object. This is an explicit `in` check on the dist-tags map, so it precisely detects unknown tags; a 404 from the registry is separately mapped to 'package not found'.

Source

Thrown at services/npm/npm-version.service.js:85

  }

  async handle(namedParams, queryParams) {
    const { scope, packageName, tag, registryUrl } =
      this.constructor.unpackParams(namedParams, queryParams)

    const slug =
      scope === undefined
        ? packageName
        : this.constructor.encodeScopedPackage({ scope, packageName })

    const packageData = await this._requestJson({
      schema,
      url: `${registryUrl}/-/package/${slug}/dist-tags`,
      httpErrors: { 404: 'package not found' },
    })

    if (tag && !(tag in packageData)) {
      throw new NotFound({ prettyMessage: 'tag not found' })
    }

    return this.constructor.render({
      tag,
      version: packageData[tag || 'latest'],
    })
  }
}

View on GitHub (pinned to 766fd8bc89)

Solutions

  1. Fetch the dist-tags endpoint and pick an existing key for the ?tag parameter
  2. Remove ?tag to use the implicit 'latest' entry
  3. Publish/point the tag on the target registry: `npm dist-tag add --registry <url>`
  4. Correct the tag spelling/casing in the badge URL

Example fix

// before
/npm/v/lodash/badge.svg?tag=stable   // 'stable' not in dist-tags
// after
/npm/v/lodash/badge.svg?tag=latest
Defensive patterns

Strategy: validation

Validate before calling

const distTags = await fetch(`${registry}/-/package/${encodeURIComponent(pkg).replace('%2F', '/')}/dist-tags`).then(r => r.json())
if (tag && !(tag in distTags)) {
  throw new Error(`tag '${tag}' not found; available: ${Object.keys(distTags).join(', ')}`)
}

Type guard

function hasTag(distTags, tag) {
  return typeof distTags === 'object' && distTags !== null && (tag ? tag in distTags : 'latest' in distTags)
}

Try / catch

try {
  return await versionBadge({ pkg, tag })
} catch (e) {
  if (e instanceof NotFound && e.message === 'tag not found') {
    const available = await fetchDistTags(pkg)
    throw new Error(`tag '${tag}' not found. Available: ${Object.keys(available).join(', ')}`)
  }
  throw e
}

Prevention

When it happens

Trigger: GET badge with ?tag=foo where foo is not in the package's dist-tags response; querying a package on a custom registry where that tag was never published; slug (scoped package encoding) fine but tag misspelled.

Common situations: Using tags from one registry against another registry/mirror; tags renamed (e.g. 'next' -> 'beta') breaking old badge URLs; case-sensitive tag mismatch.

Related errors


AI-assisted analysis of badges/shields@766fd8bc89 (2026-08-30). Data as JSON: /api/errors/72376966f9656198. Report an issue: GitHub.